Skip to content

Use-after-free when sys.unraisablehook fails and falls back to default logger #143547

Closed

Description

@jackfromeast

What happened?

POP_TOP finalizes an object whose __del__ raises, invoking sys.unraisablehook. format_unraisable_v calls the hook re-entrantly, then decref's it before reusing the pointer as the fallback obj. When the hook raises (freeing the partial), write_unraisable_exc_file tries to repr the dangling obj, hitting a heap use-after-free in _Py_TYPE.

Proof of Concept:

import sys
import functools


def takeover(unraisable):
    sys.unraisablehook = sys.__unraisablehook__
    raise RuntimeError("hook failure")


sys.unraisablehook = functools.partial(takeover)


class Boom:
    def __del__(self):
        raise ValueError("boom")


Boom()

Vulnerable Code Snippet:

Click to expand
/* Buggy Re-entrant Path */
pure inst(POP_TOP, (value --)) {
    PyStackRef_XCLOSE(value);
}

static void
format_unraisable_v(const char *format, va_list va, PyObject *obj)
{
    /* ... */
    PyObject *res = PyObject_CallOneArg(hook, hook_args);  /* Reentrant call site */
    Py_DECREF(hook);
    Py_DECREF(hook_args);
    if (res != NULL) {
        /* ... */
        goto done;
    }

    /* sys.unraisablehook failed: log its error using default hook */
    obj = hook;  /* crashing pointer derived */
    /* ... */
    (void)write_unraisable_exc(tstate, exc_type, exc_value, exc_tb,
                               err_msg, obj);
}

static int
write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type,
                          PyObject *exc_value, PyObject *exc_tb,
                          PyObject *err_msg, PyObject *obj, PyObject *file)
{
    /* ... */
    if (PyFile_WriteObject(obj, file, 0) < 0) {  /* Crash site */
        _PyErr_Clear(tstate);
    }
    /* ... */
    return 0;
}

static inline PyTypeObject* _Py_TYPE(PyObject *ob)
{
    return ob->ob_type;  /* Crash site */
}

/* Clobbering Path */
static void
partial_dealloc(PyObject *self)
{
    PyTypeObject *tp = Py_TYPE(self);
    /* ... */
    tp->tp_free(self);  /* state mutate site */
    Py_DECREF(tp);
}

Sanitizer Output:

Click to expand
=================================================================
==71182==ERROR: AddressSanitizer: heap-use-after-free on address 0x5080000234b8 at pc 0x58b4baa5e1d5 bp 0x7fffb29a0520 sp 0x7fffb29a0510
READ of size 8 at 0x5080000234b8 thread T0
    #0 0x58b4baa5e1d4 in _Py_TYPE Include/object.h:277
    #1 0x58b4baa5e1d4 in PyObject_Repr Objects/object.c:761
    #2 0x58b4ba99d32c in PyFile_WriteObject Objects/fileobject.c:122
    #3 0x58b4bad20902 in write_unraisable_exc_file Python/errors.c:1472
    #4 0x58b4bad2a94f in write_unraisable_exc Python/errors.c:1605
    #5 0x58b4bad2a94f in format_unraisable_v Python/errors.c:1752
    #6 0x58b4bad2cd04 in PyErr_FormatUnraisable Python/errors.c:1769
    #7 0x58b4baaeaa90 in slot_tp_finalize Objects/typeobject.c:10874
    #8 0x58b4baa5f270 in PyObject_CallFinalizer Objects/object.c:585
    #9 0x58b4baa5f270 in PyObject_CallFinalizerFromDealloc Objects/object.c:603
    #10 0x58b4baad12d0 in subtype_dealloc Objects/typeobject.c:2774
    #11 0x58b4baa5d1d8 in _Py_Dealloc Objects/object.c:3200
    #12 0x58b4ba7f5e28 in Py_DECREF_MORTAL Include/internal/pycore_object.h:482
    #13 0x58b4ba7f5e28 in PyStackRef_CLOSE Include/internal/pycore_stackref.h:707
    #14 0x58b4ba7f5e28 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:10110
    #15 0x58b4bacbead6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #16 0x58b4bacbead6 in _PyEval_Vector Python/ceval.c:2001
    #17 0x58b4bacbead6 in PyEval_EvalCode Python/ceval.c:884
    #18 0x58b4bae0416e in run_eval_code_obj Python/pythonrun.c:1365
    #19 0x58b4bae0416e in run_mod Python/pythonrun.c:1459
    #20 0x58b4bae08e17 in pyrun_file Python/pythonrun.c:1293
    #21 0x58b4bae08e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #22 0x58b4bae0993c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #23 0x58b4bae7ce3c in pymain_run_file_obj Modules/main.c:410
    #24 0x58b4bae7ce3c in pymain_run_file Modules/main.c:429
    #25 0x58b4bae7ce3c in pymain_run_python Modules/main.c:691
    #26 0x58b4bae7e71e in Py_RunMain Modules/main.c:772
    #27 0x58b4bae7e71e in pymain_main Modules/main.c:802
    #28 0x58b4bae7e71e in Py_BytesMain Modules/main.c:826
    #29 0x7b090a02a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #30 0x7b090a02a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #31 0x58b4ba818634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

