mirror of https://github.com/python/cpython
[3.13] gh-120289: Disallow disable() and clear() in external timer to prevent use-after-free (GH-120297) (#121984)
gh-120289: Disallow disable() and clear() in external timer to prevent use-after-free (GH-120297)
(cherry picked from commit 1ab1778283
)
Co-authored-by: Tian Gao <gaogaotiantian@hotmail.com>
This commit is contained in:
parent
53774e911d
commit
721a7dde11
|
@ -30,6 +30,43 @@ class CProfileTest(ProfileTest):
|
|||
|
||||
self.assertEqual(cm.unraisable.exc_type, TypeError)
|
||||
|
||||
def test_evil_external_timer(self):
|
||||
# gh-120289
|
||||
# Disabling profiler in external timer should not crash
|
||||
import _lsprof
|
||||
class EvilTimer():
|
||||
def __init__(self, disable_count):
|
||||
self.count = 0
|
||||
self.disable_count = disable_count
|
||||
|
||||
def __call__(self):
|
||||
self.count += 1
|
||||
if self.count == self.disable_count:
|
||||
profiler_with_evil_timer.disable()
|
||||
return self.count
|
||||
|
||||
# this will trigger external timer to disable profiler at
|
||||
# call event - in initContext in _lsprof.c
|
||||
with support.catch_unraisable_exception() as cm:
|
||||
profiler_with_evil_timer = _lsprof.Profiler(EvilTimer(1))
|
||||
profiler_with_evil_timer.enable()
|
||||
# Make a call to trigger timer
|
||||
(lambda: None)()
|
||||
profiler_with_evil_timer.disable()
|
||||
profiler_with_evil_timer.clear()
|
||||
self.assertEqual(cm.unraisable.exc_type, RuntimeError)
|
||||
|
||||
# this will trigger external timer to disable profiler at
|
||||
# return event - in Stop in _lsprof.c
|
||||
with support.catch_unraisable_exception() as cm:
|
||||
profiler_with_evil_timer = _lsprof.Profiler(EvilTimer(2))
|
||||
profiler_with_evil_timer.enable()
|
||||
# Make a call to trigger timer
|
||||
(lambda: None)()
|
||||
profiler_with_evil_timer.disable()
|
||||
profiler_with_evil_timer.clear()
|
||||
self.assertEqual(cm.unraisable.exc_type, RuntimeError)
|
||||
|
||||
def test_profile_enable_disable(self):
|
||||
prof = self.profilerclass()
|
||||
# Make sure we clean ourselves up if the test fails for some reason.
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Fixed the use-after-free issue in :mod:`cProfile` by disallowing
|
||||
``disable()`` and ``clear()`` in external timers.
|
|
@ -59,6 +59,7 @@ typedef struct {
|
|||
#define POF_ENABLED 0x001
|
||||
#define POF_SUBCALLS 0x002
|
||||
#define POF_BUILTINS 0x004
|
||||
#define POF_EXT_TIMER 0x008
|
||||
#define POF_NOMEMORY 0x100
|
||||
|
||||
/*[clinic input]
|
||||
|
@ -87,7 +88,14 @@ _lsprof_get_state(PyObject *module)
|
|||
|
||||
static PyTime_t CallExternalTimer(ProfilerObject *pObj)
|
||||
{
|
||||
PyObject *o = _PyObject_CallNoArgs(pObj->externalTimer);
|
||||
PyObject *o = NULL;
|
||||
|
||||
// External timer can do arbitrary things so we need a flag to prevent
|
||||
// horrible things to happen
|
||||
pObj->flags |= POF_EXT_TIMER;
|
||||
o = _PyObject_CallNoArgs(pObj->externalTimer);
|
||||
pObj->flags &= ~POF_EXT_TIMER;
|
||||
|
||||
if (o == NULL) {
|
||||
PyErr_WriteUnraisable(pObj->externalTimer);
|
||||
return 0;
|
||||
|
@ -777,6 +785,11 @@ Stop collecting profiling information.\n\
|
|||
static PyObject*
|
||||
profiler_disable(ProfilerObject *self, PyObject* noarg)
|
||||
{
|
||||
if (self->flags & POF_EXT_TIMER) {
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"cannot disable profiler in external timer");
|
||||
return NULL;
|
||||
}
|
||||
if (self->flags & POF_ENABLED) {
|
||||
PyObject* result = NULL;
|
||||
PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring");
|
||||
|
@ -830,6 +843,11 @@ Clear all profiling information collected so far.\n\
|
|||
static PyObject*
|
||||
profiler_clear(ProfilerObject *pObj, PyObject* noarg)
|
||||
{
|
||||
if (pObj->flags & POF_EXT_TIMER) {
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"cannot clear profiler in external timer");
|
||||
return NULL;
|
||||
}
|
||||
clearEntries(pObj);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue