Skip to content

Commit 4badc2c

Browse files
github-actions[bot]juanjuxgnufede
authored
fix: avoid changing the AST assign node since it's not needed for propagation [backport 2.2] (#8109)
Backport 4e3cac1 from #8096 to 2.2. ## Description We were doing several operations on the AST ASSIGN node that were not needed for propagation like splitting the multiple assigments and introducing temporary vars, which cause problems with introspection tools. This removes all these operations and only keep the ignore tag to left-side operands when they're a tuple or subscript. ## Checklist - [X] Change(s) are motivated and described in the PR description. - [X] Testing strategy is described if automated tests are not included in the PR. - [X] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [X] Change is maintainable (easy to change, telemetry, documentation). - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed. If no release note is required, add label `changelog/no-changelog`. - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Title is accurate. - [x] No unnecessary changes are introduced. - [x] Description motivates each change. - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [x] Testing strategy adequately addresses listed risk(s). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] Release note makes sense to a user of the library. - [x] Reviewer has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment. - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) - [x] If this PR touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. - [x] This PR doesn't touch any of that. Co-authored-by: Juanjo Alvarez Martinez <[email protected]> Co-authored-by: Federico Mon <[email protected]>
1 parent 14b588a commit 4badc2c

File tree

4 files changed

+71
-36
lines changed

4 files changed

+71
-36
lines changed

ddtrace/appsec/_iast/_ast/visitor.py

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -607,27 +607,9 @@ def visit_AugAssign(self, augassign_node): # type: (ast.AugAssign) -> Any
607607

608608
def visit_Assign(self, assign_node): # type: (ast.Assign) -> Any
609609
"""
610-
Decompose multiple assignment into single ones and
611-
check if any item in the targets list is if type Subscript and if
612-
that's the case further decompose it to use a temp variable to
613-
avoid assigning to a function call.
610+
Add the ignore marks for left-side subscripts or list/tuples to avoid problems
611+
later with the visit_Subscript node.
614612
"""
615-
# a = b = c
616-
# __dd_tmp = c
617-
# a = __dd_tmp
618-
619-
ret_nodes = []
620-
621-
if len(assign_node.targets) > 1:
622-
# Multiple assignments, assign the value to a temporal variable
623-
tmp_var_left = self._name_node(assign_node, "__dd_tmp", ctx=ast.Store())
624-
assign_value = self._name_node(assign_node, "__dd_tmp", ctx=ast.Load())
625-
assign_to_tmp = self._assign_node(from_node=assign_node, targets=[tmp_var_left], value=assign_node.value)
626-
ret_nodes.append(assign_to_tmp)
627-
self.ast_modified = True
628-
else:
629-
assign_value = assign_node.value # type: ignore
630-
631613
for target in assign_node.targets:
632614
if isinstance(target, ast.Subscript):
633615
# We can't assign to a function call, which is anyway going to rewrite
@@ -640,22 +622,8 @@ def visit_Assign(self, assign_node): # type: (ast.Assign) -> Any
640622
element.avoid_convert = True # type: ignore[attr-defined]
641623

642624
# Create a normal assignment. This way we decompose multiple assignments
643-
# like (a = b = c) into a = b and a = c so the transformation above
644-
# is possible.
645-
# Decompose it into a normal, not multiple, assignment
646-
new_assign_value = copy.copy(assign_value)
647-
648-
new_target = copy.copy(target)
649-
650-
single_assign = self._assign_node(assign_node, [new_target], new_assign_value)
651-
652-
self.generic_visit(single_assign)
653-
ret_nodes.append(single_assign)
654-
655-
if len(ret_nodes) == 1:
656-
return ret_nodes[0]
657-
658-
return ret_nodes
625+
self.generic_visit(assign_node)
626+
return assign_node
659627

660628
def visit_Delete(self, assign_node): # type: (ast.Delete) -> Any
661629
# del replaced_index(foo, bar) would fail so avoid converting the right hand side
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fixes:
2+
- |
3+
IAST: Don't split AST Assign nodes since it's not needed for propagation to work.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import pytest
2+
3+
4+
OriginType = pytest.importorskip("ddtrace.appsec._iast._taint_tracking.OriginType")
5+
get_tainted_ranges = pytest.importorskip("ddtrace.appsec._iast._taint_tracking.get_tainted_ranges")
6+
taint_pyobject = pytest.importorskip("ddtrace.appsec._iast._taint_tracking.taint_pyobject")
7+
_iast_patched_module = pytest.importorskip("tests.appsec.iast.aspects.conftest._iast_patched_module")
8+
9+
mod = _iast_patched_module("tests.appsec.iast.fixtures.aspects.str_methods")
10+
11+
12+
def test_string_assigment():
13+
string_input = taint_pyobject(
14+
pyobject="foo",
15+
source_name="test_add_aspect_tainting_left_hand",
16+
source_value="foo",
17+
source_origin=OriginType.PARAMETER,
18+
)
19+
assert get_tainted_ranges(string_input)
20+
21+
res = mod.do_string_assignment(string_input)
22+
assert len(get_tainted_ranges(res)) == 1
23+
24+
25+
def test_multiple_string_assigment():
26+
string_input = taint_pyobject(
27+
pyobject="foo",
28+
source_name="test_add_aspect_tainting_left_hand",
29+
source_value="foo",
30+
source_origin=OriginType.PARAMETER,
31+
)
32+
assert get_tainted_ranges(string_input)
33+
34+
results = mod.do_multiple_string_assigment(string_input)
35+
for res in results:
36+
assert len(get_tainted_ranges(res)) == 1
37+
38+
39+
def test_multiple_tuple_string_assigment():
40+
string_input = taint_pyobject(
41+
pyobject="foo",
42+
source_name="test_add_aspect_tainting_left_hand",
43+
source_value="foo",
44+
source_origin=OriginType.PARAMETER,
45+
)
46+
assert get_tainted_ranges(string_input)
47+
48+
results = mod.do_tuple_string_assignment(string_input)
49+
assert len(get_tainted_ranges(results[-1])) == 1

tests/appsec/iast/fixtures/aspects/str_methods.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,21 @@ def do_operator_add_params(a, b):
5454
return a + b
5555

5656

57+
def do_string_assignment(a):
58+
b = a
59+
return b
60+
61+
62+
def do_multiple_string_assigment(a):
63+
b = c = d = a
64+
return b, c, d
65+
66+
67+
def do_tuple_string_assignment(a):
68+
(b, c, d) = z = a
69+
return b, c, d, z
70+
71+
5772
def uppercase_decorator(function): # type: (Callable) -> Callable
5873
def wrapper(a, b): # type: (str, str) -> str
5974
func = function(a, b)

0 commit comments

Comments
 (0)