Skip to content

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

@jackfromeast

Description

@jackfromeast

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:

Details
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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions