Skip to content

Commit 1dbda11

Browse files
committed
gh-156118: Fix DTrace probes with names that cannot be encoded
The import and function entry/return probes passed the result of PyUnicode_AsUTF8() without checking it, leaving a UnicodeEncodeError set for names with lone surrogates. When unwinding, it also replaced the exception being raised. Now the error is discarded and the current exception is kept; the probes still receive NULL for such names.
1 parent dbac447 commit 1dbda11

9 files changed

Lines changed: 133 additions & 6 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
python$target:::function-entry,
2+
python$target:::function-return
3+
/arg0 == 0 || arg1 == 0/
4+
{
5+
printf("%d\t%s:%d\n", timestamp, probename, arg2);
6+
}
7+
8+
python$target:::import-find-load-start
9+
/arg0 == 0/
10+
{
11+
printf("%d\t%s\n", timestamp, probename);
12+
}
13+
14+
python$target:::import-find-load-done
15+
/arg0 == 0/
16+
{
17+
printf("%d\t%s:%d\n", timestamp, probename, arg1);
18+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
function-entry:1
2+
function-return:2
3+
function-entry:4
4+
function-return:5
5+
import-find-load-start
6+
import-find-load-done:0
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
def function():
2+
return 1
3+
4+
def error():
5+
raise ValueError
6+
7+
# gh-156118: The probes receive NULL for names that cannot be encoded to
8+
# UTF-8, and they must neither leak nor replace an exception.
9+
function.__code__ = function.__code__.replace(co_name='\udc80')
10+
error.__code__ = error.__code__.replace(co_filename='\udc80')
11+
12+
function()
13+
try:
14+
error()
15+
except ValueError:
16+
pass
17+
try:
18+
__import__('\udc80')
19+
except ImportError:
20+
pass
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
probe @PYTHON_SYSTEMTAP_PROBE@("function__entry"),
2+
@PYTHON_SYSTEMTAP_PROBE@("function__return")
3+
{
4+
if ($arg1 == 0 || $arg2 == 0) {
5+
printf("%d\t%s:%d\n", gettimeofday_us(), $$name, $arg3);
6+
}
7+
}
8+
9+
probe @PYTHON_SYSTEMTAP_PROBE@("import__find__load__start")
10+
{
11+
if ($arg1 == 0) {
12+
printf("%d\t%s\n", gettimeofday_us(), $$name);
13+
}
14+
}
15+
16+
probe @PYTHON_SYSTEMTAP_PROBE@("import__find__load__done")
17+
{
18+
if ($arg1 == 0) {
19+
printf("%d\t%s:%d\n", gettimeofday_us(), $$name, $arg2);
20+
}
21+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
function__entry:1
2+
function__return:2
3+
function__entry:4
4+
function__return:5
5+
import__find__load__start
6+
import__find__load__done:0

‎Lib/test/test_dtrace.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,20 @@ class BPFTraceBackend(TraceBackend):
308308
}}
309309
END {{ clear(@tracing); }}
310310
""",
311+
"unencodable_names": """
312+
usdt:{python}:python:function__entry /arg0 == 0 || arg1 == 0/ {{
313+
printf("%lld\\tfunction__entry:%d\\n", nsecs, arg2);
314+
}}
315+
usdt:{python}:python:function__return /arg0 == 0 || arg1 == 0/ {{
316+
printf("%lld\\tfunction__return:%d\\n", nsecs, arg2);
317+
}}
318+
usdt:{python}:python:import__find__load__start /arg0 == 0/ {{
319+
printf("%lld\\timport__find__load__start\\n", nsecs);
320+
}}
321+
usdt:{python}:python:import__find__load__done /arg0 == 0/ {{
322+
printf("%lld\\timport__find__load__done:%d\\n", nsecs, arg1);
323+
}}
324+
""",
311325
}
312326

313327
# Which test scripts to filter by filename (None = use @tracing flag)
@@ -352,6 +366,12 @@ def _filter_probe_rows(output):
352366
gc__done:0
353367
gc__start:2
354368
gc__done:1""",
369+
"unencodable_names": """function__entry:1
370+
function__return:2
371+
function__entry:4
372+
function__return:5
373+
import__find__load__start
374+
import__find__load__done:0""",
355375
}
356376

357377
def run_case(self, name, optimize_python=None):
@@ -494,6 +514,9 @@ def get_function_instructions(funcname):
494514
def test_gc(self):
495515
self.run_case("gc")
496516

517+
def test_unencodable_names(self):
518+
self.run_case("unencodable_names")
519+
497520

