Skip to content

Null pointer dereference in element_[ass_]subscr via re-entrant calls #143200

Closed

Description

@jackfromeast

What happened?

A crafted __index__ invoked by root[bad_index:] clears the element so element_ass_subscr frees self->extra, but the slice handling continues and calls PySlice_AdjustIndices on the dangling child store, dereferencing NULL and crashing the interpreter.

Proof of Concept:

import xml.etree.ElementTree as ET

root = ET.Element("root")
root.extend([ET.Element("c0"), ET.Element("c1")])

class Evil:
    def __index__(self):
        # Re-entrant mutation: clear() frees the internal child storage
        global root
        root.clear()
        return 0

bad_index = Evil()
root[bad_index:]
Affected Versions
Python Version Status Exit Code
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 27 2025, 21:34:13) OK 0
Python 3.10.19+ (heads/3.10:014261980b1, Oct 27 2025, 21:19:00) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.11.14+ (heads/3.11:88f3f5b5f11, Oct 27 2025, 21:20:35) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.12.12+ (heads/3.12:8cb2092bd8c, Oct 27 2025, 21:27:07) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.13.9+ (heads/3.13:9c8eade20c6, Oct 27 2025, 21:28:49) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.14.0+ (heads/3.14:2e216728038, Oct 27 2025, 21:30:55) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 27 2025, 21:32:37) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Vulnerable Code
static PyObject *
element_subscr(PyObject *op, PyObject *item)
{
    ElementObject *self = _Element_CAST(op);

    if (PyIndex_Check(item)) {
        Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError);

        if (i == -1 && PyErr_Occurred()) {
            return NULL;
        }
        if (i < 0 && self->extra)
            i += self->extra->length;
        return element_getitem(op, i);
    }
    else if (PySlice_Check(item)) {
        Py_ssize_t start, stop, step, slicelen, i;
        size_t cur;
        PyObject* list;

        if (!self->extra)
            return PyList_New(0);

        /* Call _PyEval_SliceIndex -> _PyNumber_Index and reentrant to the interpreter again. */
        if (PySlice_Unpack(item, &start, &stop, &step) < 0) {
            return NULL;
        }
        /* ... */

        // Crash: self->extra is a null pointer now
        slicelen = PySlice_AdjustIndices(self->extra->length, &start, &stop,
                                         step);
}
Sanitizer Output
AddressSanitizer:DEADLYSIGNAL
=================================================================
==11783==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000008 (pc 0x7638bd19b2a1 bp 0x7ffd815de790 sp 0x7ffd815de690 T0)
==11783==The signal is caused by a READ memory access.
==11783==Hint: address points to the zero page.
    #0 0x7638bd19b2a1 in element_subscr Modules/_elementtree.c:1819
    #1 0x7638bd19b2a1 in element_subscr Modules/_elementtree.c:1794
    #2 0x5f64666f687c in PyObject_GetItem Objects/abstract.c:163
    #3 0x5f64665ec668 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1143
    #4 0x5f6466ac4ad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #5 0x5f6466ac4ad6 in _PyEval_Vector Python/ceval.c:2001
    #6 0x5f6466ac4ad6 in PyEval_EvalCode Python/ceval.c:884
    #7 0x5f6466c0a16e in run_eval_code_obj Python/pythonrun.c:1365
    #8 0x5f6466c0a16e in run_mod Python/pythonrun.c:1459
    #9 0x5f6466c0ee17 in pyrun_file Python/pythonrun.c:1293
    #10 0x5f6466c0ee17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #11 0x5f6466c0f93c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #12 0x5f6466c82e3c in pymain_run_file_obj Modules/main.c:410
    #13 0x5f6466c82e3c in pymain_run_file Modules/main.c:429
    #14 0x5f6466c82e3c in pymain_run_python Modules/main.c:691
    #15 0x5f6466c8471e in Py_RunMain Modules/main.c:772
    #16 0x5f6466c8471e in pymain_main Modules/main.c:802
    #17 0x5f6466c8471e in Py_BytesMain Modules/main.c:826
    #18 0x7638bde2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #19 0x7638bde2a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #20 0x5f646661e634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV Modules/_elementtree.c:1819 in element_subscr
