0

Is it possible to assign a value to an "eval expression" without manipulating the evaluation string? Example: The user writes the expression

"globalPythonArray[10]"

which would evaluate to the current value of item 10 of globalPythonArray. But the goal is, to set the value of item 10 to a new value instead of getting the old value. A dirty workaround would be, to define a temporary variable "newValue" and extend the evaluation string to

"globalPythonArray[10] = newValue"

and compile and evaluate that modified string. Are there some low level Python C API functions that I can use such that I don't have to manipulate the evaluation string?

Joe M.
  • 311
  • 3
  • 5

2 Answers2

1

I'd say probably not, since accessing and storing subscriptions are different opcodes:

>>> dis.dis(compile('globalPythonArray[10]', 'a', 'exec'))
  1           0 LOAD_NAME                0 (globalPythonArray)
              2 LOAD_CONST               0 (10)
              4 BINARY_SUBSCR
              6 POP_TOP
              8 LOAD_CONST               1 (None)
             10 RETURN_VALUE

>>> dis.dis(compile('globalPythonArray[10] = myValue', 'a', 'exec'))
  1           0 LOAD_NAME                0 (myValue)
              2 LOAD_NAME                1 (globalPythonArray)
              4 LOAD_CONST               0 (10)
              6 STORE_SUBSCR
              8 LOAD_CONST               1 (None)
             10 RETURN_VALUE

Also, insert the usual warning about user input and eval() here:

globalPythonArray[__import__('os').system('rm -rf /')]
AKX
  • 152,115
  • 15
  • 115
  • 172
  • Thanks! I expected that the compiled code looks like this. However, when I compare the AST of the two expressions (e.g. here https://vpyast.appspot.com/), they don't look too different, and it could be possible to manipulate the AST before compiling it to byte code. Regarding security: The "users" (from my perspective) are actually developers who embed Python into a domain specific programming language, where they already have the power to execute "rm -rf /" directly. – Joe M. Sep 01 '18 at 09:08
0

It's possible to "assign" a value to an eval expression by manipulating its abstract syntax tree (AST). It's not necessary to modify the evaluation string directly and if the type of the new value is not too complicated (e.g. numeric or string), you can hard code it into the AST:

  • Compile eval expression to an AST.
  • Replace Load context of expression at root node by Store.
  • Create a new AST with an Assign statement at the root node.
  • Set target to the expression node of the modified eval AST.
  • Set value to the value.
  • Compile the new AST to byte code and execute it.

Example:

import ast
import numpy as np


def eval_assign_num(expression, value, global_dict, local_dict):
    expr_ast = ast.parse(expression, 'eval', 'eval')
    expr_node = expr_ast.body
    expr_node.ctx = ast.Store()

    assign_ast = ast.Module(body=[
        ast.Assign(
            targets=[expr_node],
            value=ast.Num(n=value)
        )
    ])
    ast.fix_missing_locations(assign_ast)

    c = compile(assign_ast, 'assign', 'exec')
    exec(c, global_dict, local_dict)


class TestClass:
    arr = np.array([1, 2])
    x = 6


testClass = TestClass()
arr = np.array([1, 2])

eval_assign_num('arr[0]', 10, globals(), locals())
eval_assign_num('testClass.arr[1]', 20, globals(), locals())
eval_assign_num('testClass.x', 30, globals(), locals())
eval_assign_num('newVarName', 40, globals(), locals())

print('arr', arr)
print('testClass.arr', testClass.arr)
print('testClass.x', testClass.x)
print('newVarName', newVarName)

Output:

arr [10  2]
testClass.arr [ 1 20]
testClass.x 30
newVarName 40
Joe M.
  • 311
  • 3
  • 5