Skip to content

Commit 806bcf2

Browse files
committed
added validation of expressions to prevent malicious code execution via eval()
At the point when eval is called, all symbols should have already been resolved to their values. That means we only need to allow for numeric characters along with arithmetic and bitwise operators, round brackets and whitespace. The character 'x' and the characters 'abcdef' are also accepted to allow for hex numbers such as 0x123abc. They are only allowed however in sequences starting with 0x. If any other character is encountered the expression is deemed invalid. This change also removes the try/except around the eval. As all symbols have been resolved, the only thing that can be left is a valid expression. If it does not evaluate, an exception *should* be raised rather than being silently ignored (and hoping for an exception down the line).
1 parent 66eeb61 commit 806bcf2

File tree

4 files changed

+94
-8
lines changed

4 files changed

+94
-8
lines changed

esp32_ulp/opcodes.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from uctypes import struct, addressof, LITTLE_ENDIAN, UINT32, BFUINT32, BF_POS, BF_LEN
77

88
from .soc import *
9-
from .util import split_tokens
9+
from .util import split_tokens, validate_expression
1010

1111
# XXX dirty hack: use a global for the symbol table
1212
symbols = None
@@ -277,11 +277,9 @@ def eval_arg(arg):
277277
else:
278278
parts.append(token)
279279
parts = "".join(parts)
280-
try:
281-
parts = str(eval(parts))
282-
except:
283-
pass
284-
return parts
280+
if not validate_expression(parts):
281+
raise ValueError('Unsupported expression: %s' % parts)
282+
return eval(parts)
285283

286284

287285
def arg_qualify(arg):

esp32_ulp/util.py

+25
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,28 @@ def file_exists(filename):
5252
except:
5353
pass
5454
return False
55+
56+
57+
def validate_expression(param):
58+
for token in split_tokens(param):
59+
state = 0
60+
for c in token:
61+
if c not in ' \t+-*/%()<>&|~x0123456789abcdef':
62+
return False
63+
64+
# the following allows hex digits a-f after 0x but not otherwise
65+
if state == 0:
66+
if c in 'abcdef':
67+
return False
68+
if c == '0':
69+
state = 1
70+
continue
71+
72+
if state == 1:
73+
state = 2 if c == 'x' else 0
74+
continue
75+
76+
if state == 2:
77+
if c not in '0123456789abcdef':
78+
state = 0
79+
return True

tests/opcodes.py

+38-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from uctypes import UINT32, BFUINT32, BF_POS, BF_LEN
22
from esp32_ulp.opcodes import make_ins, make_ins_struct_def
3-
from esp32_ulp.opcodes import get_reg, get_imm, get_cond, arg_qualify, ARG, REG, IMM, SYM, COND
3+
from esp32_ulp.opcodes import get_reg, get_imm, get_cond, arg_qualify, eval_arg, ARG, REG, IMM, SYM, COND
44
from esp32_ulp.assemble import SymbolTable, ABS, REL, TEXT
55
import esp32_ulp.opcodes as opcodes
66

@@ -72,9 +72,46 @@ def test_get_cond():
7272
assert get_cond('Eq') == 'eq'
7373

7474

75+
def test_eval_arg():
76+
opcodes.symbols = SymbolTable({}, {}, {})
77+
opcodes.symbols.set_sym('const', ABS, None, 42) # constant
78+
opcodes.symbols.set_sym('raise', ABS, None, 99) # constant using a python keyword as name (is allowed)
79+
80+
assert eval_arg('1+1') == 2
81+
assert eval_arg('1+const') == 43
82+
assert eval_arg('raise*2/3') == 66
83+
assert eval_arg('raise-const') == 57
84+
assert eval_arg('(raise-const)*2') == 114
85+
assert eval_arg('const % 5') == 2
86+
assert eval_arg('const + 0x19af') == 0x19af + 42
87+
assert eval_arg('const & ~2') == 40
88+
assert eval_arg('const << 3') == 336
89+
assert eval_arg('const >> 1') == 21
90+
assert eval_arg('(const|4)&0xf') == 0xe
91+
92+
assert_raises(ValueError, eval_arg, 'evil()')
93+
assert_raises(ValueError, eval_arg, 'def cafe()')
94+
assert_raises(ValueError, eval_arg, '1 ^ 2')
95+
assert_raises(ValueError, eval_arg, '!100')
96+
97+
# clean up
98+
opcodes.symbols = None
99+
100+
101+
def assert_raises(exception, func, *args):
102+
try:
103+
func(*args)
104+
except exception:
105+
raised = True
106+
else:
107+
raised = False
108+
assert raised
109+
110+
75111
test_make_ins_struct_def()
76112
test_make_ins()
77113
test_arg_qualify()
78114
test_get_reg()
79115
test_get_imm()
80116
test_get_cond()
117+
test_eval_arg()

tests/util.py

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import os
2-
from esp32_ulp.util import split_tokens, file_exists
2+
from esp32_ulp.util import split_tokens, file_exists, validate_expression
33

44
tests = []
55

@@ -31,6 +31,32 @@ def test_split_tokens():
3131
assert split_tokens("#test") == ['#', 'test']
3232

3333

34+
@test
35+
def test_validate_expression():
36+
assert validate_expression('') is True
37+
assert validate_expression('1') is True
38+
assert validate_expression('1+1') is True
39+
assert validate_expression('(1+1)') is True
40+
assert validate_expression('(1+1)*2') is True
41+
assert validate_expression('(1 + 1)') is True
42+
assert validate_expression('10 % 2') is True
43+
assert validate_expression('0x100 << 2') is True
44+
assert validate_expression('0x100 & ~2') is True
45+
assert validate_expression('0xabcdef') is True
46+
assert validate_expression('0x123def') is True
47+
assert validate_expression('2*3+4/5&6|7') is True
48+
assert validate_expression('(((((1+1) * 2') is True # valid characters, even if expression is not valid
49+
50+
assert validate_expression(':') is False
51+
assert validate_expression('_') is False
52+
assert validate_expression('=') is False
53+
assert validate_expression('.') is False
54+
assert validate_expression('!') is False
55+
assert validate_expression('123 ^ 4') is False # operator not supported for now
56+
assert validate_expression('evil()') is False
57+
assert validate_expression('def cafe()') is False # valid hex digits, but potentially dangerous code
58+
59+
3460
@test
3561
def test_file_exists():
3662
testfile = '.testfile'

0 commit comments

Comments
 (0)