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:
Diffstat (limited to 'source/blender/python/intern/bpy_driver.c')
-rw-r--r--source/blender/python/intern/bpy_driver.c254
1 files changed, 202 insertions, 52 deletions
diff --git a/source/blender/python/intern/bpy_driver.c b/source/blender/python/intern/bpy_driver.c
index a9cc0019783..04aa203d198 100644
--- a/source/blender/python/intern/bpy_driver.c
+++ b/source/blender/python/intern/bpy_driver.c
@@ -19,6 +19,7 @@
#include "BKE_animsys.h"
#include "BKE_fcurve_driver.h"
#include "BKE_global.h"
+#include "BKE_idtype.h"
#include "RNA_access.h"
#include "RNA_prototypes.h"
@@ -233,15 +234,8 @@ static void bpy_pydriver_namespace_update_depsgraph(struct Depsgraph *depsgraph)
}
}
-void BPY_driver_reset(void)
+void BPY_driver_exit(void)
{
- PyGILState_STATE gilstate;
- const bool use_gil = true; /* !PyC_IsInterpreterActive(); */
-
- if (use_gil) {
- gilstate = PyGILState_Ensure();
- }
-
if (bpy_pydriver_Dict) { /* Free the global dict used by python-drivers. */
PyDict_Clear(bpy_pydriver_Dict);
Py_DECREF(bpy_pydriver_Dict);
@@ -261,19 +255,46 @@ void BPY_driver_reset(void)
/* Freed when clearing driver dictionary. */
g_pydriver_state_prev.self = NULL;
g_pydriver_state_prev.depsgraph = NULL;
+}
+
+void BPY_driver_reset(void)
+{
+ PyGILState_STATE gilstate;
+ const bool use_gil = true; /* !PyC_IsInterpreterActive(); */
+
+ if (use_gil) {
+ gilstate = PyGILState_Ensure();
+ }
+
+ /* Currently exit/reset are practically the same besides the GIL check. */
+ BPY_driver_exit();
if (use_gil) {
PyGILState_Release(gilstate);
}
}
-/** Error return function for #BPY_eval_pydriver. */
-static void pydriver_error(ChannelDriver *driver)
+/**
+ * Error return function for #BPY_driver_exec.
+ *
+ * \param anim_rna: Used to show the target when printing the error to give additional context.
+ */
+static void pydriver_error(ChannelDriver *driver, const struct PathResolvedRNA *anim_rna)
{
driver->flag |= DRIVER_FLAG_INVALID; /* Python expression failed. */
+
+ const char *null_str = "<null>";
+ const ID *id = anim_rna->ptr.owner_id;
fprintf(stderr,
- "\nError in Driver: The following Python expression failed:\n\t'%s'\n\n",
- driver->expression);
+ "\n"
+ "Error in PyDriver: expression failed: %s\n"
+ "For target: (type=%s, name=\"%s\", property=%s, property_index=%d)\n"
+ "\n",
+ driver->expression,
+ id ? BKE_idtype_idcode_to_name(GS(id->name)) : null_str,
+ id ? id->name + 2 : null_str,
+ anim_rna->prop ? RNA_property_identifier(anim_rna->prop) : null_str,
+ anim_rna->prop_index);
// BPy_errors_to_report(NULL); /* TODO: reports. */
PyErr_Print();
@@ -282,14 +303,75 @@ static void pydriver_error(ChannelDriver *driver)
#ifdef USE_BYTECODE_WHITELIST
-# define OK_OP(op) [op] = 1
+# define OK_OP(op) [op] = true
+
+static const bool secure_opcodes[255] = {
+# if PY_VERSION_HEX >= 0x030b0000 /* Python 3.11 & newer. */
+
+ OK_OP(CACHE),
+ OK_OP(POP_TOP),
+ OK_OP(PUSH_NULL),
+ OK_OP(NOP),
+ OK_OP(UNARY_POSITIVE),
+ OK_OP(UNARY_NEGATIVE),
+ OK_OP(UNARY_NOT),
+ OK_OP(UNARY_INVERT),
+ OK_OP(BINARY_SUBSCR),
+ OK_OP(GET_LEN),
+ OK_OP(LIST_TO_TUPLE),
+ OK_OP(RETURN_VALUE),
+ OK_OP(SWAP),
+ 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(POP_JUMP_FORWARD_IF_FALSE),
+ OK_OP(POP_JUMP_FORWARD_IF_TRUE),
+ OK_OP(LOAD_GLOBAL),
+ OK_OP(IS_OP),
+ OK_OP(CONTAINS_OP),
+ OK_OP(BINARY_OP),
+ OK_OP(LOAD_FAST),
+ OK_OP(STORE_FAST),
+ OK_OP(DELETE_FAST),
+ OK_OP(POP_JUMP_FORWARD_IF_NOT_NONE),
+ OK_OP(POP_JUMP_FORWARD_IF_NONE),
+ OK_OP(BUILD_SLICE),
+ OK_OP(LOAD_DEREF),
+ OK_OP(STORE_DEREF),
+ OK_OP(RESUME),
+ OK_OP(LIST_EXTEND),
+ OK_OP(SET_UPDATE),
+/* NOTE(@campbellbarton): Don't enable dict manipulation, unless we can prove there is not way it
+ * can be used to manipulate the name-space (potentially allowing malicious code). */
+# if 0
+ OK_OP(DICT_MERGE),
+ OK_OP(DICT_UPDATE),
+# endif
+ OK_OP(POP_JUMP_BACKWARD_IF_NOT_NONE),
+ OK_OP(POP_JUMP_BACKWARD_IF_NONE),
+ OK_OP(POP_JUMP_BACKWARD_IF_FALSE),
+ OK_OP(POP_JUMP_BACKWARD_IF_TRUE),
+
+ /* Special cases. */
+ OK_OP(LOAD_CONST), /* Ok because constants are accepted. */
+ OK_OP(LOAD_NAME), /* Ok, because `PyCodeObject.names` is checked. */
+ OK_OP(CALL), /* Ok, because we check its "name" before calling. */
+ OK_OP(KW_NAMES), /* Ok, because it's used for calling functions with keyword arguments. */
+ OK_OP(PRECALL), /* Ok, because it's used for calling. */
+
+# else /* Python 3.10 and older. */
-static const char secure_opcodes[255] = {
OK_OP(POP_TOP),
OK_OP(ROT_TWO),
OK_OP(ROT_THREE),
OK_OP(DUP_TOP),
OK_OP(DUP_TOP_TWO),
+ OK_OP(ROT_FOUR),
OK_OP(NOP),
OK_OP(UNARY_POSITIVE),
OK_OP(UNARY_NEGATIVE),
@@ -307,6 +389,7 @@ static const char secure_opcodes[255] = {
OK_OP(BINARY_TRUE_DIVIDE),
OK_OP(INPLACE_FLOOR_DIVIDE),
OK_OP(INPLACE_TRUE_DIVIDE),
+ OK_OP(GET_LEN),
OK_OP(INPLACE_ADD),
OK_OP(INPLACE_SUBTRACT),
OK_OP(INPLACE_MULTIPLY),
@@ -322,7 +405,9 @@ static const char secure_opcodes[255] = {
OK_OP(INPLACE_AND),
OK_OP(INPLACE_XOR),
OK_OP(INPLACE_OR),
+ OK_OP(LIST_TO_TUPLE),
OK_OP(RETURN_VALUE),
+ OK_OP(ROT_N),
OK_OP(BUILD_TUPLE),
OK_OP(BUILD_LIST),
OK_OP(BUILD_SET),
@@ -335,11 +420,22 @@ static const char secure_opcodes[255] = {
OK_OP(POP_JUMP_IF_FALSE),
OK_OP(POP_JUMP_IF_TRUE),
OK_OP(LOAD_GLOBAL),
+ OK_OP(IS_OP),
+ OK_OP(CONTAINS_OP),
OK_OP(LOAD_FAST),
OK_OP(STORE_FAST),
OK_OP(DELETE_FAST),
+ OK_OP(BUILD_SLICE),
OK_OP(LOAD_DEREF),
OK_OP(STORE_DEREF),
+ OK_OP(LIST_EXTEND),
+ OK_OP(SET_UPDATE),
+/* NOTE(@campbellbarton): Don't enable dict manipulation, unless we can prove there is not way it
+ * can be used to manipulate the name-space (potentially allowing malicious code). */
+# if 0
+ OK_OP(DICT_MERGE),
+ OK_OP(DICT_UPDATE),
+# endif
/* Special cases. */
OK_OP(LOAD_CONST), /* Ok because constants are accepted. */
@@ -347,11 +443,16 @@ static const char secure_opcodes[255] = {
OK_OP(CALL_FUNCTION), /* Ok, because we check its "name" before calling. */
OK_OP(CALL_FUNCTION_KW),
OK_OP(CALL_FUNCTION_EX),
+
+# endif /* Python 3.10 and older. */
};
# undef OK_OP
-static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *dict_arr[])
+bool BPY_driver_secure_bytecode_test_ex(PyObject *expr_code,
+ PyObject *namespace_array[],
+ const bool verbose,
+ const char *error_prefix)
{
PyCodeObject *py_code = (PyCodeObject *)expr_code;
@@ -359,20 +460,23 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *d
{
for (int i = 0; i < PyTuple_GET_SIZE(py_code->co_names); i++) {
PyObject *name = PyTuple_GET_ITEM(py_code->co_names, i);
-
+ const char *name_str = PyUnicode_AsUTF8(name);
bool contains_name = false;
- for (int j = 0; dict_arr[j]; j++) {
- if (PyDict_Contains(dict_arr[j], name)) {
+ for (int j = 0; namespace_array[j]; j++) {
+ if (PyDict_Contains(namespace_array[j], name)) {
contains_name = true;
break;
}
}
- if (contains_name == false) {
- fprintf(stderr,
- "\tBPY_driver_eval() - restricted access disallows name '%s', "
- "enable auto-execution to support\n",
- PyUnicode_AsUTF8(name));
+ if ((contains_name == false) || (name_str[0] == '_')) {
+ if (verbose) {
+ fprintf(stderr,
+ "\t%s: restricted access disallows name '%s', "
+ "enable auto-execution to support\n",
+ error_prefix,
+ name_str);
+ }
return false;
}
}
@@ -383,26 +487,70 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *d
const _Py_CODEUNIT *codestr;
Py_ssize_t code_len;
- PyBytes_AsStringAndSize(py_code->co_code, (char **)&codestr, &code_len);
+ PyObject *co_code;
+
+# if PY_VERSION_HEX >= 0x030b0000 /* Python 3.11 & newer. */
+ co_code = PyCode_GetCode(py_code);
+ if (UNLIKELY(!co_code)) {
+ PyErr_Print();
+ PyErr_Clear();
+ return false;
+ }
+# else
+ co_code = py_code->co_code;
+# endif
+
+ PyBytes_AsStringAndSize(co_code, (char **)&codestr, &code_len);
code_len /= sizeof(*codestr);
+ bool ok = true;
+ /* Loop over op-code's, the op-code arguments are ignored. */
for (Py_ssize_t i = 0; i < code_len; i++) {
const int opcode = _Py_OPCODE(codestr[i]);
- if (secure_opcodes[opcode] == 0) {
- fprintf(stderr,
- "\tBPY_driver_eval() - restricted access disallows opcode '%d', "
- "enable auto-execution to support\n",
- opcode);
- return false;
+ if (secure_opcodes[opcode] == false) {
+ if (verbose) {
+ fprintf(stderr,
+ "\t%s: restricted access disallows opcode '%d', "
+ "enable auto-execution to support\n",
+ error_prefix,
+ opcode);
+ }
+ ok = false;
+ break;
}
}
-# undef CODESIZE
+# if PY_VERSION_HEX >= 0x030b0000 /* Python 3.11 & newer. */
+ Py_DECREF(co_code);
+# endif
+ if (!ok) {
+ return false;
+ }
}
return true;
}
+bool BPY_driver_secure_bytecode_test(PyObject *expr_code, PyObject *namespace, const bool verbose)
+{
+
+ if (!bpy_pydriver_Dict) {
+ if (bpy_pydriver_create_dict() != 0) {
+ fprintf(stderr, "%s: couldn't create Python dictionary\n", __func__);
+ return false;
+ }
+ }
+ return BPY_driver_secure_bytecode_test_ex(expr_code,
+ (PyObject *[]){
+ bpy_pydriver_Dict,
+ bpy_pydriver_Dict__whitelist,
+ namespace,
+ NULL,
+ },
+ verbose,
+ __func__);
+}
+
#endif /* USE_BYTECODE_WHITELIST */
float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
ChannelDriver *driver,
@@ -435,7 +583,7 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
DriverVar *dvar;
double result = 0.0; /* Default return. */
const char *expr;
- short targets_ok = 1;
+ bool targets_ok = true;
int i;
/* Get the python expression to be evaluated. */
@@ -470,7 +618,7 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
/* Initialize global dictionary for Python driver evaluation settings. */
if (!bpy_pydriver_Dict) {
if (bpy_pydriver_create_dict() != 0) {
- fprintf(stderr, "PyDriver error: couldn't create Python dictionary\n");
+ fprintf(stderr, "%s: couldn't create Python dictionary\n", __func__);
if (use_gil) {
PyGILState_Release(gilstate);
}
@@ -579,12 +727,11 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
/* This target failed - bad name. */
if (targets_ok) {
/* First one, print some extra info for easier identification. */
- fprintf(stderr, "\nBPY_driver_eval() - Error while evaluating PyDriver:\n");
- targets_ok = 0;
+ fprintf(stderr, "\n%s: Error while evaluating PyDriver:\n", __func__);
+ targets_ok = false;
}
- fprintf(
- stderr, "\tBPY_driver_eval() - couldn't add variable '%s' to namespace\n", dvar->name);
+ fprintf(stderr, "\t%s: couldn't add variable '%s' to namespace\n", __func__, dvar->name);
// BPy_errors_to_report(NULL); /* TODO: reports. */
PyErr_Print();
PyErr_Clear();
@@ -595,13 +742,17 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
#ifdef USE_BYTECODE_WHITELIST
if (is_recompile && expr_code) {
if (!(G.f & G_FLAG_SCRIPT_AUTOEXEC)) {
- if (!bpy_driver_secure_bytecode_validate(expr_code,
- (PyObject *[]){
- bpy_pydriver_Dict,
- bpy_pydriver_Dict__whitelist,
- driver_vars,
- NULL,
- })) {
+ if (!BPY_driver_secure_bytecode_test_ex(
+ expr_code,
+ (PyObject *[]){
+ bpy_pydriver_Dict,
+ bpy_pydriver_Dict__whitelist,
+ driver_vars,
+ NULL,
+ },
+ /* Always be verbose since this can give hints to why evaluation fails. */
+ true,
+ __func__)) {
if (!(G.f & G_FLAG_SCRIPT_AUTOEXEC_FAIL_QUIET)) {
G.f |= G_FLAG_SCRIPT_AUTOEXEC_FAIL;
BLI_snprintf(G.autoexec_fail, sizeof(G.autoexec_fail), "Driver '%s'", expr);
@@ -630,11 +781,11 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
/* Process the result. */
if (retval == NULL) {
- pydriver_error(driver);
+ pydriver_error(driver, anim_rna);
}
else {
- if ((result = PyFloat_AsDouble(retval)) == -1.0 && PyErr_Occurred()) {
- pydriver_error(driver);
+ if (UNLIKELY((result = PyFloat_AsDouble(retval)) == -1.0 && PyErr_Occurred())) {
+ pydriver_error(driver, anim_rna);
result = 0.0;
}
else {
@@ -648,11 +799,10 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
PyGILState_Release(gilstate);
}
- if (isfinite(result)) {
- return (float)result;
+ if (UNLIKELY(!isfinite(result))) {
+ fprintf(stderr, "\t%s: driver '%s' evaluates to '%f'\n", __func__, driver->expression, result);
+ return 0.0f;
}
- fprintf(
- stderr, "\tBPY_driver_eval() - driver '%s' evaluates to '%f'\n", driver->expression, result);
- return 0.0f;
+ return (float)result;
}