summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2024-01-16 16:05:15 (GMT)
committerGitHub <noreply@github.com>2024-01-16 16:05:15 (GMT)
commitd2d8332f71ae8059150a9d8d91498493f9b443fc (patch)
tree7bb8e062076a69e3a0b9317c36374da2a4739d76
parenta482bc67ee786e60937a547776fcf9528810e1ce (diff)
downloadcpython-d2d8332f71ae8059150a9d8d91498493f9b443fc.zip
cpython-d2d8332f71ae8059150a9d8d91498493f9b443fc.tar.gz
cpython-d2d8332f71ae8059150a9d8d91498493f9b443fc.tar.bz2
gh-113626: Add allow_code parameter in marshal functions (GH-113648)
Passing allow_code=False prevents serialization and de-serialization of code objects which is incompatible between Python versions.
-rw-r--r--Doc/library/marshal.rst41
-rw-r--r--Doc/whatsnew/3.13.rst8
-rw-r--r--Include/internal/pycore_global_objects_fini_generated.h1
-rw-r--r--Include/internal/pycore_global_strings.h1
-rw-r--r--Include/internal/pycore_runtime_init_generated.h1
-rw-r--r--Include/internal/pycore_unicodeobject_generated.h3
-rw-r--r--Lib/test/test_marshal.py26
-rw-r--r--Misc/NEWS.d/next/Library/2024-01-02-12-41-59.gh-issue-113626.i1PPY_.rst3
-rw-r--r--Python/clinic/marshal.c.h238
-rw-r--r--Python/marshal.c87
10 files changed, 356 insertions, 53 deletions
diff --git a/Doc/library/marshal.rst b/Doc/library/marshal.rst
index 0556f19..c6a006b 100644
--- a/Doc/library/marshal.rst
+++ b/Doc/library/marshal.rst
@@ -23,7 +23,11 @@ transfer of Python objects through RPC calls, see the modules :mod:`pickle` and
:mod:`shelve`. The :mod:`marshal` module exists mainly to support reading and
writing the "pseudo-compiled" code for Python modules of :file:`.pyc` files.
Therefore, the Python maintainers reserve the right to modify the marshal format
-in backward incompatible ways should the need arise. If you're serializing and
+in backward incompatible ways should the need arise.
+The format of code objects is not compatible between Python versions,
+even if the version of the format is the same.
+De-serializing a code object in the incorrect Python version has undefined behavior.
+If you're serializing and
de-serializing Python objects, use the :mod:`pickle` module instead -- the
performance is comparable, version independence is guaranteed, and pickle
supports a substantially wider range of objects than marshal.
@@ -40,7 +44,8 @@ Not all Python object types are supported; in general, only objects whose value
is independent from a particular invocation of Python can be written and read by
this module. The following types are supported: booleans, integers, floating
point numbers, complex numbers, strings, bytes, bytearrays, tuples, lists, sets,
-frozensets, dictionaries, and code objects, where it should be understood that
+frozensets, dictionaries, and code objects (if *allow_code* is true),
+where it should be understood that
tuples, lists, sets, frozensets and dictionaries are only supported as long as
the values contained therein are themselves supported. The
singletons :const:`None`, :const:`Ellipsis` and :exc:`StopIteration` can also be
@@ -54,7 +59,7 @@ bytes-like objects.
The module defines these functions:
-.. function:: dump(value, file[, version])
+.. function:: dump(value, file, version=version, /, *, allow_code=True)
Write the value on the open file. The value must be a supported type. The
file must be a writeable :term:`binary file`.
@@ -62,19 +67,24 @@ The module defines these functions:
If the value has (or contains an object that has) an unsupported type, a
:exc:`ValueError` exception is raised --- but garbage data will also be written
to the file. The object will not be properly read back by :func:`load`.
+ :ref:`Code objects <code-objects>` are only supported if *allow_code* is true.
The *version* argument indicates the data format that ``dump`` should use
(see below).
.. audit-event:: marshal.dumps value,version marshal.dump
+ .. versionchanged:: 3.13
+ Added the *allow_code* parameter.
-.. function:: load(file)
+
+.. function:: load(file, /, *, allow_code=True)
Read one value from the open file and return it. If no valid value is read
(e.g. because the data has a different Python version's incompatible marshal
- format), raise :exc:`EOFError`, :exc:`ValueError` or :exc:`TypeError`. The
- file must be a readable :term:`binary file`.
+ format), raise :exc:`EOFError`, :exc:`ValueError` or :exc:`TypeError`.
+ :ref:`Code objects <code-objects>` are only supported if *allow_code* is true.
+ The file must be a readable :term:`binary file`.
.. audit-event:: marshal.load "" marshal.load
@@ -88,24 +98,32 @@ The module defines these functions:
This call used to raise a ``code.__new__`` audit event for each code object. Now
it raises a single ``marshal.load`` event for the entire load operation.
+ .. versionchanged:: 3.13
+ Added the *allow_code* parameter.
+
-.. function:: dumps(value[, version])
+.. function:: dumps(value, version=version, /, *, allow_code=True)
Return the bytes object that would be written to a file by ``dump(value, file)``. The
value must be a supported type. Raise a :exc:`ValueError` exception if value
has (or contains an object that has) an unsupported type.
+ :ref:`Code objects <code-objects>` are only supported if *allow_code* is true.
The *version* argument indicates the data format that ``dumps`` should use
(see below).
.. audit-event:: marshal.dumps value,version marshal.dump
+ .. versionchanged:: 3.13
+ Added the *allow_code* parameter.
-.. function:: loads(bytes)
+
+.. function:: loads(bytes, /, *, allow_code=True)
Convert the :term:`bytes-like object` to a value. If no valid value is found, raise
- :exc:`EOFError`, :exc:`ValueError` or :exc:`TypeError`. Extra bytes in the
- input are ignored.
+ :exc:`EOFError`, :exc:`ValueError` or :exc:`TypeError`.
+ :ref:`Code objects <code-objects>` are only supported if *allow_code* is true.
+ Extra bytes in the input are ignored.
.. audit-event:: marshal.loads bytes marshal.load
@@ -114,6 +132,9 @@ The module defines these functions:
This call used to raise a ``code.__new__`` audit event for each code object. Now
it raises a single ``marshal.loads`` event for the entire load operation.
+ .. versionchanged:: 3.13
+ Added the *allow_code* parameter.
+
In addition, the following constants are defined:
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index e96fcf9..40f0cd3 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -247,6 +247,14 @@ ipaddress
* Add the :attr:`ipaddress.IPv4Address.ipv6_mapped` property, which returns the IPv4-mapped IPv6 address.
(Contributed by Charles Machalow in :gh:`109466`.)
+marshal
+-------
+
+* Add the *allow_code* parameter in module functions.
+ Passing ``allow_code=False`` prevents serialization and de-serialization of
+ code objects which are incompatible between Python versions.
+ (Contributed by Serhiy Storchaka in :gh:`113626`.)
+
mmap
----
diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h
index 1fd67ce..0a24b12 100644
--- a/Include/internal/pycore_global_objects_fini_generated.h
+++ b/Include/internal/pycore_global_objects_fini_generated.h
@@ -787,6 +787,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(after_in_child));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(after_in_parent));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(aggregate_class));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(allow_code));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(append));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(argdefs));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(arguments));
diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h
index da1f9b6..efb659c 100644
--- a/Include/internal/pycore_global_strings.h
+++ b/Include/internal/pycore_global_strings.h
@@ -276,6 +276,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(after_in_child)
STRUCT_FOR_ID(after_in_parent)
STRUCT_FOR_ID(aggregate_class)
+ STRUCT_FOR_ID(allow_code)
STRUCT_FOR_ID(append)
STRUCT_FOR_ID(argdefs)
STRUCT_FOR_ID(arguments)
diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h
index e285d02..e3ebd80 100644
--- a/Include/internal/pycore_runtime_init_generated.h
+++ b/Include/internal/pycore_runtime_init_generated.h
@@ -785,6 +785,7 @@ extern "C" {
INIT_ID(after_in_child), \
INIT_ID(after_in_parent), \
INIT_ID(aggregate_class), \
+ INIT_ID(allow_code), \
INIT_ID(append), \
INIT_ID(argdefs), \
INIT_ID(arguments), \
diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h
index 2f98740..9fa6c89 100644
--- a/Include/internal/pycore_unicodeobject_generated.h
+++ b/Include/internal/pycore_unicodeobject_generated.h
@@ -669,6 +669,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(aggregate_class);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
+ string = &_Py_ID(allow_code);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ _PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(append);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py
index 3d9d6d5..6e17e01 100644
--- a/Lib/test/test_marshal.py
+++ b/Lib/test/test_marshal.py
@@ -129,6 +129,32 @@ class CodeTestCase(unittest.TestCase):
self.assertEqual(co1.co_filename, "f1")
self.assertEqual(co2.co_filename, "f2")
+ def test_no_allow_code(self):
+ data = {'a': [({0},)]}
+ dump = marshal.dumps(data, allow_code=False)
+ self.assertEqual(marshal.loads(dump, allow_code=False), data)
+
+ f = io.BytesIO()
+ marshal.dump(data, f, allow_code=False)
+ f.seek(0)
+ self.assertEqual(marshal.load(f, allow_code=False), data)
+
+ co = ExceptionTestCase.test_exceptions.__code__
+ data = {'a': [({co, 0},)]}
+ dump = marshal.dumps(data, allow_code=True)
+ self.assertEqual(marshal.loads(dump, allow_code=True), data)
+ with self.assertRaises(ValueError):
+ marshal.dumps(data, allow_code=False)
+ with self.assertRaises(ValueError):
+ marshal.loads(dump, allow_code=False)
+
+ marshal.dump(data, io.BytesIO(), allow_code=True)
+ self.assertEqual(marshal.load(io.BytesIO(dump), allow_code=True), data)
+ with self.assertRaises(ValueError):
+ marshal.dump(data, io.BytesIO(), allow_code=False)
+ with self.assertRaises(ValueError):
+ marshal.load(io.BytesIO(dump), allow_code=False)
+
@requires_debug_ranges()
def test_minimal_linetable_with_no_debug_ranges(self):
# Make sure when demarshalling objects with `-X no_debug_ranges`
diff --git a/Misc/NEWS.d/next/Library/2024-01-02-12-41-59.gh-issue-113626.i1PPY_.rst b/Misc/NEWS.d/next/Library/2024-01-02-12-41-59.gh-issue-113626.i1PPY_.rst
new file mode 100644
index 0000000..5c37dad
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-01-02-12-41-59.gh-issue-113626.i1PPY_.rst
@@ -0,0 +1,3 @@
+Add support for the *allow_code* argument in the :mod:`marshal` module.
+Passing ``allow_code=False`` prevents serialization and de-serialization of
+code objects which is incompatible between Python versions.
diff --git a/Python/clinic/marshal.c.h b/Python/clinic/marshal.c.h
index e6b0f19..c19a3ed 100644
--- a/Python/clinic/marshal.c.h
+++ b/Python/clinic/marshal.c.h
@@ -2,10 +2,14 @@
preserve
[clinic start generated code]*/
-#include "pycore_modsupport.h" // _PyArg_CheckPositional()
+#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+# include "pycore_gc.h" // PyGC_Head
+# include "pycore_runtime.h" // _Py_ID()
+#endif
+#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
PyDoc_STRVAR(marshal_dump__doc__,
-"dump($module, value, file, version=version, /)\n"
+"dump($module, value, file, version=version, /, *, allow_code=True)\n"
"--\n"
"\n"
"Write the value on the open file.\n"
@@ -16,53 +20,95 @@ PyDoc_STRVAR(marshal_dump__doc__,
" Must be a writeable binary file.\n"
" version\n"
" Indicates the data format that dump should use.\n"
+" allow_code\n"
+" Allow to write code objects.\n"
"\n"
"If the value has (or contains an object that has) an unsupported type, a\n"
"ValueError exception is raised - but garbage data will also be written\n"
"to the file. The object will not be properly read back by load().");
#define MARSHAL_DUMP_METHODDEF \
- {"dump", _PyCFunction_CAST(marshal_dump), METH_FASTCALL, marshal_dump__doc__},
+ {"dump", _PyCFunction_CAST(marshal_dump), METH_FASTCALL|METH_KEYWORDS, marshal_dump__doc__},
static PyObject *
marshal_dump_impl(PyObject *module, PyObject *value, PyObject *file,
- int version);
+ int version, int allow_code);
static PyObject *
-marshal_dump(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+marshal_dump(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 1
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_item = { &_Py_ID(allow_code), },
+ };
+ #undef NUM_KEYWORDS
+ #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+ #else // !Py_BUILD_CORE
+ # define KWTUPLE NULL
+ #endif // !Py_BUILD_CORE
+
+ static const char * const _keywords[] = {"", "", "", "allow_code", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "dump",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[4];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2;
PyObject *value;
PyObject *file;
int version = Py_MARSHAL_VERSION;
+ int allow_code = 1;
- if (!_PyArg_CheckPositional("dump", nargs, 2, 3)) {
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 3, 0, argsbuf);
+ if (!args) {
goto exit;
}
value = args[0];
file = args[1];
if (nargs < 3) {
- goto skip_optional;
+ goto skip_optional_posonly;
}
+ noptargs--;
version = PyLong_AsInt(args[2]);
if (version == -1 && PyErr_Occurred()) {
goto exit;
}
-skip_optional:
- return_value = marshal_dump_impl(module, value, file, version);
+skip_optional_posonly:
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ allow_code = PyObject_IsTrue(args[3]);
+ if (allow_code < 0) {
+ goto exit;
+ }
+skip_optional_kwonly:
+ return_value = marshal_dump_impl(module, value, file, version, allow_code);
exit:
return return_value;
}
PyDoc_STRVAR(marshal_load__doc__,
-"load($module, file, /)\n"
+"load($module, file, /, *, allow_code=True)\n"
"--\n"
"\n"
"Read one value from the open file and return it.\n"
"\n"
" file\n"
" Must be readable binary file.\n"
+" allow_code\n"
+" Allow to load code objects.\n"
"\n"
"If no valid value is read (e.g. because the data has a different Python\n"
"version\'s incompatible marshal format), raise EOFError, ValueError or\n"
@@ -72,10 +118,66 @@ PyDoc_STRVAR(marshal_load__doc__,
"dump(), load() will substitute None for the unmarshallable type.");
#define MARSHAL_LOAD_METHODDEF \
- {"load", (PyCFunction)marshal_load, METH_O, marshal_load__doc__},
+ {"load", _PyCFunction_CAST(marshal_load), METH_FASTCALL|METH_KEYWORDS, marshal_load__doc__},
+
+static PyObject *
+marshal_load_impl(PyObject *module, PyObject *file, int allow_code);
+
+static PyObject *
+marshal_load(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 1
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_item = { &_Py_ID(allow_code), },
+ };
+ #undef NUM_KEYWORDS
+ #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+ #else // !Py_BUILD_CORE
+ # define KWTUPLE NULL
+ #endif // !Py_BUILD_CORE
+
+ static const char * const _keywords[] = {"", "allow_code", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "load",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
+ PyObject *file;
+ int allow_code = 1;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ file = args[0];
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ allow_code = PyObject_IsTrue(args[1]);
+ if (allow_code < 0) {
+ goto exit;
+ }
+skip_optional_kwonly:
+ return_value = marshal_load_impl(module, file, allow_code);
+
+exit:
+ return return_value;
+}
PyDoc_STRVAR(marshal_dumps__doc__,
-"dumps($module, value, version=version, /)\n"
+"dumps($module, value, version=version, /, *, allow_code=True)\n"
"--\n"
"\n"
"Return the bytes object that would be written to a file by dump(value, file).\n"
@@ -84,66 +186,150 @@ PyDoc_STRVAR(marshal_dumps__doc__,
" Must be a supported type.\n"
" version\n"
" Indicates the data format that dumps should use.\n"
+" allow_code\n"
+" Allow to write code objects.\n"
"\n"
"Raise a ValueError exception if value has (or contains an object that has) an\n"
"unsupported type.");
#define MARSHAL_DUMPS_METHODDEF \
- {"dumps", _PyCFunction_CAST(marshal_dumps), METH_FASTCALL, marshal_dumps__doc__},
+ {"dumps", _PyCFunction_CAST(marshal_dumps), METH_FASTCALL|METH_KEYWORDS, marshal_dumps__doc__},
static PyObject *
-marshal_dumps_impl(PyObject *module, PyObject *value, int version);
+marshal_dumps_impl(PyObject *module, PyObject *value, int version,
+ int allow_code);
static PyObject *
-marshal_dumps(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+marshal_dumps(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 1
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_item = { &_Py_ID(allow_code), },
+ };
+ #undef NUM_KEYWORDS
+ #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+ #else // !Py_BUILD_CORE
+ # define KWTUPLE NULL
+ #endif // !Py_BUILD_CORE
+
+ static const char * const _keywords[] = {"", "", "allow_code", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "dumps",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[3];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
PyObject *value;
int version = Py_MARSHAL_VERSION;
+ int allow_code = 1;
- if (!_PyArg_CheckPositional("dumps", nargs, 1, 2)) {
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf);
+ if (!args) {
goto exit;
}
value = args[0];
if (nargs < 2) {
- goto skip_optional;
+ goto skip_optional_posonly;
}
+ noptargs--;
version = PyLong_AsInt(args[1]);
if (version == -1 && PyErr_Occurred()) {
goto exit;
}
-skip_optional:
- return_value = marshal_dumps_impl(module, value, version);
+skip_optional_posonly:
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ allow_code = PyObject_IsTrue(args[2]);
+ if (allow_code < 0) {
+ goto exit;
+ }
+skip_optional_kwonly:
+ return_value = marshal_dumps_impl(module, value, version, allow_code);
exit:
return return_value;
}
PyDoc_STRVAR(marshal_loads__doc__,
-"loads($module, bytes, /)\n"
+"loads($module, bytes, /, *, allow_code=True)\n"
"--\n"
"\n"
"Convert the bytes-like object to a value.\n"
"\n"
+" allow_code\n"
+" Allow to load code objects.\n"
+"\n"
"If no valid value is found, raise EOFError, ValueError or TypeError. Extra\n"
"bytes in the input are ignored.");
#define MARSHAL_LOADS_METHODDEF \
- {"loads", (PyCFunction)marshal_loads, METH_O, marshal_loads__doc__},
+ {"loads", _PyCFunction_CAST(marshal_loads), METH_FASTCALL|METH_KEYWORDS, marshal_loads__doc__},
static PyObject *
-marshal_loads_impl(PyObject *module, Py_buffer *bytes);
+marshal_loads_impl(PyObject *module, Py_buffer *bytes, int allow_code);
static PyObject *
-marshal_loads(PyObject *module, PyObject *arg)
+marshal_loads(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 1
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_item = { &_Py_ID(allow_code), },
+ };
+ #undef NUM_KEYWORDS
+ #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+ #else // !Py_BUILD_CORE
+ # define KWTUPLE NULL
+ #endif // !Py_BUILD_CORE
+
+ static const char * const _keywords[] = {"", "allow_code", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "loads",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
Py_buffer bytes = {NULL, NULL};
+ int allow_code = 1;
- if (PyObject_GetBuffer(arg, &bytes, PyBUF_SIMPLE) != 0) {
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (PyObject_GetBuffer(args[0], &bytes, PyBUF_SIMPLE) != 0) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ allow_code = PyObject_IsTrue(args[1]);
+ if (allow_code < 0) {
goto exit;
}
- return_value = marshal_loads_impl(module, &bytes);
+skip_optional_kwonly:
+ return_value = marshal_loads_impl(module, &bytes, allow_code);
exit:
/* Cleanup for bytes */
@@ -153,4 +339,4 @@ exit:
return return_value;
}
-/*[clinic end generated code: output=92d2d47aac9128ee input=a9049054013a1b77]*/
+/*[clinic end generated code: output=1575b9a3ae48ad3d input=a9049054013a1b77]*/
diff --git a/Python/marshal.c b/Python/marshal.c
index 8940582..daec741 100644
--- a/Python/marshal.c
+++ b/Python/marshal.c
@@ -78,6 +78,7 @@ module marshal
#define WFERR_UNMARSHALLABLE 1
#define WFERR_NESTEDTOODEEP 2
#define WFERR_NOMEMORY 3
+#define WFERR_CODE_NOT_ALLOWED 4
typedef struct {
FILE *fp;
@@ -89,6 +90,7 @@ typedef struct {
char *buf;
_Py_hashtable_t *hashtable;
int version;
+ int allow_code;
} WFILE;
#define w_byte(c, p) do { \
@@ -225,6 +227,9 @@ w_short_pstring(const void *s, Py_ssize_t n, WFILE *p)
w_byte((t) | flag, (p)); \
} while(0)
+static PyObject *
+_PyMarshal_WriteObjectToString(PyObject *x, int version, int allow_code);
+
static void
w_PyLong(const PyLongObject *ob, char flag, WFILE *p)
{
@@ -520,7 +525,8 @@ w_complex_object(PyObject *v, char flag, WFILE *p)
}
Py_ssize_t i = 0;
while (_PySet_NextEntry(v, &pos, &value, &hash)) {
- PyObject *dump = PyMarshal_WriteObjectToString(value, p->version);
+ PyObject *dump = _PyMarshal_WriteObjectToString(value,
+ p->version, p->allow_code);
if (dump == NULL) {
p->error = WFERR_UNMARSHALLABLE;
Py_DECREF(pairs);
@@ -549,6 +555,10 @@ w_complex_object(PyObject *v, char flag, WFILE *p)
Py_DECREF(pairs);
}
else if (PyCode_Check(v)) {
+ if (!p->allow_code) {
+ p->error = WFERR_CODE_NOT_ALLOWED;
+ return;
+ }
PyCodeObject *co = (PyCodeObject *)v;
PyObject *co_code = _PyCode_GetCode(co);
if (co_code == NULL) {
@@ -657,6 +667,7 @@ PyMarshal_WriteObjectToFile(PyObject *x, FILE *fp, int version)
wf.end = wf.ptr + sizeof(buf);
wf.error = WFERR_OK;
wf.version = version;
+ wf.allow_code = 1;
if (w_init_refs(&wf, version)) {
return; /* caller must check PyErr_Occurred() */
}
@@ -674,6 +685,7 @@ typedef struct {
char *buf;
Py_ssize_t buf_size;
PyObject *refs; /* a list */
+ int allow_code;
} RFILE;
static const char *
@@ -1364,6 +1376,11 @@ r_object(RFILE *p)
PyObject* linetable = NULL;
PyObject *exceptiontable = NULL;
+ if (!p->allow_code) {
+ PyErr_SetString(PyExc_ValueError,
+ "unmarshalling code objects is disallowed");
+ break;
+ }
idx = r_ref_reserve(flag, p);
if (idx < 0)
break;
@@ -1609,6 +1626,7 @@ PyMarshal_ReadObjectFromFile(FILE *fp)
{
RFILE rf;
PyObject *result;
+ rf.allow_code = 1;
rf.fp = fp;
rf.readable = NULL;
rf.depth = 0;
@@ -1629,6 +1647,7 @@ PyMarshal_ReadObjectFromString(const char *str, Py_ssize_t len)
{
RFILE rf;
PyObject *result;
+ rf.allow_code = 1;
rf.fp = NULL;
rf.readable = NULL;
rf.ptr = str;
@@ -1645,8 +1664,8 @@ PyMarshal_ReadObjectFromString(const char *str, Py_ssize_t len)
return result;
}
-PyObject *
-PyMarshal_WriteObjectToString(PyObject *x, int version)
+static PyObject *
+_PyMarshal_WriteObjectToString(PyObject *x, int version, int allow_code)
{
WFILE wf;
@@ -1661,6 +1680,7 @@ PyMarshal_WriteObjectToString(PyObject *x, int version)
wf.end = wf.ptr + PyBytes_GET_SIZE(wf.str);
wf.error = WFERR_OK;
wf.version = version;
+ wf.allow_code = allow_code;
if (w_init_refs(&wf, version)) {
Py_DECREF(wf.str);
return NULL;
@@ -1674,17 +1694,35 @@ PyMarshal_WriteObjectToString(PyObject *x, int version)
}
if (wf.error != WFERR_OK) {
Py_XDECREF(wf.str);
- if (wf.error == WFERR_NOMEMORY)
+ switch (wf.error) {
+ case WFERR_NOMEMORY:
PyErr_NoMemory();
- else
+ break;
+ case WFERR_NESTEDTOODEEP:
PyErr_SetString(PyExc_ValueError,
- (wf.error==WFERR_UNMARSHALLABLE)?"unmarshallable object"
- :"object too deeply nested to marshal");
+ "object too deeply nested to marshal");
+ break;
+ case WFERR_CODE_NOT_ALLOWED:
+ PyErr_SetString(PyExc_ValueError,
+ "marshalling code objects is disallowed");
+ break;
+ default:
+ case WFERR_UNMARSHALLABLE:
+ PyErr_SetString(PyExc_ValueError,
+ "unmarshallable object");
+ break;
+ }
return NULL;
}
return wf.str;
}
+PyObject *
+PyMarshal_WriteObjectToString(PyObject *x, int version)
+{
+ return _PyMarshal_WriteObjectToString(x, version, 1);
+}
+
/* And an interface for Python programs... */
/*[clinic input]
marshal.dump
@@ -1696,6 +1734,9 @@ marshal.dump
version: int(c_default="Py_MARSHAL_VERSION") = version
Indicates the data format that dump should use.
/
+ *
+ allow_code: bool = True
+ Allow to write code objects.
Write the value on the open file.
@@ -1706,14 +1747,14 @@ to the file. The object will not be properly read back by load().
static PyObject *
marshal_dump_impl(PyObject *module, PyObject *value, PyObject *file,
- int version)
-/*[clinic end generated code: output=aaee62c7028a7cb2 input=6c7a3c23c6fef556]*/
+ int version, int allow_code)
+/*[clinic end generated code: output=429e5fd61c2196b9 input=041f7f6669b0aafb]*/
{
/* XXX Quick hack -- need to do this differently */
PyObject *s;
PyObject *res;
- s = PyMarshal_WriteObjectToString(value, version);
+ s = _PyMarshal_WriteObjectToString(value, version, allow_code);
if (s == NULL)
return NULL;
res = PyObject_CallMethodOneArg(file, &_Py_ID(write), s);
@@ -1727,6 +1768,9 @@ marshal.load
file: object
Must be readable binary file.
/
+ *
+ allow_code: bool = True
+ Allow to load code objects.
Read one value from the open file and return it.
@@ -1739,8 +1783,8 @@ dump(), load() will substitute None for the unmarshallable type.
[clinic start generated code]*/
static PyObject *
-marshal_load(PyObject *module, PyObject *file)
-/*[clinic end generated code: output=f8e5c33233566344 input=c85c2b594cd8124a]*/
+marshal_load_impl(PyObject *module, PyObject *file, int allow_code)
+/*[clinic end generated code: output=0c1aaf3546ae3ed3 input=2dca7b570653b82f]*/
{
PyObject *data, *result;
RFILE rf;
@@ -1762,6 +1806,7 @@ marshal_load(PyObject *module, PyObject *file)
result = NULL;
}
else {
+ rf.allow_code = allow_code;
rf.depth = 0;
rf.fp = NULL;
rf.readable = file;
@@ -1787,6 +1832,9 @@ marshal.dumps
version: int(c_default="Py_MARSHAL_VERSION") = version
Indicates the data format that dumps should use.
/
+ *
+ allow_code: bool = True
+ Allow to write code objects.
Return the bytes object that would be written to a file by dump(value, file).
@@ -1795,10 +1843,11 @@ unsupported type.
[clinic start generated code]*/
static PyObject *
-marshal_dumps_impl(PyObject *module, PyObject *value, int version)
-/*[clinic end generated code: output=9c200f98d7256cad input=a2139ea8608e9b27]*/
+marshal_dumps_impl(PyObject *module, PyObject *value, int version,
+ int allow_code)
+/*[clinic end generated code: output=115f90da518d1d49 input=167eaecceb63f0a8]*/
{
- return PyMarshal_WriteObjectToString(value, version);
+ return _PyMarshal_WriteObjectToString(value, version, allow_code);
}
/*[clinic input]
@@ -1806,6 +1855,9 @@ marshal.loads
bytes: Py_buffer
/
+ *
+ allow_code: bool = True
+ Allow to load code objects.
Convert the bytes-like object to a value.
@@ -1814,13 +1866,14 @@ bytes in the input are ignored.
[clinic start generated code]*/
static PyObject *
-marshal_loads_impl(PyObject *module, Py_buffer *bytes)
-/*[clinic end generated code: output=9fc65985c93d1bb1 input=6f426518459c8495]*/
+marshal_loads_impl(PyObject *module, Py_buffer *bytes, int allow_code)
+/*[clinic end generated code: output=62c0c538d3edc31f input=14de68965b45aaa7]*/
{
RFILE rf;
char *s = bytes->buf;
Py_ssize_t n = bytes->len;
PyObject* result;
+ rf.allow_code = allow_code;
rf.fp = NULL;
rf.readable = NULL;
rf.ptr = s;