[3.5] bpo-29537: Tolerate legacy invalid bytecode (#169)

bpo-27286 fixed a problem where BUILD_MAP_UNPACK_WITH_CALL could
be emitted with an incorrect oparg value, causing the eval loop
to access the wrong stack entry when attempting to read the
function name.

The associated magic number change caused significant problems when
attempting to upgrade to 3.5.3 for anyone that relies on pre-cached
bytecode remaining valid across maintenance releases.

This patch restores the ability to import legacy bytecode generated
by 3.5.0, 3.5.1 or 3.5.2, and modifies the eval loop to
avoid any harmful consequences from the potentially malformed legacy
bytecode.

Original import patch by Petr Viktorin, eval loop patch by Serhiy Storchaka,
and tests and integration by Nick Coghlan.
This commit is contained in:
Nick Coghlan 2017-03-08 16:41:01 +10:00 committed by GitHub
parent bef209d449
commit 93602e3af7
13 changed files with 2828 additions and 2612 deletions

View file

@ -2672,14 +2672,22 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
if (PyErr_ExceptionMatches(PyExc_AttributeError) ||
!PyMapping_Check(arg)) {
int function_location = (oparg>>8) & 0xff;
PyObject *func = (
PEEK(function_location + num_maps));
PyErr_Format(PyExc_TypeError,
"%.200s%.200s argument after ** "
"must be a mapping, not %.200s",
PyEval_GetFuncName(func),
PyEval_GetFuncDesc(func),
arg->ob_type->tp_name);
if (function_location == 1) {
PyObject *func = (
PEEK(function_location + num_maps));
PyErr_Format(PyExc_TypeError,
"%.200s%.200s argument after ** "
"must be a mapping, not %.200s",
PyEval_GetFuncName(func),
PyEval_GetFuncDesc(func),
arg->ob_type->tp_name);
}
else {
PyErr_Format(PyExc_TypeError,
"argument after ** "
"must be a mapping, not %.200s",
arg->ob_type->tp_name);
}
}
Py_DECREF(sum);
goto error;
@ -2689,21 +2697,34 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
Py_ssize_t idx = 0;
PyObject *key;
int function_location = (oparg>>8) & 0xff;
PyObject *func = PEEK(function_location + num_maps);
Py_hash_t hash;
_PySet_NextEntry(intersection, &idx, &key, &hash);
if (!PyUnicode_Check(key)) {
PyErr_Format(PyExc_TypeError,
"%.200s%.200s keywords must be strings",
PyEval_GetFuncName(func),
PyEval_GetFuncDesc(func));
} else {
PyErr_Format(PyExc_TypeError,
"%.200s%.200s got multiple "
"values for keyword argument '%U'",
PyEval_GetFuncName(func),
PyEval_GetFuncDesc(func),
key);
if (function_location == 1) {
PyObject *func = PEEK(function_location + num_maps);
if (!PyUnicode_Check(key)) {
PyErr_Format(PyExc_TypeError,
"%.200s%.200s keywords must be strings",
PyEval_GetFuncName(func),
PyEval_GetFuncDesc(func));
} else {
PyErr_Format(PyExc_TypeError,
"%.200s%.200s got multiple "
"values for keyword argument '%U'",
PyEval_GetFuncName(func),
PyEval_GetFuncDesc(func),
key);
}
}
else {
if (!PyUnicode_Check(key)) {
PyErr_SetString(PyExc_TypeError,
"keywords must be strings");
} else {
PyErr_Format(PyExc_TypeError,
"function got multiple "
"values for keyword argument '%U'",
key);
}
}
Py_DECREF(intersection);
Py_DECREF(sum);
@ -2716,13 +2737,21 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
if (with_call) {
int function_location = (oparg>>8) & 0xff;
PyObject *func = PEEK(function_location + num_maps);
PyErr_Format(PyExc_TypeError,
"%.200s%.200s argument after ** "
"must be a mapping, not %.200s",
PyEval_GetFuncName(func),
PyEval_GetFuncDesc(func),
arg->ob_type->tp_name);
if (function_location == 1) {
PyObject *func = PEEK(function_location + num_maps);
PyErr_Format(PyExc_TypeError,
"%.200s%.200s argument after ** "
"must be a mapping, not %.200s",
PyEval_GetFuncName(func),
PyEval_GetFuncDesc(func),
arg->ob_type->tp_name);
}
else {
PyErr_Format(PyExc_TypeError,
"argument after ** "
"must be a mapping, not %.200s",
arg->ob_type->tp_name);
}
}
else {
PyErr_Format(PyExc_TypeError,

View file

@ -483,9 +483,17 @@ PyImport_Cleanup(void)
#undef STORE_MODULE_WEAKREF
}
/* Issue #29537: handle issue27286 bytecode incompatibility
*
* In order to avoid forcing recompilation of all extension modules, we export
* the legacy 3.5.0 magic number here rather than putting it in a header file.
*
* See Lib/importlib/_bootstrap_external.py for general discussion
*/
PY_UINT32_T _Py_BACKCOMPAT_MAGIC_NUMBER = 168627478;
PY_UINT32_T _Py_BACKCOMPAT_HALF_MAGIC = 3350;
/* Helper for pythonrun.c -- return magic number and tag. */
long
PyImport_GetMagicNumber(void)
{

File diff suppressed because it is too large Load diff

View file

@ -259,6 +259,21 @@ PyRun_InteractiveOneFlags(FILE *fp, const char *filename_str, PyCompilerFlags *f
}
/* Issue #29537: handle issue27286 bytecode incompatibility
* See Lib/importlib/_bootstrap_external.py for general discussion
*/
extern PY_UINT32_T _Py_BACKCOMPAT_HALF_MAGIC;
static int
_check_half_magic(unsigned int read_value, unsigned int halfmagic) {
return (read_value == halfmagic || read_value == _Py_BACKCOMPAT_HALF_MAGIC);
}
extern PY_UINT32_T _Py_BACKCOMPAT_MAGIC_NUMBER;
static int
_check_magic(long read_value, long magic) {
return (read_value == magic || read_value == _Py_BACKCOMPAT_MAGIC_NUMBER);
}
/* Check whether a file maybe a pyc file: Look at the extension,
the file type, and, if we may close it, at the first few bytes. */
@ -290,7 +305,7 @@ maybe_pyc_file(FILE *fp, const char* filename, const char* ext, int closeit)
int ispyc = 0;
if (ftell(fp) == 0) {
if (fread(buf, 1, 2, fp) == 2 &&
((unsigned int)buf[1]<<8 | buf[0]) == halfmagic)
_check_half_magic(((unsigned int)buf[1]<<8 | buf[0]), halfmagic))
ispyc = 1;
rewind(fp);
}
@ -988,7 +1003,7 @@ run_pyc_file(FILE *fp, const char *filename, PyObject *globals,
long PyImport_GetMagicNumber(void);
magic = PyMarshal_ReadLongFromFile(fp);
if (magic != PyImport_GetMagicNumber()) {
if (!_check_magic(magic, PyImport_GetMagicNumber())) {
if (!PyErr_Occurred())
PyErr_SetString(PyExc_RuntimeError,
"Bad magic number in .pyc file");