==11783==ABORTING
[Inferior 1 (process 11783) exited with code 01]

Linked PRs

Activity

  1. jackfromeast commented on Dec 26, 2025

    @jackfromeast
    Author

    Similar to #143199, but with a different reentrant call.

  2. self-assigned this
    on Dec 26, 2025
  3. added
    type-crashA hard crash of the interpreter, possibly with a core dump
    and removed
    type-crashA hard crash of the interpreter, possibly with a core dump
    on Dec 26, 2025
  4. picnixz commented on Dec 26, 2025

    @picnixz
    Member

    I'm going to close #143199 as a duplicate. In the future, if the affected function is the same, just create a single issue instead of many. Also, please use the "crash" template for reporting those issues. TiA!

  5. changed the title [-]Null pointer dereference in `element_ass_subscr` via re-entrant `__index__`[/-] [+]Null pointer dereference in `element_ass_subscr` via re-entrant calls[/+] on Dec 26, 2025
  6. added
    type-crashA hard crash of the interpreter, possibly with a core dump
    on Dec 26, 2025
  7. jackfromeast commented on Dec 26, 2025

    @jackfromeast
    Author

    Thanks for the explanation! I was using the reentrant call location to deduplicate. Next time, I will use their living functions instead.

  8. picnixz commented on Dec 26, 2025

    @picnixz
    Member

    You can include the different places where there is an issue, but it's easier to have fewer issues for triaging.

  9. superboy-zjc commented on Dec 27, 2025

    @superboy-zjc
    Contributor

    @picnixz hi there, this is actually not duplicated of #143199 as the affected function here should be element_subscr()(slice handling), not the one discussed in this issue.

    Here is my proposed fix by adding a NULL pointer check after the possible re-entrant operation.

    static PyObject *
    element_subscr(PyObject *op, PyObject *item)
    {
        // ... (existing code) ...
        else if (PySlice_Check(item)) {
            // ...
            if (!self->extra)
                return PyList_New(0);
    
            if (PySlice_Unpack(item, &start, &stop, &step) < 0) {
                return NULL;
            }
    
            // fix added here
            if (!self->extra) {
                /* The slice argument evaluation (via __index__) may have cleared 
                   the element (re-entrancy). If so, the element is now empty. */
                return PyList_New(0);
            }
    
            slicelen = PySlice_AdjustIndices(self->extra->length, &start, &stop,
                                             step);
            // ...
    

    I’m new to contributing to CPython but happy to work on the PR + a regression test if this direction makes sense. Please let me know what you’d like to see.

  10. picnixz commented on Dec 27, 2025

    @picnixz
    Member

    For XML UAFs I do not think it is an easy issue for beginners because there is the Python implementation but thanks for pinpointing the exact cause. I will take care of this myself as I already fixed UAFs for XML in the past.

    EDIT: Even if they are not clear duplicate, I'll just consider them under the same issue (that is, __setitem__/__getitem__).

  11. changed the title [-]Null pointer dereference in `element_ass_subscr` via re-entrant calls[/-] [+]Null pointer dereference in `element_[ass]_subscr` via re-entrant calls[/+] on Dec 27, 2025
  12. changed the title [-]Null pointer dereference in `element_[ass]_subscr` via re-entrant calls[/-] [+]Null pointer dereference in `element_[ass_]subscr` via re-entrant calls[/+] on Dec 27, 2025
  13. jackfromeast commented on Dec 27, 2025

    @jackfromeast
    Author

    Thank you, @superboy-zjc, for pointing out the issue! The original attached code snippets and sanitizer report do not belong to this PoC, and I have just updated both of them :>

    Also, thank you, @picnixz, for helping update the title and patch the issue.

  14. added 3 commits that reference this issue on Dec 27, 2025
  15. picnixz commented on Dec 27, 2025

    @picnixz
    Member

    To summarize:

  16. added a commit that references this issue on Dec 29, 2025
  17. added 2 commits that reference this issue on Dec 29, 2025
  18. added a commit that references this issue on Dec 29, 2025
  19. picnixz commented on Dec 29, 2025

    @picnixz
    Member

    Thanks for the report!

  20. added a commit that references this issue on Dec 29, 2025
  21. 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

extension-modulesC modules in the Modules dirtopic-XMLtype-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