From 59538ad35bbdcb6d3761272ab54c562d02d90d29 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sun, 9 Jun 2024 12:50:15 -0700 Subject: [PATCH 01/13] Warn if the external timer changes the cprofile context --- Lib/test/test_cprofile.py | 14 ++++++++++++++ Modules/_lsprof.c | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/Lib/test/test_cprofile.py b/Lib/test/test_cprofile.py index 27e8a767903777..009b27b06150c2 100644 --- a/Lib/test/test_cprofile.py +++ b/Lib/test/test_cprofile.py @@ -30,6 +30,20 @@ def test_bad_counter_during_dealloc(self): self.assertEqual(cm.unraisable.exc_type, TypeError) + def test_evil_external_timer(self): + import _lsprof + class EvilTimer(): + def __call__(self): + profiler_with_evil_timer.disable() + return True + + with support.catch_unraisable_exception() as cm: + profiler_with_evil_timer = _lsprof.Profiler(EvilTimer()) + profiler_with_evil_timer.enable() + # Make a call to trigger timer + (lambda: None)() + 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. diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index 5cf9eba243bd20..3fa7d064a48bf7 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -87,12 +87,20 @@ _lsprof_get_state(PyObject *module) static PyTime_t CallExternalTimer(ProfilerObject *pObj) { + ProfilerContext *context = pObj->currentProfilerContext; PyObject *o = _PyObject_CallNoArgs(pObj->externalTimer); if (o == NULL) { PyErr_WriteUnraisable(pObj->externalTimer); return 0; } + if (pObj->currentProfilerContext != context) { + PyErr_SetString(PyExc_RuntimeError, + "external timer callback changed the profiler context"); + PyErr_WriteUnraisable(pObj->externalTimer); + return 0; + } + PyTime_t result; int err; if (pObj->externalTimerUnit > 0.0) { From 811b74c1708d393d696142715188b794a44903cc Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 9 Jun 2024 19:53:14 +0000 Subject: [PATCH 02/13] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst diff --git a/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst b/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst new file mode 100644 index 00000000000000..baf5329391ce7e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst @@ -0,0 +1 @@ +Generate an unraisable exception if the external timer :mod:`cProfile` uses changes the profile context. From df99b2eb5b0afea0d6d06b31a319cee2ae4d503d Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sun, 9 Jun 2024 17:53:10 -0700 Subject: [PATCH 03/13] Do not free memory when disable --- Lib/test/test_cprofile.py | 34 +++++++++++++++++++++++++--------- Modules/_lsprof.c | 12 ++---------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_cprofile.py b/Lib/test/test_cprofile.py index 009b27b06150c2..ebedc1c840799a 100644 --- a/Lib/test/test_cprofile.py +++ b/Lib/test/test_cprofile.py @@ -30,19 +30,35 @@ def test_bad_counter_during_dealloc(self): self.assertEqual(cm.unraisable.exc_type, TypeError) + @unittest.skipUnless(support.check_sanitizer(address=True), "only used to fail with ASAN") def test_evil_external_timer(self): + # gh-120289 + # Disabling profiler in external timer should not crash with ASAN import _lsprof class EvilTimer(): - def __call__(self): - profiler_with_evil_timer.disable() - return True + def __init__(self, disable_count): + self.count = 0 + self.disable_count = disable_count - with support.catch_unraisable_exception() as cm: - profiler_with_evil_timer = _lsprof.Profiler(EvilTimer()) - profiler_with_evil_timer.enable() - # Make a call to trigger timer - (lambda: None)() - self.assertEqual(cm.unraisable.exc_type, RuntimeError) + 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 + profiler_with_evil_timer = _lsprof.Profiler(EvilTimer(1)) + profiler_with_evil_timer.enable() + # Make a call to trigger timer + (lambda: None)() + + # this will trigger external timer to disable profiler at + # return event - in Stop in _lsprof.c + profiler_with_evil_timer = _lsprof.Profiler(EvilTimer(2)) + profiler_with_evil_timer.enable() + # Make a call to trigger timer + (lambda: None)() def test_profile_enable_disable(self): prof = self.profilerclass() diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index 3fa7d064a48bf7..3057154ddc0714 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -87,20 +87,12 @@ _lsprof_get_state(PyObject *module) static PyTime_t CallExternalTimer(ProfilerObject *pObj) { - ProfilerContext *context = pObj->currentProfilerContext; PyObject *o = _PyObject_CallNoArgs(pObj->externalTimer); if (o == NULL) { PyErr_WriteUnraisable(pObj->externalTimer); return 0; } - if (pObj->currentProfilerContext != context) { - PyErr_SetString(PyExc_RuntimeError, - "external timer callback changed the profiler context"); - PyErr_WriteUnraisable(pObj->externalTimer); - return 0; - } - PyTime_t result; int err; if (pObj->externalTimerUnit > 0.0) { @@ -770,8 +762,8 @@ flush_unmatched(ProfilerObject *pObj) Stop(pObj, pContext, profEntry); else pObj->currentProfilerContext = pContext->previous; - if (pContext) - PyMem_Free(pContext); + pContext->previous = pObj->freelistProfilerContext; + pObj->freelistProfilerContext = pContext; } } From 7675efb4bd7266ecc861c7e06b0497e216b8563d Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sun, 9 Jun 2024 21:21:46 -0700 Subject: [PATCH 04/13] We need to check if the context is already put in the free list --- Lib/test/test_cprofile.py | 2 ++ Modules/_lsprof.c | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_cprofile.py b/Lib/test/test_cprofile.py index ebedc1c840799a..20a17495cca650 100644 --- a/Lib/test/test_cprofile.py +++ b/Lib/test/test_cprofile.py @@ -52,6 +52,7 @@ def __call__(self): profiler_with_evil_timer.enable() # Make a call to trigger timer (lambda: None)() + profiler_with_evil_timer.clear() # this will trigger external timer to disable profiler at # return event - in Stop in _lsprof.c @@ -59,6 +60,7 @@ def __call__(self): profiler_with_evil_timer.enable() # Make a call to trigger timer (lambda: None)() + profiler_with_evil_timer.clear() def test_profile_enable_disable(self): prof = self.profilerclass() diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index 3057154ddc0714..195859e16ef3fd 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -42,6 +42,7 @@ typedef struct _ProfilerContext { PyTime_t subt; struct _ProfilerContext *previous; ProfilerEntry *ctxEntry; + bool inFreeList; } ProfilerContext; typedef struct { @@ -296,6 +297,7 @@ initContext(ProfilerObject *pObj, ProfilerContext *self, ProfilerEntry *entry) self->ctxEntry = entry; self->subt = 0; self->previous = pObj->currentProfilerContext; + self->inFreeList = false; pObj->currentProfilerContext = self; ++entry->recursionLevel; if ((pObj->flags & POF_SUBCALLS) && self->previous) { @@ -315,6 +317,9 @@ Stop(ProfilerObject *pObj, ProfilerContext *self, ProfilerEntry *entry) { PyTime_t tt = call_timer(pObj) - self->t0; PyTime_t it = tt - self->subt; + // call_timer could have flushed all contexts + if (pObj->currentProfilerContext == NULL) + return; if (self->previous) self->previous->subt += tt; pObj->currentProfilerContext = self->previous; @@ -400,9 +405,13 @@ ptrace_leave_call(PyObject *self, void *key) else { pObj->currentProfilerContext = pContext->previous; } - /* put pContext into the free list */ - pContext->previous = pObj->freelistProfilerContext; - pObj->freelistProfilerContext = pContext; + /* put pContext into the free list if it's not already in */ + /* we need to check because Stop could potentially call disable*/ + if (!pContext->inFreeList) { + pContext->previous = pObj->freelistProfilerContext; + pObj->freelistProfilerContext = pContext; + pContext->inFreeList = true; + } } static int @@ -764,6 +773,7 @@ flush_unmatched(ProfilerObject *pObj) pObj->currentProfilerContext = pContext->previous; pContext->previous = pObj->freelistProfilerContext; pObj->freelistProfilerContext = pContext; + pContext->inFreeList = true; } } From e2d68e6e52ee4079957c150c660097d65c0cafa4 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Mon, 10 Jun 2024 17:32:00 -0700 Subject: [PATCH 05/13] nullify the context pointer instead of use an extra field --- Modules/_lsprof.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index 195859e16ef3fd..a0495650028972 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -42,7 +42,6 @@ typedef struct _ProfilerContext { PyTime_t subt; struct _ProfilerContext *previous; ProfilerEntry *ctxEntry; - bool inFreeList; } ProfilerContext; typedef struct { @@ -297,7 +296,6 @@ initContext(ProfilerObject *pObj, ProfilerContext *self, ProfilerEntry *entry) self->ctxEntry = entry; self->subt = 0; self->previous = pObj->currentProfilerContext; - self->inFreeList = false; pObj->currentProfilerContext = self; ++entry->recursionLevel; if ((pObj->flags & POF_SUBCALLS) && self->previous) { @@ -313,13 +311,17 @@ initContext(ProfilerObject *pObj, ProfilerContext *self, ProfilerEntry *entry) } static void -Stop(ProfilerObject *pObj, ProfilerContext *self, ProfilerEntry *entry) +Stop(ProfilerObject *pObj, ProfilerContext **pself, ProfilerEntry *entry) { + ProfilerContext *self = *pself; PyTime_t tt = call_timer(pObj) - self->t0; PyTime_t it = tt - self->subt; - // call_timer could have flushed all contexts - if (pObj->currentProfilerContext == NULL) + // call_timer could have flushed all contexts, in this case, set pself to NULL + // to indicate that there's nothing needs to be done for it + if (pObj->currentProfilerContext == NULL) { + *pself = NULL; return; + } if (self->previous) self->previous->subt += tt; pObj->currentProfilerContext = self->previous; @@ -400,17 +402,16 @@ ptrace_leave_call(PyObject *self, void *key) return; profEntry = getEntry(pObj, key); if (profEntry) { - Stop(pObj, pContext, profEntry); + Stop(pObj, &pContext, profEntry); } else { pObj->currentProfilerContext = pContext->previous; } - /* put pContext into the free list if it's not already in */ - /* we need to check because Stop could potentially call disable*/ - if (!pContext->inFreeList) { + /* put pContext into the free list if it's not NULL */ + /* we need to check because Stop could potentially set pContext to NULL*/ + if (pContext != NULL) { pContext->previous = pObj->freelistProfilerContext; pObj->freelistProfilerContext = pContext; - pContext->inFreeList = true; } } @@ -768,12 +769,13 @@ flush_unmatched(ProfilerObject *pObj) ProfilerContext *pContext = pObj->currentProfilerContext; ProfilerEntry *profEntry= pContext->ctxEntry; if (profEntry) - Stop(pObj, pContext, profEntry); + Stop(pObj, &pContext, profEntry); else pObj->currentProfilerContext = pContext->previous; - pContext->previous = pObj->freelistProfilerContext; - pObj->freelistProfilerContext = pContext; - pContext->inFreeList = true; + if (pContext != NULL) { + pContext->previous = pObj->freelistProfilerContext; + pObj->freelistProfilerContext = pContext; + } } } From b317c678c07683aae68b092b145abfc0dff1fbb6 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Thu, 13 Jun 2024 21:09:44 -0700 Subject: [PATCH 06/13] Update 2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst --- .../next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst b/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst index baf5329391ce7e..8a61e2c40cc52e 100644 --- a/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst +++ b/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst @@ -1 +1 @@ -Generate an unraisable exception if the external timer :mod:`cProfile` uses changes the profile context. +Fixed the use-after-free issue in :mod`cProfile` when the profiler is disabled in the external timer. From aa5efcd5b7af2933e54f597de0d46f4cbc2b1c35 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Thu, 13 Jun 2024 21:10:59 -0700 Subject: [PATCH 07/13] Update 2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst --- .../next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst b/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst index 8a61e2c40cc52e..3698b881915b56 100644 --- a/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst +++ b/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst @@ -1 +1 @@ -Fixed the use-after-free issue in :mod`cProfile` when the profiler is disabled in the external timer. +Fixed the use-after-free issue in :mod:`cProfile` when the profiler is disabled in the external timer. From 92cb052c6c4bc0c8421adada67e80b21b0275e58 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 25 Jun 2024 10:56:55 -0700 Subject: [PATCH 08/13] Remove restriction to limit the test on ASAN --- Lib/test/test_cprofile.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_cprofile.py b/Lib/test/test_cprofile.py index 20a17495cca650..892817402e7992 100644 --- a/Lib/test/test_cprofile.py +++ b/Lib/test/test_cprofile.py @@ -30,10 +30,9 @@ def test_bad_counter_during_dealloc(self): self.assertEqual(cm.unraisable.exc_type, TypeError) - @unittest.skipUnless(support.check_sanitizer(address=True), "only used to fail with ASAN") def test_evil_external_timer(self): # gh-120289 - # Disabling profiler in external timer should not crash with ASAN + # Disabling profiler in external timer should not crash import _lsprof class EvilTimer(): def __init__(self, disable_count): From c5f89b5794864cc4f3a0b894eb6924405bef892e Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 2 Jul 2024 17:51:52 -0700 Subject: [PATCH 09/13] Revert changes to lsprof --- Modules/_lsprof.c | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index a0495650028972..7bc934348963b2 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -311,17 +311,10 @@ initContext(ProfilerObject *pObj, ProfilerContext *self, ProfilerEntry *entry) } static void -Stop(ProfilerObject *pObj, ProfilerContext **pself, ProfilerEntry *entry) +Stop(ProfilerObject *pObj, ProfilerContext *self, ProfilerEntry *entry) { - ProfilerContext *self = *pself; PyTime_t tt = call_timer(pObj) - self->t0; PyTime_t it = tt - self->subt; - // call_timer could have flushed all contexts, in this case, set pself to NULL - // to indicate that there's nothing needs to be done for it - if (pObj->currentProfilerContext == NULL) { - *pself = NULL; - return; - } if (self->previous) self->previous->subt += tt; pObj->currentProfilerContext = self->previous; @@ -402,17 +395,15 @@ ptrace_leave_call(PyObject *self, void *key) return; profEntry = getEntry(pObj, key); if (profEntry) { - Stop(pObj, &pContext, profEntry); + Stop(pObj, pContext, profEntry); } else { pObj->currentProfilerContext = pContext->previous; } - /* put pContext into the free list if it's not NULL */ - /* we need to check because Stop could potentially set pContext to NULL*/ - if (pContext != NULL) { - pContext->previous = pObj->freelistProfilerContext; - pObj->freelistProfilerContext = pContext; - } + /* put pContext into the free list */ + pContext->previous = pObj->freelistProfilerContext; + + pObj->freelistProfilerContext = pContext; } static int @@ -769,13 +760,11 @@ flush_unmatched(ProfilerObject *pObj) ProfilerContext *pContext = pObj->currentProfilerContext; ProfilerEntry *profEntry= pContext->ctxEntry; if (profEntry) - Stop(pObj, &pContext, profEntry); + Stop(pObj, pContext, profEntry); else pObj->currentProfilerContext = pContext->previous; - if (pContext != NULL) { - pContext->previous = pObj->freelistProfilerContext; - pObj->freelistProfilerContext = pContext; - } + if (pContext) + PyMem_Free(pContext); } } From 98e27a95175a2b6b080cdeccba62baf9c07a2efe Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 2 Jul 2024 17:52:19 -0700 Subject: [PATCH 10/13] Revert blank line as well --- Modules/_lsprof.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index 7bc934348963b2..5cf9eba243bd20 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -402,7 +402,6 @@ ptrace_leave_call(PyObject *self, void *key) } /* put pContext into the free list */ pContext->previous = pObj->freelistProfilerContext; - pObj->freelistProfilerContext = pContext; } From faa05b14e662cf8850dfacdcbe09a7fa3b6227ad Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 2 Jul 2024 18:05:55 -0700 Subject: [PATCH 11/13] Disallow disable/clear in external timer --- Modules/_lsprof.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index 5cf9eba243bd20..3dd5f554e06f76 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -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; } From 1a448203e58734ccbd5e02ac8a48ff7018a20466 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 2 Jul 2024 18:06:24 -0700 Subject: [PATCH 12/13] Add tests --- Lib/test/test_cprofile.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_cprofile.py b/Lib/test/test_cprofile.py index 892817402e7992..b2595eccc82f70 100644 --- a/Lib/test/test_cprofile.py +++ b/Lib/test/test_cprofile.py @@ -47,19 +47,25 @@ def __call__(self): # this will trigger external timer to disable profiler at # call event - in initContext in _lsprof.c - 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.clear() + 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 - 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.clear() + 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() From d59f4c4f94d713037797d3cb31548a2d14173060 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 2 Jul 2024 18:43:53 -0700 Subject: [PATCH 13/13] Update 2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst --- .../Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst b/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst index 3698b881915b56..518f79dc446ae7 100644 --- a/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst +++ b/Misc/NEWS.d/next/Library/2024-06-09-19-53-11.gh-issue-120289.s4HXR0.rst @@ -1 +1,2 @@ -Fixed the use-after-free issue in :mod:`cProfile` when the profiler is disabled in the external timer. +Fixed the use-after-free issue in :mod:`cProfile` by disallowing +``disable()`` and ``clear()`` in external timers.