Skip to content

Commit 23c186f

Browse files
bcallerBen Caller
authored and
Ben Caller
committed
Simplify trigger file for sink argument propagation
This changes the schema of the trigger file. Previously there were too many options and it was confusing. My fault, sorry. This meant that `db.execute(query, **TAINT)` was marked as a vulnerability whereas `db.execute(text=query, **TAINT)` wasn't. Neither are vulnerabilities, so this gave a FALSE POSITIVE. Now we have `arg_dict` which is a dictionary of keyword to argument position. E.g. for `def f(a, b, *, c)` we can specify the arg_dict as: ``` {"a": 0, "b": 1, "c": null} ``` if we want them all to propagate or not propagate depending on the `unlisted_args_propagate` value. This way, we can more easily define db.execute as: ``` "execute(": { "unlisted_args_propagate": false, "arg_dict": { "text": 0 } }, ```
1 parent 0932cc9 commit 23c186f

File tree

4 files changed

+33
-36
lines changed

4 files changed

+33
-36
lines changed

pyt/vulnerabilities/trigger_definitions_parser.py

+16-11
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,38 @@
1616
class Sink:
1717
def __init__(
1818
self, trigger, *,
19-
unlisted_args_propagate=True, unlisted_kwargs_propagate=True,
20-
arg_list=None, kwarg_list=None,
21-
sanitisers=None
19+
unlisted_args_propagate=True,
20+
arg_dict=None,
21+
sanitisers=None,
2222
):
2323
self._trigger = trigger
2424
self.sanitisers = sanitisers or []
2525
self.arg_list_propagates = not unlisted_args_propagate
26-
self.kwarg_list_propagates = not unlisted_kwargs_propagate
2726

2827
if trigger[-1] != '(':
29-
if self.arg_list_propagates or self.kwarg_list_propagates or arg_list or kwarg_list:
28+
if self.arg_list_propagates or arg_dict:
3029
raise ValueError("Propagation options specified, but trigger word isn't a function call")
3130

32-
self.arg_list = set(arg_list or ())
33-
self.kwarg_list = set(kwarg_list or ())
31+
arg_dict = {} if arg_dict is None else arg_dict
32+
self.arg_position_to_kwarg = {
33+
position: name for name, position in arg_dict.items() if position is not None
34+
}
35+
self.kwarg_list = set(arg_dict.keys())
3436

3537
def arg_propagates(self, index):
36-
in_list = index in self.arg_list
37-
return self.arg_list_propagates == in_list
38+
kwarg = self.get_kwarg_from_position(index)
39+
return self.kwarg_propagates(kwarg)
3840

3941
def kwarg_propagates(self, keyword):
4042
in_list = keyword in self.kwarg_list
41-
return self.kwarg_list_propagates == in_list
43+
return self.arg_list_propagates == in_list
44+
45+
def get_kwarg_from_position(self, index):
46+
return self.arg_position_to_kwarg.get(index)
4247

4348
@property
4449
def all_arguments_propagate_taint(self):
45-
if self.arg_list or self.kwarg_list:
50+
if self.kwarg_list:
4651
return False
4752
return True
4853

pyt/vulnerabilities/vulnerabilities.py

+9-11
Original file line numberDiff line numberDiff line change
@@ -243,29 +243,27 @@ def get_sink_args(cfg_node):
243243
def get_sink_args_which_propagate(sink, ast_node):
244244
sink_args_with_positions = CallVisitor.get_call_visit_results(sink.trigger.call, ast_node)
245245
sink_args = []
246+
kwargs_present = set()
246247

247248
for i, vars in enumerate(sink_args_with_positions.args):
248-
if sink.trigger.arg_propagates(i):
249+
kwarg = sink.trigger.get_kwarg_from_position(i)
250+
if kwarg:
251+
kwargs_present.add(kwarg)
252+
if sink.trigger.kwarg_propagates(kwarg):
249253
sink_args.extend(vars)
250254

251-
if (
252-
# Either any unspecified arg propagates
253-
not sink.trigger.arg_list_propagates or
254-
# or there are some propagating args which weren't passed positionally
255-
any(1 for position in sink.trigger.arg_list if position >= len(sink_args_with_positions.args))
256-
):
257-
sink_args.extend(sink_args_with_positions.unknown_args)
258-
259255
for keyword, vars in sink_args_with_positions.kwargs.items():
256+
kwargs_present.add(keyword)
260257
if sink.trigger.kwarg_propagates(keyword):
261258
sink_args.extend(vars)
262259

263260
if (
264261
# Either any unspecified kwarg propagates
265-
not sink.trigger.kwarg_list_propagates or
262+
not sink.trigger.arg_list_propagates or
266263
# or there are some propagating kwargs which have not been passed by keyword
267-
sink.trigger.kwarg_list - set(sink_args_with_positions.kwargs.keys())
264+
sink.trigger.kwarg_list - kwargs_present
268265
):
266+
sink_args.extend(sink_args_with_positions.unknown_args)
269267
sink_args.extend(sink_args_with_positions.unknown_kwargs)
270268

271269
return sink_args

pyt/vulnerability_definitions/test_positions.pyt

+7-14
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,15 @@
77
"normal(": {},
88
"execute(": {
99
"unlisted_args_propagate": false,
10-
"arg_list": [
11-
0
12-
],
13-
"unlisted_kwargs_propagate": false,
14-
"kwarg_list": [
15-
"text"
16-
]
10+
"arg_dict": {
11+
"text": 0
12+
}
1713
},
1814
"run(": {
19-
"kwarg_list": [
20-
"non_propagating"
21-
],
22-
"arg_list": [
23-
2,
24-
3
25-
]
15+
"arg_dict": {
16+
"non_propagating": 2,
17+
"something_else": 3
18+
}
2619
}
2720
}
2821
}

tests/vulnerabilities/vulnerabilities_test.py

+1
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ def check(fixture, vulnerable):
577577
'execute(x, name=TAINT)',
578578
'execute(x, *TAINT)',
579579
'execute(text=x, **TAINT)',
580+
'execute(x, **TAINT)',
580581
'dont_run(TAINT)',
581582
)
582583
vuln_fixtures = (

0 commit comments

Comments
 (0)