-
Notifications
You must be signed in to change notification settings - Fork 413
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(iast): index propagation (#7050)
## 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.
- Loading branch information
Showing
8 changed files
with
294 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
#include <pybind11/stl.h> | ||
|
||
#include "AspectIndex.h" | ||
|
||
PyObject* | ||
index_aspect(PyObject* result_o, PyObject* candidate_text, PyObject* idx, TaintRangeMapType* tx_taint_map) | ||
{ | ||
size_t len_result_o{ get_pyobject_size(result_o) }; | ||
auto idx_long = PyLong_AsLong(idx); | ||
TaintRangeRefs ranges_to_set; | ||
auto ranges = get_ranges(candidate_text); | ||
for (const auto& current_range : ranges) { | ||
if (current_range->start <= idx_long and idx_long < (current_range->start + current_range->length)) { | ||
ranges_to_set.emplace_back(initializer->allocate_taint_range(0l, 1l, current_range->source)); | ||
break; | ||
} | ||
} | ||
|
||
const auto& res_new_id = new_pyobject_id(result_o, len_result_o); | ||
Py_DECREF(result_o); | ||
|
||
if (ranges_to_set.empty()) { | ||
return res_new_id; | ||
} | ||
set_ranges(res_new_id, ranges_to_set); | ||
|
||
return res_new_id; | ||
} | ||
|
||
PyObject* | ||
api_index_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs) | ||
{ | ||
if (nargs != 2) { | ||
return nullptr; | ||
} | ||
PyObject* candidate_text = args[0]; | ||
PyObject* idx = args[1]; | ||
|
||
PyObject* result_o; | ||
|
||
result_o = PyObject_GetItem(candidate_text, idx); | ||
auto ctx_map = initializer->get_tainting_map(); | ||
if (not ctx_map or ctx_map->empty()) { | ||
return result_o; | ||
} | ||
auto res = index_aspect(result_o, candidate_text, idx, ctx_map); | ||
return res; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#pragma once | ||
#include "Aspects/Helpers.h" | ||
#include "TaintedOps/TaintedOps.h" | ||
|
||
PyObject* | ||
index_aspect(PyObject* result_o, PyObject* candidate_text, PyObject* idx, TaintRangeMapType* tx_taint_map); | ||
PyObject* | ||
api_index_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# -*- encoding: utf-8 -*- | ||
import sys | ||
|
||
import pytest | ||
|
||
from ddtrace.appsec._iast._utils import _is_python_version_supported as python_supported_by_iast | ||
from tests.appsec.iast.aspects.conftest import _iast_patched_module | ||
|
||
|
||
if python_supported_by_iast(): | ||
from ddtrace.appsec._iast._taint_tracking import OriginType | ||
from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges | ||
from ddtrace.appsec._iast._taint_tracking import taint_pyobject | ||
|
||
mod = _iast_patched_module("tests.appsec.iast.fixtures.aspects.str_methods") | ||
|
||
|
||
@pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") | ||
def test_string_index_error_index_error(): | ||
with pytest.raises(IndexError) as excinfo: | ||
mod.do_index("abc", 22) # pylint: disable=no-member | ||
assert "string index out of range" in str(excinfo.value) | ||
|
||
|
||
@pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") | ||
def test_string_index_error_type_error(): | ||
with pytest.raises(TypeError) as excinfo: | ||
mod.do_index("abc", "22") # pylint: disable=no-member | ||
assert "string indices must be integers" in str(excinfo.value) | ||
|
||
|
||
@pytest.mark.skipif( | ||
not python_supported_by_iast() or sys.version_info < (3, 9, 0), reason="Python version not supported by IAST" | ||
) | ||
@pytest.mark.parametrize( | ||
"input_str, index_pos, expected_result, tainted", | ||
[ | ||
("abcde", 0, "a", True), | ||
("abcde", 1, "b", True), | ||
("abc", 2, "c", True), | ||
(b"abcde", 0, 97, False), | ||
(b"abcde", 1, 98, False), | ||
(b"abc", 2, 99, False), | ||
(bytearray(b"abcde"), 0, 97, False), | ||
(bytearray(b"abcde"), 1, 98, False), | ||
(bytearray(b"abc"), 2, 99, False), | ||
], | ||
) | ||
def test_string_index(input_str, index_pos, expected_result, tainted): | ||
string_input = taint_pyobject( | ||
pyobject=input_str, | ||
source_name="test_add_aspect_tainting_left_hand", | ||
source_value="foo", | ||
source_origin=OriginType.PARAMETER, | ||
) | ||
assert get_tainted_ranges(string_input) | ||
result = mod.do_index(string_input, index_pos) | ||
|
||
assert result == expected_result | ||
tainted_ranges = get_tainted_ranges(result) | ||
if not tainted: | ||
assert len(tainted_ranges) == 0 | ||
else: | ||
assert len(tainted_ranges) == 1 | ||
assert tainted_ranges[0].start == 0 | ||
assert tainted_ranges[0].length == 1 |
Oops, something went wrong.