Skip to content

Commit 6d8523d

Browse files
fix(iast): format aspect propagation error [backport 1.20] (#7889)
Backport 4626db4 from #7884 to 1.20. IAST: Fix propagation error on ``.format`` string method. Error partially introduced in: #7772 ## 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: Alberto Vara <[email protected]>
1 parent 0db7b10 commit 6d8523d

File tree

4 files changed

+27
-36
lines changed

4 files changed

+27
-36
lines changed

ddtrace/appsec/iast/_taint_tracking/Aspects/AspectFormat.cpp

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,23 @@ api_format_aspect(StrType& candidate_text,
1717
py::list new_args;
1818
py::dict new_kwargs;
1919
for (const auto arg : args) {
20-
auto str_arg = py::cast<py::str>(arg);
21-
auto n_arg = _all_as_formatted_evidence<py::str>(str_arg, TagMappingMode::Mapper);
22-
new_args.append(n_arg);
20+
if (is_text(arg.ptr())) {
21+
auto str_arg = py::cast<py::str>(arg);
22+
auto n_arg = _all_as_formatted_evidence<py::str>(str_arg, TagMappingMode::Mapper);
23+
new_args.append(n_arg);
24+
} else {
25+
new_args.append(arg);
26+
}
2327
}
24-
for (auto [key, value] : new_kwargs) {
25-
auto str_value = py::cast<py::str>(value);
26-
auto n_value = _all_as_formatted_evidence<py::str>(str_value, TagMappingMode::Mapper);
27-
new_kwargs[key] = n_value;
28+
for (auto [key, value] : kwargs) {
29+
if (is_text(value.ptr())) {
30+
auto str_value = py::cast<py::str>(value);
31+
auto n_value = _all_as_formatted_evidence<py::str>(str_value, TagMappingMode::Mapper);
32+
new_kwargs[key] = n_value;
33+
} else {
34+
new_kwargs[key] = value;
35+
}
2836
}
29-
3037
StrType new_template_format =
3138
py::getattr(new_template, "format")(*(py::cast<py::tuple>(new_args)), **new_kwargs);
3239
std::tuple result = _convert_escaped_text_to_taint_text<StrType>(new_template_format, ranges_orig);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
fixes:
3+
- |
4+
Vulnerability Management for Code-level (IAST): Fix propagation error on ``.format`` string method.

tests/appsec/iast/aspects/test_format_aspect_fixtures.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ def test_format_when_tainted_unicode_emoji_strings_then_tainted_result(self):
113113
escaped_expected_result=":+-<input1>template⚠️<input1>-+: " ":+-<input2>parameter⚠️<input2>-+:",
114114
)
115115

116-
@pytest.mark.skip(reason="Migrating format_aspect to C++ breaks this test")
117116
def test_format_when_tainted_template_range_no_brackets_and_param_not_str_then_tainted(self):
118117
# type: () -> None
119118
self._assert_format_result(
@@ -123,7 +122,6 @@ def test_format_when_tainted_template_range_no_brackets_and_param_not_str_then_t
123122
escaped_expected_result=":+-<input1>template<input1>-+: 3.14",
124123
)
125124

126-
@pytest.mark.skip(reason="Migrating format_aspect to C++ breaks this test")
127125
def test_format_when_tainted_template_range_with_brackets_and_param_not_str_then_tainted(self):
128126
# type: () -> None
129127
self._assert_format_result(
@@ -163,9 +161,8 @@ def test_format_with_args_and_kwargs(self): # type: () -> None
163161
assert as_formatted_evidence(res) == "-1234 6 1"
164162

165163
string_input = create_taint_range_with_format(":+--12-+:34 {} {test_var}")
166-
mod.do_args_kwargs_1(string_input, 6, test_var=1) # pylint: disable=no-member
167-
# TODO format with params doesn't work correctly
168-
# assert as_formatted_evidence(res) == ":+--12-+:34 6 1"
164+
res = mod.do_args_kwargs_1(string_input, 6, test_var=1) # pylint: disable=no-member
165+
assert as_formatted_evidence(res) == ":+--12-+:34 6 1"
169166

170167
def test_format_with_one_argument_args_and_kwargs(self): # type: () -> None
171168
string_input = "-1234 {} {} {test_var}"
@@ -177,9 +174,8 @@ def test_format_with_one_argument_args_and_kwargs(self): # type: () -> None
177174
assert as_formatted_evidence(res) == "-1234 1 6 1"
178175

179176
string_input = create_taint_range_with_format(":+--12-+:34 {} {} {test_var}")
180-
mod.do_args_kwargs_2(string_input, 6, test_var=1) # pylint: disable=no-member
181-
# TODO format with params doesn't work correctly
182-
# as_formatted_evidence(res) == ":+--12-+:34 1 6 1"
177+
res = mod.do_args_kwargs_2(string_input, 6, test_var=1) # pylint: disable=no-member
178+
assert as_formatted_evidence(res) == ":+--12-+:34 1 6 1"
183179

184180
def test_format_with_two_argument_args_and_kwargs(self): # type: () -> None
185181
string_input = "-1234 {} {} {} {test_var}"
@@ -191,9 +187,8 @@ def test_format_with_two_argument_args_and_kwargs(self): # type: () -> None
191187
assert as_formatted_evidence(res) == "-1234 1 2 6 1"
192188

193189
string_input = create_taint_range_with_format(":+--12-+:34 {} {} {} {test_var}")
194-
mod.do_args_kwargs_3(string_input, 6, test_var=1) # pylint: disable=no-member
195-
# TODO format with params doesn't work correctly
196-
# as_formatted_evidence(res) == ":+--12-+:34 1 2 6 1"
190+
res = mod.do_args_kwargs_3(string_input, 6, test_var=1) # pylint: disable=no-member
191+
assert as_formatted_evidence(res) == ":+--12-+:34 1 2 6 1"
197192

198193
def test_format_with_two_argument_two_keywordargument_args_kwargs(self): # type: () -> None
199194
string_input = "-1234 {} {} {} {test_kwarg} {test_var}"
@@ -205,9 +200,8 @@ def test_format_with_two_argument_two_keywordargument_args_kwargs(self): # type
205200
assert as_formatted_evidence(res) == "-1234 1 2 6 3 1"
206201

207202
string_input = create_taint_range_with_format(":+--12-+:34 {} {} {} {test_kwarg} {test_var}")
208-
mod.do_args_kwargs_4(string_input, 6, test_var=1) # pylint: disable=no-member
209-
# TODO format with params doesn't work correctly
210-
# as_formatted_evidence(res) == ":+--12-+:34 1 2 6 3 1"
203+
res = mod.do_args_kwargs_4(string_input, 6, test_var=1) # pylint: disable=no-member
204+
assert as_formatted_evidence(res) == ":+--12-+:34 1 2 6 3 1"
211205

212206
def test_format_when_tainted_template_range_special_then_tainted_result(self): # type: () -> None
213207
self._assert_format_result(

tests/appsec/iast/aspects/test_format_map_aspect_fixtures.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,19 @@ def _assert_format_map_result(
3636

3737
assert as_formatted_evidence(result, tag_mapping_function=None) == escaped_expected_result
3838

39-
@pytest.mark.skipif(not hasattr("", "format_map"), reason="Method format_map not supported")
4039
def test_format_map_when_template_is_none_then_raises_attribute_error(self): # type: () -> None
4140
with pytest.raises(AttributeError):
4241
mod.do_format_map(None, {})
4342

44-
@pytest.mark.skipif(not hasattr("", "format_map"), reason="Method format_map not supported")
4543
def test_format_map_when_parameter_is_none_then_raises_type_error(self): # type: () -> None
4644
with pytest.raises(TypeError):
4745
assert mod.do_format_map("{key}", None) == "None"
4846

49-
@pytest.mark.skipif(not hasattr("", "format_map"), reason="Method format_map not supported")
5047
def test_format_map_when_no_tainted_strings_then_no_tainted_result(self): # type: () -> None
5148
result = mod.do_format_map("template {key}", {"key": "parameter"})
5249
assert result, "template parameter"
5350
assert not get_ranges(result)
5451

55-
@pytest.mark.skipif(not hasattr("", "format_map"), reason="Method format_map not supported")
5652
def test_format_map_when_tainted_parameter_then_tainted_result(self): # type: () -> None
5753
self._assert_format_map_result(
5854
taint_escaped_template="template {key}",
@@ -61,7 +57,6 @@ def test_format_map_when_tainted_parameter_then_tainted_result(self): # type: (
6157
escaped_expected_result="template :+-<input1>parameter<input1>-+:",
6258
)
6359

64-
@pytest.mark.skipif(not hasattr("", "format_map"), reason="Method format_map not supported")
6560
def test_format_map_when_tainted_template_range_no_brackets_then_tainted_result(self): # type: () -> None
6661
self._assert_format_map_result(
6762
taint_escaped_template=":+-<input1>template<input1>-+: {key}",
@@ -70,7 +65,6 @@ def test_format_map_when_tainted_template_range_no_brackets_then_tainted_result(
7065
escaped_expected_result=":+-<input1>template<input1>-+: parameter",
7166
)
7267

73-
@pytest.mark.skipif(not hasattr("", "format_map"), reason="Method format_map not supported")
7468
def test_format_map_when_tainted_template_range_with_brackets_then_tainted_result(self): # type: () -> None
7569
self._assert_format_map_result(
7670
taint_escaped_template="template :+-<input1>{key}<input1>-+:",
@@ -79,7 +73,6 @@ def test_format_map_when_tainted_template_range_with_brackets_then_tainted_resul
7973
escaped_expected_result="template :+-<input1>parameter<input1>-+:",
8074
)
8175

82-
@pytest.mark.skipif(not hasattr("", "format_map"), reason="Method format_map not supported")
8376
def test_format_map_when_tainted_template_range_no_brackets_and_tainted_param_then_tainted(
8477
self,
8578
): # type: () -> None
@@ -90,7 +83,6 @@ def test_format_map_when_tainted_template_range_no_brackets_and_tainted_param_th
9083
escaped_expected_result=":+-<input1>template<input1>-+: " ":+-<input2>parameter<input2>-+:",
9184
)
9285

93-
@pytest.mark.skipif(not hasattr("", "format_map"), reason="Method format_map not supported")
9486
def test_format_map_when_tainted_template_range_with_brackets_and_tainted_param_then_tainted(
9587
self,
9688
): # type: () -> None
@@ -101,7 +93,6 @@ def test_format_map_when_tainted_template_range_with_brackets_and_tainted_param_
10193
escaped_expected_result=":+-<input1>template <input1>-+:" ":+-<input2>parameter<input2>-+:",
10294
)
10395

104-
@pytest.mark.skipif(not hasattr("", "format_map"), reason="Method format_map not supported")
10596
def test_format_map_when_ranges_overlap_then_give_preference_to_ranges_from_parameter(self): # type: () -> None
10697
self._assert_format_map_result(
10798
taint_escaped_template=":+-<input1>template {key} range overlapping<input1>-+:",
@@ -112,7 +103,6 @@ def test_format_map_when_ranges_overlap_then_give_preference_to_ranges_from_para
112103
":+-<input1> range overlapping<input1>-+:",
113104
)
114105

115-
@pytest.mark.skipif(not hasattr("", "format_map"), reason="Method format_map not supported")
116106
def test_format_map_when_tainted_str_emoji_strings_then_tainted_result(self): # type: () -> None
117107
self._assert_format_map_result(
118108
taint_escaped_template=":+-<input1>template⚠️<input1>-+: {key}",
@@ -121,7 +111,6 @@ def test_format_map_when_tainted_str_emoji_strings_then_tainted_result(self): #
121111
escaped_expected_result=":+-<input1>template⚠️<input1>-+: " ":+-<input2>parameter⚠️<input2>-+:",
122112
)
123113

124-
@pytest.mark.skipif(not hasattr("", "format_map"), reason="Method format_map not supported")
125114
def test_format_map_when_tainted_template_range_no_brackets_and_param_not_str_then_tainted(
126115
self,
127116
): # type: () -> None
@@ -132,7 +121,6 @@ def test_format_map_when_tainted_template_range_no_brackets_and_param_not_str_th
132121
escaped_expected_result=":+-<input1>template<input1>-+: 3.14",
133122
)
134123

135-
@pytest.mark.skipif(not hasattr("", "format_map"), reason="Method format_map not supported")
136124
def test_format_map_when_tainted_template_range_with_brackets_and_param_not_str_then_tainted(
137125
self,
138126
): # type: () -> None
@@ -143,7 +131,6 @@ def test_format_map_when_tainted_template_range_with_brackets_and_param_not_str_
143131
escaped_expected_result=":+-<input1>template 3.14<input1>-+:",
144132
)
145133

146-
@pytest.mark.skipif(not hasattr("", "format_map"), reason="Method format_map not supported")
147134
def test_format_map_when_texts_tainted_and_contain_escape_sequences_then_result_uncorrupted(
148135
self,
149136
): # type: () -> None
@@ -156,7 +143,6 @@ def test_format_map_when_texts_tainted_and_contain_escape_sequences_then_result_
156143
":+-<0>my_code<0>-+:",
157144
)
158145

159-
@pytest.mark.skipif(not hasattr("", "format_map"), reason="Method format_map not supported")
160146
def test_format_map_when_parameter_value_already_present_in_template_then_range_is_correct(
161147
self,
162148
): # type: () -> None

0 commit comments

Comments
 (0)