python / python/cpython

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

Ouverte
#143,545 2 commentaires 1 réaction 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

extension-modules topic-profiling type-bug
Langage dominant
Python
Étoiles
77.2k
Forks
35.9k
Métriques de merge des PR
Métriques de PR en attente

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

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez dans Modules/_lsprof.c, au niveau de initContext, CallExternalTimer, clearEntries et du chemin __pystart_callback. Reproduisez l’exemple Timer fourni avec une build ASAN, puis examinez le travail associé gh-143550. C’est terminé lorsque le callback réentrant ne produit plus de use-after-free et qu’un test de régression couvre ce comportement.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
c, python
Domaine
devtools, performance
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
À l'abandon
Clarté
Plutôt claire
Accessibilité débutants
25/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.