python / python/cpython

Use-after-free in lsprof `initContext` via re-entrant external timer `__index__`

Open
#143,545 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

extension-modules topic-profiling type-bug
Dominant language
Python
Stars
77.2k
Forks
35.9k
PR merge metrics
PR metrics pending

Description

What happened?

prof._pystart_callback installs a new ProfilerContext and immediately calls the external timer. If the timer’s __index__ clears the profiler, it frees currentProfilerContext while initContext still writes to it, leading to a heap use-after-free.

Proof of Concept:

import cProfile

class Timer:
    def __call__(self):
        return self

    def __index__(self):
        prof.clear()
        return 0

prof = cProfile.Profile(timer=Timer())

def victim():
    pass

prof._pystart_callback(victim.__code__, 0)

Vulnerable Code Snippet:

Click to expand
/* Buggy Re-entrant Path */
op(_DO_CALL, (callable, self_or_null, args[oparg] -- res)) {
    /* ... */
    PyObject *res_o = PyObject_Vectorcall(
        callable_o, args_o,
        total_args | PY_VECTORCALL_ARGUMENTS_OFFSET,
        NULL);
    /* ... */
}

static void
initContext(ProfilerObject *pObj, ProfilerContext *self, ProfilerEntry *entry)
{
    self->ctxEntry = entry;
    self->subt = 0;
    self->previous = pObj->currentProfilerContext;
    pObj->currentProfilerContext = self;  /* crashing pointer derived */
    /* ... */
    self->t0 = call_timer(pObj);  /* Reentrant call site */ /* Crash site */
}

static PyTime_t
CallExternalTimer(ProfilerObject *pObj)
{
    PyObject *o;
    /* ... */
    o = _PyObject_CallNoArgs(pObj->externalTimer);
    /* ... */
    err = _PyTime_FromSecondsObject(&result, o, _PyTime_ROUND_FLOOR);
    /* ... */
    return result;
}

static inline PyObject*
vectorcall_unbound(PyThreadState *tstate, int unbound, PyObject *func,
                   PyObject *const *args, Py_ssize_t nargs)
{
    /* ... */
    return _PyObject_VectorcallTstate(tstate, func, args, nargsf, NULL);  /* Reentrant call site */
}

/* Clobbering Path */
static void clearEntries(ProfilerObject *pObj)
{
    /* ... */
    if (pObj->currentProfilerContext) {
        PyMem_Free(pObj->currentProfilerContext);  /* state mutate site */
        pObj->currentProfilerContext = NULL;
    }
    /* ... */
}

Sanitizer Output:

Click to expand
=================================================================
==287983==ERROR: AddressSanitizer: heap-use-after-free on address 0x50300001dc80 at pc 0x78e4bf424a0a bp 0x7ffd850cf040 sp 0x7ffd850cf030
WRITE of size 8 at 0x50300001dc80 thread T0
    #0 0x78e4bf424a09 in initContext Modules/_lsprof.c:324
    #1 0x78e4bf424a09 in ptrace_enter_call Modules/_lsprof.c:393
    #2 0x78e4bf424c51 in _lsprof_Profiler__pystart_callback_impl Modules/_lsprof.c:631
    #3 0x78e4bf424c51 in _lsprof_Profiler__pystart_callback Modules/clinic/_lsprof.c.h:88
    #4 0x5f5f549183e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #5 0x5f5f549183e7 in PyObject_Vectorcall Objects/call.c:327
    #6 0x5f5f547cc5a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #7 0x5f5f54c96ad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #8 0x5f5f54c96ad6 in _PyEval_Vector Python/ceval.c:2001
    #9 0x5f5f54c96ad6 in PyEval_EvalCode Python/ceval.c:884
    #10 0x5f5f54ddc16e in run_eval_code_obj Python/pythonrun.c:1365
    #11 0x5f5f54ddc16e in run_mod Python/pythonrun.c:1459
    #12 0x5f5f54de0e17 in pyrun_file Python/pythonrun.c:1293
    #13 0x5f5f54de0e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #14 0x5f5f54de193c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #15 0x5f5f54e54e3c in pymain_run_file_obj Modules/main.c:410
    #16 0x5f5f54e54e3c in pymain_run_file Modules/main.c:429
    #17 0x5f5f54e54e3c in pymain_run_python Modules/main.c:691
    #18 0x5f5f54e5671e in Py_RunMain Modules/main.c:772
    #19 0x5f5f54e5671e in pymain_main Modules/main.c:802
    #20 0x5f5f54e5671e in Py_BytesMain Modules/main.c:826
    #21 0x78e4bf22a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #22 0x78e4bf22a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #23 0x5f5f547f0634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

0x50300001dc80 is located 0 bytes inside of 32-byte region [0x50300001dc80,0x50300001dca0)
freed by thread T0 here:
    #0 0x78e4bf6fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
    #1 0x78e4bf425706 in clearEntries Modules/_lsprof.c:296
    #2 0x78e4bf425706 in _lsprof_Profiler_clear_impl Modules/_lsprof.c:954
    #3 0x78e4bf425706 in _lsprof_Profiler_clear Modules/clinic/_lsprof.c.h:370
    #4 0x5f5f549183e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #5 0x5f5f549183e7 in PyObject_Vectorcall Objects/call.c:327
    #6 0x5f5f547cc5a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #7 0x5f5f54c972a5 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #8 0x5f5f54c972a5 in _PyEval_Vector Python/ceval.c:2001
    #9 0x5f5f54ad3607 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #10 0x5f5f54ad3607 in vectorcall_unbound Objects/typeobject.c:3033
    #11 0x5f5f54ad3607 in vectorcall_method Objects/typeobject.c:3104
    #12 0x5f5f54ad3607 in slot_nb_index Objects/typeobject.c:10507
    #13 0x5f5f548c413f in _PyNumber_Index Objects/abstract.c:1418
    #14 0x5f5f549cc267 in PyLong_AsLongLong Objects/longobject.c:1592
    #15 0x5f5f54de599a in pytime_from_object Python/pytime.c:593
    #16 0x5f5f54de599a in pytime_from_object Python/pytime.c:589
    #17 0x5f5f54de599a in _PyTime_FromSecondsObject Python/pytime.c:630
    #18 0x78e4bf4232f3 in CallExternalTimer Modules/_lsprof.c:120
    #19 0x78e4bf423f91 in call_timer Modules/_lsprof.c:135
    #20 0x78e4bf423f91 in initContext Modules/_lsprof.c:324
    #21 0x78e4bf423f91 in ptrace_enter_call Modules/_lsprof.c:393
    #22 0x78e4bf424c51 in _lsprof_Profiler__pystart_callback_impl Modules/_lsprof.c:631
    #23 0x78e4bf424c51 in _lsprof_Profiler__pystart_callback Modules/clinic/_lsprof.c.h:88
    #24 0x5f5f549183e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #25 0x5f5f549183e7 in PyObject_Vectorcall Objects/call.c:327
    #26 0x5f5f547cc5a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #27 0x5f5f54c96ad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #28 0x5f5f54c96ad6 in _PyEval_Vector Python/ceval.c:2001
    #29 0x5f5f54c96ad6 in PyEval_EvalCode Python/ceval.c:884
    #30 0x5f5f54ddc16e in run_eval_code_obj Python/pythonrun.c:1365
    #31 0x5f5f54ddc16e in run_mod Python/pythonrun.c:1459
    #32 0x5f5f54de0e17 in pyrun_file Python/pythonrun.c:1293
    #33 0x5f5f54de0e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #34 0x5f5f54de193c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #35 0x5f5f54e54e3c in pymain_run_file_obj Modules/main.c:410
    #36 0x5f5f54e54e3c in pymain_run_file Modules/main.c:429
    #37 0x5f5f54e54e3c in pymain_run_python Modules/main.c:691
    #38 0x5f5f54e5671e in Py_RunMain Modules/main.c:772
    #39 0x5f5f54e5671e in pymain_main Modules/main.c:802
    #40 0x5f5f54e5671e in Py_BytesMain Modules/main.c:826
    #41 0x78e4bf22a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #42 0x78e4bf22a28a in __libc_start_main_impl ../csu/libc-start.c:360
CPython versions tested on:
Python Version Status Exit Code
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 28 2025, 16:51:20) Exception 1
Python 3.10.19+ (heads/3.10:014261980b1, Oct 28 2025, 16:52:08) [Clang 18.1.3 (1ubuntu1)] Exception 1
Python 3.11.14+ (heads/3.11:88f3f5b5f11, Oct 28 2025, 16:53:08) [Clang 18.1.3 (1ubuntu1)] Exception 1
Python 3.12.12+ (heads/3.12:8cb2092bd8c, Oct 28 2025, 16:54:14) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.13.9+ (heads/3.13:9c8eade20c6, Oct 28 2025, 16:55:18) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.14.0+ (heads/3.14:2e216728038, Oct 28 2025, 16:56:16) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 19:29:54) [GCC 13.3.0] ASAN 1
Operating systems tested on:

Linux

Output from running 'python -VV' on the command line:

Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 19:29:54) [GCC 13.3.0]

Linked PRs
  • gh-143550

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in Modules/_lsprof.c at initContext, CallExternalTimer, clearEntries, and the __pystart_callback path. Reproduce the supplied Timer example under an ASAN build, then inspect the linked gh-143550 work. Done means the re-entrant callback no longer produces a use-after-free and a regression test covers the behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, python
Domain
devtools, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.