From 2ceff8bd63252924a2ae06451af6080876ed0cb3 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 17 Jun 2018 19:51:05 +0200 Subject: Python: Allow untrusted py-drivers to run limited expressions Limit to a restricted set of built-ins, as well as the math module. Also restrict of op-codes, disallowing imports and attribute access. This allows most math expressions to run without any performance cost once the initial check is done. See: D1862 for details. --- source/blender/python/intern/bpy_driver.c | 212 ++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) (limited to 'source/blender/python/intern/bpy_driver.c') diff --git a/source/blender/python/intern/bpy_driver.c b/source/blender/python/intern/bpy_driver.c index 215a64a4449..bc81eb8ee24 100644 --- a/source/blender/python/intern/bpy_driver.c +++ b/source/blender/python/intern/bpy_driver.c @@ -52,9 +52,19 @@ extern void BPY_update_rna_module(void); #define USE_RNA_AS_PYOBJECT +#define USE_BYTECODE_WHITELIST + +#ifdef USE_BYTECODE_WHITELIST +# include +#endif + /* for pydrivers (drivers using one-line Python expressions to express relationships between targets) */ PyObject *bpy_pydriver_Dict = NULL; +#ifdef USE_BYTECODE_WHITELIST +static PyObject *bpy_pydriver_Dict__whitelist = NULL; +#endif + /* For faster execution we keep a special dictionary for pydrivers, with * the needed modules and aliases. */ @@ -79,6 +89,9 @@ int bpy_pydriver_create_dict(void) PyDict_Merge(d, PyModule_GetDict(mod), 0); /* 0 - don't overwrite existing values */ Py_DECREF(mod); } +#ifdef USE_BYTECODE_WHITELIST + PyObject *mod_math = mod; +#endif /* add bpy to global namespace */ mod = PyImport_ImportModuleLevel("bpy", NULL, NULL, NULL, 0); @@ -95,6 +108,48 @@ int bpy_pydriver_create_dict(void) Py_DECREF(mod); } +#ifdef USE_BYTECODE_WHITELIST + /* setup the whitelist */ + { + bpy_pydriver_Dict__whitelist = PyDict_New(); + const char *whitelist[] = { + /* builtins (basic) */ + "all", + "any", + "len", + /* builtins (numeric) */ + "max", + "min", + "pow", + "round", + "sum", + /* types */ + "bool", + "float", + "int", + + NULL, + }; + + for (int i = 0; whitelist[i]; i++) { + PyDict_SetItemString(bpy_pydriver_Dict__whitelist, whitelist[i], Py_None); + } + + /* Add all of 'math' functions. */ + if (mod_math != NULL) { + PyObject *mod_math_dict = PyModule_GetDict(mod_math); + PyObject *arg_key, *arg_value; + Py_ssize_t arg_pos = 0; + while (PyDict_Next(mod_math_dict, &arg_pos, &arg_key, &arg_value)) { + const char *arg_str = _PyUnicode_AsString(arg_key); + if (arg_str[0] && arg_str[1] != '_') { + PyDict_SetItem(bpy_pydriver_Dict__whitelist, arg_key, Py_None); + } + } + } + } +#endif /* USE_BYTECODE_WHITELIST */ + return 0; } @@ -163,6 +218,14 @@ void BPY_driver_reset(void) bpy_pydriver_Dict = NULL; } +#ifdef USE_BYTECODE_WHITELIST + if (bpy_pydriver_Dict__whitelist) { + PyDict_Clear(bpy_pydriver_Dict__whitelist); + Py_DECREF(bpy_pydriver_Dict__whitelist); + bpy_pydriver_Dict__whitelist = NULL; + } +#endif + g_pydriver_state_prev.evaltime = FLT_MAX; /* freed when clearing driver dict */ @@ -185,6 +248,130 @@ static void pydriver_error(ChannelDriver *driver) PyErr_Clear(); } +#ifdef USE_BYTECODE_WHITELIST + +#define OK_OP(op) [op] = 1 + +const char secure_opcodes[255] = { + OK_OP(0), + OK_OP(POP_TOP), + OK_OP(ROT_TWO), + OK_OP(ROT_THREE), + OK_OP(DUP_TOP), + OK_OP(DUP_TOP_TWO), + OK_OP(NOP), + OK_OP(UNARY_POSITIVE), + OK_OP(UNARY_NEGATIVE), + OK_OP(UNARY_NOT), + OK_OP(UNARY_INVERT), + OK_OP(BINARY_MATRIX_MULTIPLY), + OK_OP(INPLACE_MATRIX_MULTIPLY), + OK_OP(BINARY_POWER), + OK_OP(BINARY_MULTIPLY), + OK_OP(BINARY_MODULO), + OK_OP(BINARY_ADD), + OK_OP(BINARY_SUBTRACT), + OK_OP(BINARY_SUBSCR), + OK_OP(BINARY_FLOOR_DIVIDE), + OK_OP(BINARY_TRUE_DIVIDE), + OK_OP(INPLACE_FLOOR_DIVIDE), + OK_OP(INPLACE_TRUE_DIVIDE), + OK_OP(INPLACE_ADD), + OK_OP(INPLACE_SUBTRACT), + OK_OP(INPLACE_MULTIPLY), + OK_OP(INPLACE_MODULO), + OK_OP(BINARY_LSHIFT), + OK_OP(BINARY_RSHIFT), + OK_OP(BINARY_AND), + OK_OP(BINARY_XOR), + OK_OP(BINARY_OR), + OK_OP(INPLACE_POWER), + OK_OP(INPLACE_LSHIFT), + OK_OP(INPLACE_RSHIFT), + OK_OP(INPLACE_AND), + OK_OP(INPLACE_XOR), + OK_OP(INPLACE_OR), + OK_OP(RETURN_VALUE), + OK_OP(BUILD_TUPLE), + OK_OP(BUILD_LIST), + OK_OP(BUILD_SET), + OK_OP(BUILD_MAP), + OK_OP(COMPARE_OP), + OK_OP(JUMP_FORWARD), + OK_OP(JUMP_IF_FALSE_OR_POP), + OK_OP(JUMP_IF_TRUE_OR_POP), + OK_OP(JUMP_ABSOLUTE), + OK_OP(POP_JUMP_IF_FALSE), + OK_OP(POP_JUMP_IF_TRUE), + OK_OP(LOAD_GLOBAL), + OK_OP(LOAD_FAST), + OK_OP(STORE_FAST), + OK_OP(DELETE_FAST), + OK_OP(LOAD_DEREF), + OK_OP(STORE_DEREF), + + /* special cases */ + OK_OP(LOAD_CONST), /* ok because constants are accepted */ + OK_OP(LOAD_NAME), /* ok, because PyCodeObject.names is checked */ + OK_OP(CALL_FUNCTION), /* ok, because we check its 'name' before calling */ + OK_OP(CALL_FUNCTION_KW), + OK_OP(CALL_FUNCTION_EX), +}; + +#undef OK_OP + +static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *dict_arr[]) +{ + PyCodeObject *py_code = (PyCodeObject *)expr_code; + + /* Check names. */ + { + for (int i = 0; i < PyTuple_GET_SIZE(py_code->co_names); i++) { + PyObject *name = PyTuple_GET_ITEM(py_code->co_names, i); + + bool contains_name = false; + for (int j = 0; dict_arr[j]; j++) { + if (PyDict_Contains(dict_arr[j], name)) { + contains_name = true; + break; + } + } + + if (contains_name == false) { + fprintf(stderr, "\tBPY_driver_eval() - restructed access disallows name '%s', " + "enable auto-execution to support\n", _PyUnicode_AsString(name)); + return false; + } + } + } + + /* Check opcodes. */ + { + const char *codestr; + Py_ssize_t code_len; + + PyBytes_AsStringAndSize(py_code->co_code, (char **)&codestr, &code_len); + +#define CODESIZE(op) (HAS_ARG(op) ? 3 : 1) + + for (Py_ssize_t i = 0; i < code_len; i += CODESIZE(codestr[i])) { + const int opcode = codestr[i]; + if (secure_opcodes[opcode] == 0) { + fprintf(stderr, "\tBPY_driver_eval() - restructed access disallows opcode '%d', " + "enable auto-execution to support\n", opcode); + return false; + } + } + +#undef CODESIZE + } + + return true; +} + +#endif /* USE_BYTECODE_WHITELIST */ + + /* This evals py driver expressions, 'expr' is a Python expression that * should evaluate to a float number, which is returned. * @@ -217,6 +404,7 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, ChannelDriver *driver, c if (expr[0] == '\0') return 0.0f; +#ifndef USE_BYTECODE_WHITELIST if (!(G.f & G_SCRIPT_AUTOEXEC)) { if (!(G.f & G_SCRIPT_AUTOEXEC_FAIL_QUIET)) { G.f |= G_SCRIPT_AUTOEXEC_FAIL; @@ -226,6 +414,9 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, ChannelDriver *driver, c } return 0.0f; } +#else + bool is_recompile = false; +#endif use_gil = true; /* !PyC_IsInterpreterActive(); */ @@ -269,6 +460,9 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, ChannelDriver *driver, c driver->flag &= ~DRIVER_FLAG_RECOMPILE; driver->flag |= DRIVER_FLAG_RENAMEVAR; /* maybe this can be removed but for now best keep until were sure */ +#ifdef USE_BYTECODE_WHITELIST + is_recompile = true; +#endif } else { expr_code = PyTuple_GET_ITEM(((PyObject *)driver->expr_comp), 0); @@ -350,6 +544,24 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, ChannelDriver *driver, c } } +#ifdef USE_BYTECODE_WHITELIST + if (is_recompile) { + if (!(G.f & G_SCRIPT_AUTOEXEC)) { + if (!bpy_driver_secure_bytecode_validate( + expr_code, (PyObject *[]){ + bpy_pydriver_Dict, + bpy_pydriver_Dict__whitelist, + driver_vars, + NULL,} + )) + { + Py_DECREF(expr_code); + expr_code = NULL; + PyTuple_SET_ITEM(((PyObject *)driver->expr_comp), 0, NULL); + } + } + } +#endif /* USE_BYTECODE_WHITELIST */ #if 0 /* slow, with this can avoid all Py_CompileString above. */ /* execute expression to get a value */ -- cgit v1.2.3