Skip to content

Commit 001584d

Browse files
[3.12] gh-120289: Disallow disable() and clear() in external timer to prevent use-after-free (GH-120297) (#121989)
gh-120289: Disallow disable() and clear() in external timer to prevent use-after-free (GH-120297) (cherry picked from commit 1ab1778) Co-authored-by: Tian Gao <[email protected]>
1 parent f88ec9a commit 001584d

File tree

3 files changed

+58
-1
lines changed

3 files changed

+58
-1
lines changed

Lib/test/test_cprofile.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,43 @@ def test_bad_counter_during_dealloc(self):
3030

3131
self.assertEqual(cm.unraisable.exc_type, TypeError)
3232

33+
def test_evil_external_timer(self):
34+
# gh-120289
35+
# Disabling profiler in external timer should not crash
36+
import _lsprof
37+
class EvilTimer():
38+
def __init__(self, disable_count):
39+
self.count = 0
40+
self.disable_count = disable_count
41+
42+
def __call__(self):
43+
self.count += 1
44+
if self.count == self.disable_count:
45+
profiler_with_evil_timer.disable()
46+
return self.count
47+
48+
# this will trigger external timer to disable profiler at
49+
# call event - in initContext in _lsprof.c
50+
with support.catch_unraisable_exception() as cm:
51+
profiler_with_evil_timer = _lsprof.Profiler(EvilTimer(1))
52+
profiler_with_evil_timer.enable()
53+
# Make a call to trigger timer
54+
(lambda: None)()
55+
profiler_with_evil_timer.disable()
56+
profiler_with_evil_timer.clear()
57+
self.assertEqual(cm.unraisable.exc_type, RuntimeError)
58+
59+
# this will trigger external timer to disable profiler at
60+
# return event - in Stop in _lsprof.c
61+
with support.catch_unraisable_exception() as cm:
62+
profiler_with_evil_timer = _lsprof.Profiler(EvilTimer(2))
63+
profiler_with_evil_timer.enable()
64+
# Make a call to trigger timer
65+
(lambda: None)()
66+
profiler_with_evil_timer.disable()
67+
profiler_with_evil_timer.clear()
68+
self.assertEqual(cm.unraisable.exc_type, RuntimeError)
69+
3370
def test_profile_enable_disable(self):
3471
prof = self.profilerclass()
3572
# Make sure we clean ourselves up if the test fails for some reason.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed the use-after-free issue in :mod:`cProfile` by disallowing
2+
``disable()`` and ``clear()`` in external timers.

Modules/_lsprof.c

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ typedef struct {
5656
#define POF_ENABLED 0x001
5757
#define POF_SUBCALLS 0x002
5858
#define POF_BUILTINS 0x004
59+
#define POF_EXT_TIMER 0x008
5960
#define POF_NOMEMORY 0x100
6061

6162
/*[clinic input]
@@ -84,7 +85,14 @@ _lsprof_get_state(PyObject *module)
8485

8586
static _PyTime_t CallExternalTimer(ProfilerObject *pObj)
8687
{
87-
PyObject *o = _PyObject_CallNoArgs(pObj->externalTimer);
88+
PyObject *o = NULL;
89+
90+
// External timer can do arbitrary things so we need a flag to prevent
91+
// horrible things to happen
92+
pObj->flags |= POF_EXT_TIMER;
93+
o = _PyObject_CallNoArgs(pObj->externalTimer);
94+
pObj->flags &= ~POF_EXT_TIMER;
95+
8896
if (o == NULL) {
8997
PyErr_WriteUnraisable(pObj->externalTimer);
9098
return 0;
@@ -773,6 +781,11 @@ Stop collecting profiling information.\n\
773781
static PyObject*
774782
profiler_disable(ProfilerObject *self, PyObject* noarg)
775783
{
784+
if (self->flags & POF_EXT_TIMER) {
785+
PyErr_SetString(PyExc_RuntimeError,
786+
"cannot disable profiler in external timer");
787+
return NULL;
788+
}
776789
if (self->flags & POF_ENABLED) {
777790
PyObject* result = NULL;
778791
PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring");
@@ -826,6 +839,11 @@ Clear all profiling information collected so far.\n\
826839
static PyObject*
827840
profiler_clear(ProfilerObject *pObj, PyObject* noarg)
828841
{
842+
if (pObj->flags & POF_EXT_TIMER) {
843+
PyErr_SetString(PyExc_RuntimeError,
844+
"cannot clear profiler in external timer");
845+
return NULL;
846+
}
829847
clearEntries(pObj);
830848
Py_RETURN_NONE;
831849
}

0 commit comments

Comments
 (0)