498521
class DTraceNormalTests(TraceTests, unittest.TestCase):
499522
backend = DTraceBackend()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix a crash in debug builds, and a spurious :exc:`SystemError` or
2+
:exc:`UnicodeEncodeError` in release builds, when a DTrace or SystemTap
3+
probe fires for a module, function or file name that cannot be encoded to
4+
UTF-8.

‎Python/ceval.c‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,21 @@ int _PyEval_StoreName(PyThreadState *tstate, _PyStackRef v, PyObject *name, PyOb
11961196
#endif
11971197

11981198
#ifdef WITH_DTRACE
1199+
/* Return the UTF-8 encoding of name, or NULL if it cannot be encoded
1200+
(e.g. it contains a lone surrogate). Never sets an exception and keeps
1201+
the current one, if any, since it is used in the unwinding path. */
1202+
static const char *
1203+
dtrace_utf8(PyObject *name)
1204+
{
1205+
PyObject *exc = PyErr_GetRaisedException();
1206+
const char *utf8 = PyUnicode_AsUTF8(name);
1207+
if (utf8 == NULL) {
1208+
PyErr_Clear();
1209+
}
1210+
PyErr_SetRaisedException(exc);
1211+
return utf8;
1212+
}
1213+
11991214
static void
12001215
dtrace_function_entry(_PyInterpreterFrame *frame)
12011216
{
@@ -1204,8 +1219,8 @@ dtrace_function_entry(_PyInterpreterFrame *frame)
12041219
int lineno;
12051220

12061221
PyCodeObject *code = _PyFrame_GetCode(frame);
1207-
filename = PyUnicode_AsUTF8(code->co_filename);
1208-
funcname = PyUnicode_AsUTF8(code->co_name);
1222+
filename = dtrace_utf8(code->co_filename);
1223+
funcname = dtrace_utf8(code->co_name);
12091224
lineno = PyUnstable_InterpreterFrame_GetLine(frame);
12101225

12111226
PyDTrace_FUNCTION_ENTRY(filename, funcname, lineno);
@@ -1219,8 +1234,8 @@ dtrace_function_return(_PyInterpreterFrame *frame)
12191234
int lineno;
12201235

12211236
PyCodeObject *code = _PyFrame_GetCode(frame);
1222-
filename = PyUnicode_AsUTF8(code->co_filename);
1223-
funcname = PyUnicode_AsUTF8(code->co_name);
1237+
filename = dtrace_utf8(code->co_filename);
1238+
funcname = dtrace_utf8(code->co_name);
12241239
lineno = PyUnstable_InterpreterFrame_GetLine(frame);
12251240

12261241
PyDTrace_FUNCTION_RETURN(filename, funcname, lineno);

‎Python/import.c‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,20 @@ get_importtime_name(PyObject *name)
299299
return encoded;
300300
}
301301

302+
/* Return the UTF-8 encoding of name for a DTrace probe, or NULL if it cannot
303+
be encoded. Never sets an exception and keeps the current one, if any. */
304+
static const char *
305+
get_dtrace_name(PyObject *name)
306+
{
307+
PyObject *exc = PyErr_GetRaisedException();
308+
const char *utf8 = PyUnicode_AsUTF8(name);
309+
if (utf8 == NULL) {
310+
PyErr_Clear();
311+
}
312+
PyErr_SetRaisedException(exc);
313+
return utf8;
314+
}
315+
302316
static int
303317
import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *name)
304318
{
@@ -4120,14 +4134,14 @@ import_find_and_load_with_name(PyThreadState *tstate, PyObject *abs_name,
41204134
}
41214135

41224136
if (PyDTrace_IMPORT_FIND_LOAD_START_ENABLED())
4123-
PyDTrace_IMPORT_FIND_LOAD_START(PyUnicode_AsUTF8(abs_name));
4137+
PyDTrace_IMPORT_FIND_LOAD_START(get_dtrace_name(abs_name));
41244138

41254139
mod = PyObject_CallMethodObjArgs(IMPORTLIB(interp), find_and_load,
41264140
abs_name, IMPORT_FUNC(interp), NULL);
41274141

41284142
if (PyDTrace_IMPORT_FIND_LOAD_DONE_ENABLED()) {
41294143
int found = mod != NULL && mod != not_found;
4130-
PyDTrace_IMPORT_FIND_LOAD_DONE(PyUnicode_AsUTF8(abs_name),
4144+
PyDTrace_IMPORT_FIND_LOAD_DONE(get_dtrace_name(abs_name),
41314145
found);
41324146
}
41334147

0 commit comments

Comments
 (0)