Skip to content

Heap buffer overflow in _format_TracebackException when TracebackException.format omits newline #143377

Closed

Description

@jackfromeast

What happened?

_format_TracebackException assumes the TracebackException.format result is a newline-terminated buffer and truncates it in place, so a crafted override that returns an empty sequence makes the code treat a one-byte allocation as if it held a trailing \n, writing past the buffer and turning the mistaken type expectation into a heap buffer overflow.

Proof of Concept:

import traceback
import _interpreters

orig_format = traceback.TracebackException.format

def empty_format(self):
    return []

traceback.TracebackException.format = empty_format

try:
    raise ValueError("boom")
except Exception as exc:
    _interpreters.capture_exception(exc)
finally:
    traceback.TracebackException.format = orig_format

Vulnerable Code Snippet:

Click to expand
/* Buggy Re-entrant Path */
static PyObject *
_interpreters_capture_exception_impl(PyObject *module, PyObject *exc_arg)
{
    PyObject *exc = exc_arg;
    /* ... */
    _PyXI_excinfo *info = _PyXI_NewExcInfo(exc);
    if (info == NULL) {
        goto finally;
    }
    /* ... */
    PyObject *formatted = _PyXI_FormatExcInfo(info);
    if (formatted == NULL) {
        Py_CLEAR(captured);
        goto finally;
    }
    /* ... */
    return captured;
}

_PyXI_excinfo *
_PyXI_NewExcInfo(PyObject *exc)
{
    _PyXI_excinfo *info = PyMem_RawCalloc(1, sizeof(_PyXI_excinfo));
    /* ... */
    if (PyExceptionInstance_Check(exc) || PyExceptionClass_Check(exc)) {
        failure = _PyXI_excinfo_InitFromException(info, exc);
    }
    /* ... */
    return info;
}

static const char *
_PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
{
    /* ... */
    info->errdisplay = _format_TracebackException(tbexc);
    /* ... */
    return NULL;
}

static const char *
_format_TracebackException(PyObject *tbexc)
{
    PyObject *lines = PyObject_CallMethod(tbexc, "format", NULL);  /* Reentrant call site */
    if (lines == NULL) {
        return NULL;
    }
    assert(_Py_EMPTY_STR != NULL);
    PyObject *formatted_obj = PyUnicode_Join(_Py_EMPTY_STR, lines);
    Py_DECREF(lines);
    if (formatted_obj == NULL) {
        return NULL;
    }

    Py_ssize_t size = -1;
    const char *formatted = _copy_string_obj_raw(formatted_obj, &size);  /* crashing pointer derived */
    Py_DECREF(formatted_obj);
    // Assume the formatted ends with \n while we can return an empty object
    // We remove trailing the newline added by TracebackException.format().
    assert(formatted[size-1] == '\n');
    ((char *)formatted)[size-1] = '\0';  /* Crash site */
    return formatted;
}

/* Clobbering Path */
static const char *
_copy_string_obj_raw(PyObject *strobj, Py_ssize_t *p_size)
{
    Py_ssize_t size = -1;
    const char *str = PyUnicode_AsUTF8AndSize(strobj, &size);
    if (str == NULL) {
        return NULL;
    }
    /* ... */
    char *copied = PyMem_RawMalloc(size+1);
    if (copied == NULL) {
        PyErr_NoMemory();
        return NULL;
    }
    strcpy(copied, str);
    if (p_size != NULL) {
        *p_size = size;  /* state mutate site */
    }
    return copied;
}

Sanitizer Output:

Click to expand
=================================================================
==2203897==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x50200002df2f at pc 0x627ab61ef34d bp 0x7fffb6056900 sp 0x7fffb60568f0
WRITE of size 1 at 0x50200002df2f thread T0
    #0 0x627ab61ef34c in _format_TracebackException Python/crossinterp.c:1146
    #1 0x627ab61ef34c in _PyXI_excinfo_InitFromException Python/crossinterp.c:1407
    #2 0x627ab61f7dea in _PyXI_NewExcInfo Python/crossinterp.c:1649
    #3 0x73d11eb12510 in _interpreters_capture_exception_impl Modules/_interpretersmodule.c:1522
    #4 0x73d11eb12510 in _interpreters_capture_exception Modules/clinic/_interpretersmodule.c.h:1196
    #5 0x627ab5e1e3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #6 0x627ab5e1e3e7 in PyObject_Vectorcall Objects/call.c:327
    #7 0x627ab5cd25a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #8 0x627ab619cad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #9 0x627ab619cad6 in _PyEval_Vector Python/ceval.c:2001
    #10 0x627ab619cad6 in PyEval_EvalCode Python/ceval.c:884
    #11 0x627ab62e216e in run_eval_code_obj Python/pythonrun.c:1365
    #12 0x627ab62e216e in run_mod Python/pythonrun.c:1459
    #13 0x627ab62e6e17 in pyrun_file Python/pythonrun.c:1293
    #14 0x627ab62e6e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #15 0x627ab62e793c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #16 0x627ab635ae3c in pymain_run_file_obj Modules/main.c:410
    #17 0x627ab635ae3c in pymain_run_file Modules/main.c:429
    #18 0x627ab635ae3c in pymain_run_python Modules/main.c:691
    #19 0x627ab635c71e in Py_RunMain Modules/main.c:772
    #20 0x627ab635c71e in pymain_main Modules/main.c:802
    #21 0x627ab635c71e in Py_BytesMain Modules/main.c:826
    #22 0x73d11f82a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #23 0x73d11f82a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #24 0x627ab5cf6634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

0x50200002df2f is located 1 bytes before 1-byte region [0x50200002df30,0x50200002df31)
allocated by thread T0 here:
    #0 0x73d11fcfd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x627ab61ee6f9 in _copy_string_obj_raw Python/crossinterp.c:1055
    #2 0x627ab61ee6f9 in _format_TracebackException Python/crossinterp.c:1142
    #3 0x627ab61ee6f9 in _PyXI_excinfo_InitFromException Python/crossinterp.c:1407
    #4 0x627ab61f7dea in _PyXI_NewExcInfo Python/crossinterp.c:1649
    #5 0x73d11eb12510 in _interpreters_capture_exception_impl Modules/_interpretersmodule.c:1522
    #6 0x73d11eb12510 in _interpreters_capture_exception Modules/clinic/_interpretersmodule.c.h:1196
    #7 0x627ab5e1e3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #8 0x627ab5e1e3e7 in PyObject_Vectorcall Objects/call.c:327
    #9 0x627ab5cd25a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #10 0x627ab619cad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #11 0x627ab619cad6 in _PyEval_Vector Python/ceval.c:2001
    #12 0x627ab619cad6 in PyEval_EvalCode Python/ceval.c:884
    #13 0x627ab62e216e in run_eval_code_obj Python/pythonrun.c:1365
    #14 0x627ab62e216e in run_mod Python/pythonrun.c:1459
    #15 0x627ab62e6e17 in pyrun_file Python/pythonrun.c:1293
    #16 0x627ab62e6e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #17 0x627ab62e793c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #18 0x627ab635ae3c in pymain_run_file_obj Modules/main.c:410
    #19 0x627ab635ae3c in pymain_run_file Modules/main.c:429
    #20 0x627ab635ae3c in pymain_run_python Modules/main.c:691
    #21 0x627ab635c71e in Py_RunMain Modules/main.c:772
    #22 0x627ab635c71e in pymain_main Modules/main.c:802
    #23 0x627ab635c71e in Py_BytesMain Modules/main.c:826
    #24 0x73d11f82a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #25 0x73d11f82a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #26 0x627ab5cf6634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

SUMMARY: AddressSanitizer: heap-buffer-overflow Python/crossinterp.c:1146 in _format_TracebackException
Shadow bytes around the buggy address:
  0x50200002dc80: fa fa fd fa fa fa fd fd fa fa fd fd fa fa fd fa
  0x50200002dd00: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
  0x50200002dd80: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
  0x50200002de00: fa fa fd fa fa fa fd fa fa fa 00 00 fa fa 00 03
  0x50200002de80: fa fa 00 03 fa fa 00 01 fa fa 05 fa fa fa fd fa
=>0x50200002df00: fa fa fd fa fa[fa]01 fa fa fa fa fa fa fa fa fa
  0x50200002df80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50200002e000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50200002e080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50200002e100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50200002e180: 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
==2203897==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) 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

Activity

  1. self-assigned this
    on Jan 4, 2026
  2. added 5 commits that reference this issue on Jan 4, 2026
  3. added
    interpreter-core(Objects, Python, Grammar, and Parser dirs)
    and removed on Jan 4, 2026
  4. picnixz commented on Jan 4, 2026

    @picnixz
    Member

    There was another possible issue when formatted couldn't be allocated, in which case we'll get a similar crash.

  5. added 2 commits that reference this issue on Jan 4, 2026
  6. added a commit that references this issue on Jan 10, 2026
  7. added 2 commits that reference this issue on Jan 10, 2026
  8. picnixz commented on Jan 10, 2026

    @picnixz
    Member

    Thank you for the report!

  9. added a commit that references this issue on Jan 10, 2026
  10. added a commit that references this issue on Jan 12, 2026
  11. 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

Labels

interpreter-core(Objects, Python, Grammar, and Parser dirs)topic-subinterpreterstype-crashA hard crash of the interpreter, possibly with a core dump

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions