Skip to content

Null pointer dereference in element_ass_subscr via re-entrant slice assignment #143199

@jackfromeast

Description

@jackfromeast

What happened?

A crafted iterable for root[:] = Evil() clears the element during PySequence_Fast, so element_ass_subscr frees self->extra and then immediately iterates that null child table in the recycle loop, dereferencing NULL while populating PyList_SET_ITEM.

Proof of Concept:

import xml.etree.ElementTree as ET

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

class Evil:
    def __iter__(self):
        yield from self._gen()

    def _gen(self):
        root.clear()
        yield ET.Element('new')

root[:] = Evil()
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 int
element_ass_subscr(PyObject *op, PyObject *item, PyObject *value)
{
    /* ... */
    /* A new slice is actually being assigned */
    // Bug: Invoke the __iter__ methods which clear the element tree
    seq = PySequence_Fast(value, "assignment expects an iterable");
    if (!seq) {
        return -1;
    }
    newlen = PySequence_Fast_GET_SIZE(seq);

    if (step !=  1 && newlen != slicelen)
    {
        Py_DECREF(seq);
        PyErr_Format(PyExc_ValueError,
            "attempt to assign sequence of size %zd "
            "to extended slice of size %zd",
            newlen, slicelen
            );
        return -1;
    }

    /* Resize before creating the recycle bin, to prevent refleaks. */
    if (newlen > slicelen) {
        if (element_resize(self, newlen - slicelen) < 0) {
            Py_DECREF(seq);
            return -1;
        }
    }

    PyTypeObject *tp = Py_TYPE(self);
    elementtreestate *st = get_elementtree_state_by_type(tp);
    for (i = 0; i < newlen; i++) {
        PyObject *element = PySequence_Fast_GET_ITEM(seq, i);
        if (!Element_Check(st, element)) {
            raise_type_error(element);
            Py_DECREF(seq);
            return -1;
        }
    }

    if (slicelen > 0) {
        /* to avoid recursive calls to this method (via decref), move
            old items to the recycle bin here, and get rid of them when
            we're done modifying the element */
        recycle = PyList_New(slicelen);
        if (!recycle) {
            Py_DECREF(seq);
            return -1;
        }
        for (cur = start, i = 0; i < slicelen;
                cur += step, i++)
            // Crash: self->extra is null ptr right now
            PyList_SET_ITEM(recycle, i, self->extra->children[cur]);
    }
    /* ... */
Sanitizer Output
=================================================================
==1358729==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000018 (pc 0x7ec7aa3f4a34 bp 0x7ffc814b7630 sp 0x7ffc814b7540 T0)
==1358729==The signal is caused by a READ memory access.
==1358729==Hint: address points to the zero page.
    #0 0x7ec7aa3f4a34 in element_ass_subscr /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/./Modules/_elementtree.c:1992:17
    #1 0x5eb4609c0d68 in _PyEval_EvalFrameDefault /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/generated_cases.c.h:11245:27
    #2 0x5eb4609b23bb in _PyEval_EvalFrame /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/./Include/internal/pycore_ceval.h:121:16
    #3 0x5eb4609b23bb in _PyEval_Vector /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/ceval.c:2005:12
    #4 0x5eb4609b23bb in PyEval_EvalCode /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/ceval.c:888:21
    #5 0x5eb460b09370 in run_eval_code_obj /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:1365:12
    #6 0x5eb460b09370 in run_mod /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:1459:19
    #7 0x5eb460b0343c in pyrun_file /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:1293:15
    #8 0x5eb460b0343c in _PyRun_SimpleFileObject /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:521:13
    #9 0x5eb460b02b05 in _PyRun_AnyFileObject /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:81:15
    #10 0x5eb460b6afe5 in pymain_run_file_obj /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:410:15
    #11 0x5eb460b6afe5 in pymain_run_file /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:429:15
    #12 0x5eb460b6999d in pymain_run_python /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:691:21
    #13 0x5eb460b6999d in Py_RunMain /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:772:5
    #14 0x5eb460b6a451 in pymain_main /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:802:12
    #15 0x5eb460b6a5c3 in Py_BytesMain /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:826:12
    #16 0x7ec7ac62a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #17 0x7ec7ac62a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #18 0x5eb46051a104 in _start (/home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/python+0x1c7104) (BuildId: 5de9d2fcbcd44bfc1b0fe256566d49ad35ca1d56)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/./Modules/_elementtree.c:1992:17 in element_ass_subscr
==1358729==ABORTING

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions