Skip to content

Use-after-free in _json.raise_errmsg via re-entrant JSONDecodeError hook #143544

@jackfromeast

Description

@jackfromeast

What happened?

raise_errmsg calls a user-replaceable json.JSONDecodeError object, immediately drops its reference, and then reuses that pointer in PyErr_SetObject. A crafted JSONDecodeError.__call__ deletes the attribute while it runs, so the DECREF frees the object and the subsequent PyErr_SetObject dereferences freed memory, producing a UAF during JSON parsing.

Proof of Concept:

import json

class Trigger:
    def __call__(self, *args):
        import json as mod
        for target in (mod.decoder, mod):
            if getattr(target, "JSONDecodeError", None) is self:
                delattr(target, "JSONDecodeError")
        return ValueError()

hook = Trigger()
json.decoder.JSONDecodeError = hook
json.JSONDecodeError = hook
del hook

json.loads('"\\uZZZZ"')

Vulnerable Code Snippet:

Click to expand
/* Buggy Re-entrant Path */
static PyObject *
scanner_call(PyObject *self, PyObject *args, PyObject *kwds)
{
    PyObject *pystr;
    PyObject *rval;
    /* ... */
    rval = scan_once_unicode(PyScannerObject_CAST(self), memo, pystr, idx, &next_idx);
    /* ... */
    return _build_rval_index_tuple(rval, next_idx);
}

static void
raise_errmsg(const char *msg, PyObject *s, Py_ssize_t end)
{
    _Py_DECLARE_STR(json_decoder, "json.decoder");
    PyObject *JSONDecodeError =
         PyImport_ImportModuleAttr(&_Py_STR(json_decoder), &_Py_ID(JSONDecodeError));  /* crashing pointer derived */
    if (JSONDecodeError == NULL) {
        return;
    }

    PyObject *exc;
    exc = PyObject_CallFunction(JSONDecodeError, "zOn", msg, s, end);  /* Reentrant call site */

    /* Clobbering Path */
    Py_DECREF(JSONDecodeError);  /* state mutate site */

    if (exc) {
        PyErr_SetObject(JSONDecodeError, exc);  /* Crash site */
        Py_DECREF(exc);
    }
}

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

Sanitizer Output:

Click to expand
=================================================================
==286847==ERROR: AddressSanitizer: heap-use-after-free on address 0x513000035d68 at pc 0x5c8f538957e0 bp 0x7ffde4c03c60 sp 0x7ffde4c03c50
READ of size 8 at 0x513000035d68 thread T0
    #0 0x5c8f538957df in _Py_TYPE Include/object.h:277
    #1 0x5c8f538957df in PyType_Check Include/object.h:813
    #2 0x5c8f538957df in _PyErr_SetObject Python/errors.c:157
    #3 0x5c8f538957df in PyErr_SetObject Python/errors.c:246
    #4 0x722fd510a335 in raise_errmsg Modules/_json.c:422
    #5 0x722fd510c4b9 in scanstring_unicode Modules/_json.c:615
    #6 0x722fd510f735 in scan_once_unicode Modules/_json.c:1143
    #7 0x722fd5113e50 in scanner_call Modules/_json.c:1244
    #8 0x5c8f534ae9cd in _PyObject_MakeTpCall Objects/call.c:242
    #9 0x5c8f533645a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #10 0x5c8f5382ead6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #11 0x5c8f5382ead6 in _PyEval_Vector Python/ceval.c:2001
    #12 0x5c8f5382ead6 in PyEval_EvalCode Python/ceval.c:884
    #13 0x5c8f5397416e in run_eval_code_obj Python/pythonrun.c:1365
    #14 0x5c8f5397416e in run_mod Python/pythonrun.c:1459
    #15 0x5c8f53978e17 in pyrun_file Python/pythonrun.c:1293
    #16 0x5c8f53978e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #17 0x5c8f5397993c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #18 0x5c8f539ece3c in pymain_run_file_obj Modules/main.c:410
    #19 0x5c8f539ece3c in pymain_run_file Modules/main.c:429
    #20 0x5c8f539ece3c in pymain_run_python Modules/main.c:691
    #21 0x5c8f539ee71e in Py_RunMain Modules/main.c:772
    #22 0x5c8f539ee71e in pymain_main Modules/main.c:802
    #23 0x5c8f539ee71e in Py_BytesMain Modules/main.c:826
    #24 0x722fd562a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #25 0x722fd562a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #26 0x5c8f53388634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

0x513000035d68 is located 40 bytes inside of 328-byte region [0x513000035d40,0x513000035e88)
freed by thread T0 here:
    #0 0x722fd5afc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
    #1 0x5c8f536418f3 in subtype_dealloc Objects/typeobject.c:2852
    #2 0x5c8f535cd1d8 in _Py_Dealloc Objects/object.c:3200
    #3 0x722fd510a377 in Py_DECREF Include/refcount.h:418
    #4 0x722fd510a377 in raise_errmsg Modules/_json.c:420
    #5 0x722fd510c4b9 in scanstring_unicode Modules/_json.c:615
    #6 0x722fd510f735 in scan_once_unicode Modules/_json.c:1143
    #7 0x722fd5113e50 in scanner_call Modules/_json.c:1244
    #8 0x5c8f534ae9cd in _PyObject_MakeTpCall Objects/call.c:242
    #9 0x5c8f533645a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #10 0x5c8f5382ead6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #11 0x5c8f5382ead6 in _PyEval_Vector Python/ceval.c:2001
    #12 0x5c8f5382ead6 in PyEval_EvalCode Python/ceval.c:884
    #13 0x5c8f5397416e in run_eval_code_obj Python/pythonrun.c:1365
    #14 0x5c8f5397416e in run_mod Python/pythonrun.c:1459
    #15 0x5c8f53978e17 in pyrun_file Python/pythonrun.c:1293
    #16 0x5c8f53978e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #17 0x5c8f5397993c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #18 0x5c8f539ece3c in pymain_run_file_obj Modules/main.c:410
    #19 0x5c8f539ece3c in pymain_run_file Modules/main.c:429
    #20 0x5c8f539ece3c in pymain_run_python Modules/main.c:691
    #21 0x5c8f539ee71e in Py_RunMain Modules/main.c:772
    #22 0x5c8f539ee71e in pymain_main Modules/main.c:802
    #23 0x5c8f539ee71e in Py_BytesMain Modules/main.c:826
    #24 0x722fd562a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #25 0x722fd562a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #26 0x5c8f53388634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

previously allocated by thread T0 here:
    #0 0x722fd5afd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x5c8f5365588e in _PyObject_MallocWithType Include/internal/pycore_object_alloc.h:46
    #2 0x5c8f5365588e in _PyType_AllocNoTrack Objects/typeobject.c:2504
    #3 0x5c8f53655af4 in PyType_GenericAlloc Objects/typeobject.c:2535
    #4 0x5c8f5364d118 in type_call Objects/typeobject.c:2448
    #5 0x5c8f534ae9cd in _PyObject_MakeTpCall Objects/call.c:242
    #6 0x5c8f5336cf33 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #7 0x5c8f5382ead6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #8 0x5c8f5382ead6 in _PyEval_Vector Python/ceval.c:2001
    #9 0x5c8f5382ead6 in PyEval_EvalCode Python/ceval.c:884
    #10 0x5c8f5397416e in run_eval_code_obj Python/pythonrun.c:1365
    #11 0x5c8f5397416e in run_mod Python/pythonrun.c:1459
    #12 0x5c8f53978e17 in pyrun_file Python/pythonrun.c:1293
    #13 0x5c8f53978e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #14 0x5c8f5397993c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #15 0x5c8f539ece3c in pymain_run_file_obj Modules/main.c:410
    #16 0x5c8f539ece3c in pymain_run_file Modules/main.c:429
    #17 0x5c8f539ece3c in pymain_run_python Modules/main.c:691
    #18 0x5c8f539ee71e in Py_RunMain Modules/main.c:772
    #19 0x5c8f539ee71e in pymain_main Modules/main.c:802
    #20 0x5c8f539ee71e in Py_BytesMain Modules/main.c:826
    #21 0x722fd562a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #22 0x722fd562a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #23 0x5c8f53388634 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:
  0x513000035a80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x513000035b00: 00 fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x513000035b80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x513000035c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x513000035c80: 00 00 00 00 00 00 00 00 00 fa fa fa fa fa fa fa
=>0x513000035d00: fa fa fa fa fa fa fa fa fd fd fd fd fd[fd]fd fd
  0x513000035d80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x513000035e00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x513000035e80: fd fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x513000035f00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x513000035f80: 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
==286847==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)] 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

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

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions