Skip to content

Commit bf8e827

Browse files
authored
chore(iast): improve detection of ddtrace frames (#13344)
Use more robust method to filter out frames from `ddtrace` module. The previous version would not work in some tests if the relevant frame was in site-packages within a virtual env named `ddtrace`, which happens in tests for #13256. This would generally not affect customers, but misbehaves in our test suite.
1 parent 479699d commit bf8e827

File tree

2 files changed

+51
-11
lines changed

2 files changed

+51
-11
lines changed

ddtrace/appsec/_iast/_stacktrace.c

+49-11
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,6 @@ static __thread int in_stacktrace = 0;
1111
#include <patchlevel.h>
1212
#include <stdbool.h>
1313

14-
#ifdef _WIN32
15-
#define DD_TRACE_INSTALLED_PREFIX "\\ddtrace\\"
16-
#define TESTS_PREFIX "\\tests\\"
17-
#define SITE_PACKAGES_PREFIX "\\site-packages\\"
18-
#else
19-
#define DD_TRACE_INSTALLED_PREFIX "/ddtrace/"
20-
#define TESTS_PREFIX "/tests/"
21-
#define SITE_PACKAGES_PREFIX "/site-packages/"
22-
#endif
23-
2414
#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 11
2515
#include <internal/pycore_frame.h>
2616
#define GET_LINENO(frame) PyFrame_GetLineNumber((PyFrameObject*)frame)
@@ -99,6 +89,10 @@ static ssize_t STDLIB_PATH_LEN = 0;
9989
static char* PURELIB_PATH = NULL;
10090
static ssize_t PURELIB_PATH_LEN = 0;
10191

92+
// ddtrace module path
93+
static char* DDTRACE_PATH = NULL;
94+
static ssize_t DDTRACE_PATH_LEN = 0;
95+
10296
static inline PyObject*
10397
SAFE_GET_LOCALS(PyFrameObject* frame)
10498
{
@@ -144,7 +138,7 @@ _is_special_frame(const char* filename)
144138
static inline bool
145139
_is_ddtrace_filename(const char* filename)
146140
{
147-
return filename && strstr(filename, DD_TRACE_INSTALLED_PREFIX) != NULL && strstr(filename, TESTS_PREFIX) == NULL;
141+
return filename && DDTRACE_PATH && strncmp(filename, DDTRACE_PATH, DDTRACE_PATH_LEN) == 0;
148142
}
149143

150144
static inline bool
@@ -189,6 +183,46 @@ get_sysconfig_path(const char* name)
189183
return res;
190184
}
191185

186+
static char*
187+
get_ddtrace_path()
188+
{
189+
char* res = NULL;
190+
PyObject* ddtrace_mod = NULL;
191+
PyObject* path = NULL;
192+
193+
ddtrace_mod = PyImport_ImportModule("ddtrace");
194+
if (!ddtrace_mod) {
195+
goto exit;
196+
}
197+
198+
path = PyObject_GetAttrString(ddtrace_mod, "__file__");
199+
if (!path) {
200+
goto exit;
201+
}
202+
203+
const char* path_str = PyUnicode_AsUTF8(path);
204+
if (path_str) {
205+
// Remove /__init__.py from the end. Suffix is removed by length,
206+
// so no need to check for Windows vs Unix path separator.
207+
const int ddtrace_len = sizeof("ddtrace") - 1;
208+
const int suffix_len = sizeof("/__init__.py") - 1;
209+
const int path_len = strlen(path_str);
210+
if (path_len < ddtrace_len + suffix_len) {
211+
goto exit;
212+
}
213+
const char* ddtrace_part = path_str + path_len - ddtrace_len - suffix_len;
214+
if (strncmp(ddtrace_part, "ddtrace", ddtrace_len) != 0) {
215+
goto exit;
216+
}
217+
res = strndup(path_str, path_len - suffix_len);
218+
}
219+
220+
exit:
221+
Py_XDECREF(path);
222+
Py_XDECREF(ddtrace_mod);
223+
return res;
224+
}
225+
192226
/**
193227
* Gets a reference to a PyFrameObject and walks up the stack until a relevant frame is found.
194228
*
@@ -343,5 +377,9 @@ PyInit__stacktrace(void)
343377
if (PURELIB_PATH) {
344378
PURELIB_PATH_LEN = strlen(PURELIB_PATH);
345379
}
380+
DDTRACE_PATH = get_ddtrace_path();
381+
if (DDTRACE_PATH) {
382+
DDTRACE_PATH_LEN = strlen(DDTRACE_PATH);
383+
}
346384
return m;
347385
}

tests/appsec/integrations/pygoat_tests/test_pygoat.py

+2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ def assert_vulnerability_in_traces(
7474
assert spans, "No spans with meta"
7575
spans = [s for s in spans if "_dd.iast.json" in s["meta"]]
7676
assert spans, "No spans with iast data"
77+
# Ignore vulns from login, which is done on every test
78+
spans = [s for s in spans if s["meta"].get("http.route") != "login/"]
7779
assert len(spans) == 1, "A single span was expected"
7880
span = spans[0]
7981
vulns = json.loads(span["meta"]["_dd.iast.json"])["vulnerabilities"]

0 commit comments

Comments
 (0)