Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCampbell Barton <ideasman42@gmail.com>2018-06-17 20:51:05 +0300
committerCampbell Barton <ideasman42@gmail.com>2018-06-17 21:01:05 +0300
commit2ceff8bd63252924a2ae06451af6080876ed0cb3 (patch)
treefbfb8cfd5d3f36c7e33c6c352716b5c788f3326a /source/blender/python/intern/bpy_driver.c
parenta24b4e6090057479796e914bc603119b12f6ca06 (diff)
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.
Diffstat (limited to 'source/blender/python/intern/bpy_driver.c')
-rw-r--r--source/blender/python/intern/bpy_driver.c212
1 files changed, 212 insertions, 0 deletions
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 <opcode.h>
+#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 */