Python: add opcodes for safe py-drivers

The following opcodes have been added, see [0] for details:

- LIST_TO_TUPLE: convert a list to a tuple,
  use for constructing lists/tuples in some cases.

- LIST_EXTEND: use for constructing lists with unpacking.

- SET_UPDATE: use for constructing sets with unpacking.

- CONTAINS_OP: check if `a in b` generally useful.

When writing tests these op-codes where needed for basic operations
and can be safely supported.

Add note why dictionary manipulation op-codes have been left out.

Also restrict namsepace access to anything with an underscore prefix
since these may be undocumented.

[0]: https://docs.python.org/3.10/library/dis.html
This commit is contained in:
Campbell Barton 2022-07-12 16:05:13 +10:00
parent 2a1d12d7a0
commit 00c7e760b3
Notes: blender-bot 2023-02-14 06:00:49 +01:00
Referenced by issue #98661, 3.2: Potential candidates for corrective releases
2 changed files with 83 additions and 25 deletions

View File

@ -318,6 +318,7 @@ static const bool secure_opcodes[255] = {
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),
@ -332,6 +333,7 @@ static const bool secure_opcodes[255] = {
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),
@ -342,6 +344,14 @@ static const bool secure_opcodes[255] = {
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),
@ -395,6 +405,7 @@ static const bool 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),
@ -410,12 +421,21 @@ static const bool secure_opcodes[255] = {
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. */
@ -429,9 +449,10 @@ static const bool secure_opcodes[255] = {
# undef OK_OP
static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code,
PyObject *dict_arr[],
const char *error_prefix)
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;
@ -439,21 +460,23 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code,
{
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,
"\t%s: restricted access disallows name '%s', "
"enable auto-execution to support\n",
error_prefix,
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;
}
}
@ -485,11 +508,13 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code,
for (Py_ssize_t i = 0; i < code_len; i++) {
const int opcode = _Py_OPCODE(codestr[i]);
if (secure_opcodes[opcode] == false) {
fprintf(stderr,
"\t%s: restricted access disallows opcode '%d', "
"enable auto-execution to support\n",
error_prefix,
opcode);
if (verbose) {
fprintf(stderr,
"\t%s: restricted access disallows opcode '%d', "
"enable auto-execution to support\n",
error_prefix,
opcode);
}
ok = false;
break;
}
@ -506,6 +531,26 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code,
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,
@ -697,14 +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,
},
__func__)) {
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);

View File

@ -10,6 +10,8 @@
extern "C" {
#endif
#include <stdbool.h>
/**
* For faster execution we keep a special dictionary for py-drivers, with
* the needed modules and aliases.
@ -21,6 +23,14 @@ int bpy_pydriver_create_dict(void);
*/
extern PyObject *bpy_pydriver_Dict;
extern bool BPY_driver_secure_bytecode_test_ex(PyObject *expr_code,
PyObject *namespace_array[],
const bool verbose,
const char *error_prefix);
extern bool BPY_driver_secure_bytecode_test(PyObject *expr_code,
PyObject *namespace,
const bool verbose);
#ifdef __cplusplus
}
#endif