0x5080000234b8 is located 24 bytes inside of 96-byte region [0x5080000234a0,0x508000023500)
freed by thread T0 here:
    #0 0x7b090a4fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
    #1 0x58b4bafdcadf in partial_dealloc Modules/_functoolsmodule.c:350
    #2 0x58b4baa5d1d8 in _Py_Dealloc Objects/object.c:3200
    #3 0x58b4bad2b493 in Py_DECREF Include/refcount.h:418
    #4 0x58b4bad2b493 in format_unraisable_v Python/errors.c:1730
    #5 0x58b4bad2cd04 in PyErr_FormatUnraisable Python/errors.c:1769
    #6 0x58b4baaeaa90 in slot_tp_finalize Objects/typeobject.c:10874
    #7 0x58b4baa5f270 in PyObject_CallFinalizer Objects/object.c:585
    #8 0x58b4baa5f270 in PyObject_CallFinalizerFromDealloc Objects/object.c:603
    #9 0x58b4baad12d0 in subtype_dealloc Objects/typeobject.c:2774
    #10 0x58b4baa5d1d8 in _Py_Dealloc Objects/object.c:3200
    #11 0x58b4ba7f5e28 in Py_DECREF_MORTAL Include/internal/pycore_object.h:482
    #12 0x58b4ba7f5e28 in PyStackRef_CLOSE Include/internal/pycore_stackref.h:707
    #13 0x58b4ba7f5e28 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:10110
    #14 0x58b4bacbead6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #15 0x58b4bacbead6 in _PyEval_Vector Python/ceval.c:2001
    #16 0x58b4bacbead6 in PyEval_EvalCode Python/ceval.c:884
    #17 0x58b4bae0416e in run_eval_code_obj Python/pythonrun.c:1365
    #18 0x58b4bae0416e in run_mod Python/pythonrun.c:1459
    #19 0x58b4bae08e17 in pyrun_file Python/pythonrun.c:1293
    #20 0x58b4bae08e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #21 0x58b4bae0993c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #22 0x58b4bae7ce3c in pymain_run_file_obj Modules/main.c:410
    #23 0x58b4bae7ce3c in pymain_run_file Modules/main.c:429
    #24 0x58b4bae7ce3c in pymain_run_python Modules/main.c:691
    #25 0x58b4bae7e71e in Py_RunMain Modules/main.c:772
    #26 0x58b4bae7e71e in pymain_main Modules/main.c:802
    #27 0x58b4bae7e71e in Py_BytesMain Modules/main.c:826
    #28 0x7b090a02a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #29 0x7b090a02a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #30 0x58b4ba818634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

previously allocated by thread T0 here:
    #0 0x7b090a4fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x58b4baae588e in _PyObject_MallocWithType Include/internal/pycore_object_alloc.h:46
    #2 0x58b4baae588e in _PyType_AllocNoTrack Objects/typeobject.c:2504
    #3 0x58b4baae5af4 in PyType_GenericAlloc Objects/typeobject.c:2535
    #4 0x58b4bafd9952 in partial_new Modules/_functoolsmodule.c:226
    #5 0x58b4baadd118 in type_call Objects/typeobject.c:2448
    #6 0x58b4ba93e9cd in _PyObject_MakeTpCall Objects/call.c:242
    #7 0x58b4ba7f45a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #8 0x58b4bacbead6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #9 0x58b4bacbead6 in _PyEval_Vector Python/ceval.c:2001
    #10 0x58b4bacbead6 in PyEval_EvalCode Python/ceval.c:884
    #11 0x58b4bae0416e in run_eval_code_obj Python/pythonrun.c:1365
    #12 0x58b4bae0416e in run_mod Python/pythonrun.c:1459
    #13 0x58b4bae08e17 in pyrun_file Python/pythonrun.c:1293
    #14 0x58b4bae08e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #15 0x58b4bae0993c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #16 0x58b4bae7ce3c in pymain_run_file_obj Modules/main.c:410
    #17 0x58b4bae7ce3c in pymain_run_file Modules/main.c:429
    #18 0x58b4bae7ce3c in pymain_run_python Modules/main.c:691
    #19 0x58b4bae7e71e in Py_RunMain Modules/main.c:772
    #20 0x58b4bae7e71e in pymain_main Modules/main.c:802
    #21 0x58b4bae7e71e in Py_BytesMain Modules/main.c:826
    #22 0x7b090a02a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #23 0x7b090a02a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #24 0x58b4ba818634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

SUMMARY: AddressSanitizer: heap-use-after-free Include/object.h:277 in _Py_TYPE
Shadow bytes around the buggy address:
  0x508000023200: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
  0x508000023280: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 00
  0x508000023300: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 00
  0x508000023380: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 03 fa
  0x508000023400: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd
=>0x508000023480: fa fa fa fa fd fd fd[fd]fd fd fd fd fd fd fd fd
  0x508000023500: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
  0x508000023580: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fa
  0x508000023600: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
  0x508000023680: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
  0x508000023700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==71182==ABORTING

CPython versions tested on:

Details
Python Version Status Exit Code
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 28 2025, 16:51:20) ASAN 1
Python 3.10.19+ (heads/3.10:014261980b1, Oct 28 2025, 16:52:08) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.11.14+ (heads/3.11:88f3f5b5f11, Oct 28 2025, 16:53:08) [Clang 18.1.3 (1ubuntu1)] ASAN 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

Activity

  1. added
    type-crashA hard crash of the interpreter, possibly with a core dump
    on Jan 8, 2026
  2. VanshAgarwal24036 commented on Jan 8, 2026

    @VanshAgarwal24036
    Contributor

    Thanks for the detailed report.

    I’m analyzing the code paths around sys.unraisablehook fallback handling.
    From a first look, it seems the fallback logger may access an object
    after its refcount has reached zero when the hook raises and re-enters.

    I’ll continue digging into whether we need to INCREF before reuse or
    avoid reusing the same pointer entirely.

  3. added a commit that references this issue on Jan 8, 2026
  4. vstinner commented on Jan 8, 2026

    @vstinner
    Member

    I wrote #143557 to fix this bug.

  5. VanshAgarwal24036 commented on Jan 8, 2026

    @VanshAgarwal24036
    Contributor

    Thanks for the fix!

    I had been analyzing the same issue locally and reached a similar
    conclusion around holding a strong reference during fallback handling.
    Appreciate you addressing it so quickly.

  6. added 3 commits that reference this issue on Jan 9, 2026
  7. added a commit that references this issue on Jan 9, 2026
  8. vstinner commented on Jan 9, 2026

    @vstinner
    Member

    Fixed. Thanks for your bug report.

  9. added a commit that references this issue on Jan 9, 2026
  10. added a commit that references this issue on Jan 12, 2026
  11. added a commit that references this issue on Jan 16, 2026
  12. added a commit that references this issue on Feb 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirtype-crashA hard crash of the interpreter, possibly with a core dump

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions