Skip to content

Use-after-free in memoryview slicing via re-entrant __index__ #142665

@jackfromeast

Description

@jackfromeast

What happened?

memory_subscript() creates a new view (mbuf_add_view) before parsing slice indices. If a slice bound’s __index__ releases/truncates the underlying buffer (e.g., mv.release(); ftruncate()), the newly created subview keeps a dangling pointer. Subsequent access (e.g., sub[0]) dereferences freed/invalid memory, crashing in unpack_single.

Proof of Concept:

import mmap, os, tempfile

fd, path = tempfile.mkstemp()
os.write(fd, b"A" * 4096)
mm = mmap.mmap(fd, 4096, access=mmap.ACCESS_WRITE)
mv = memoryview(mm)

class Trigger:
    def __index__(self):
        mv.release()
        os.ftruncate(fd, 0)
        return 0

try:
    sub = mv[slice(Trigger(), None, None)]
    sub[0] # Trigger the slicing to call __index__
finally:
    mm.close()
    os.close(fd)
    os.unlink(path)

Related Code Snippet

static PyObject *
memory_subscript(PyObject *_self, PyObject *key)
{
    PyMemoryViewObject *self = (PyMemoryViewObject *)_self;
    Py_buffer *view;
    view = &(self->view);

    CHECK_RELEASED(self);

    if (view->ndim == 0) {
        if (PyTuple_Check(key) && PyTuple_GET_SIZE(key) == 0) {
            const char *fmt = adjust_fmt(view);
            if (fmt == NULL)
                return NULL;
            return unpack_single(self, view->buf, fmt);
        }
        else if (key == Py_Ellipsis) {
            return Py_NewRef(self);
        }
        else {
            PyErr_SetString(PyExc_TypeError,
                "invalid indexing of 0-dim memory");
            return NULL;
        }
    }

    if (_PyIndex_Check(key)) {
        Py_ssize_t index;
        index = PyNumber_AsSsize_t(key, PyExc_IndexError);
        if (index == -1 && PyErr_Occurred())
            return NULL;
        return memory_item((PyObject *)self, index);
    }
    else if (PySlice_Check(key)) {
        CHECK_RESTRICTED(self);
        PyMemoryViewObject *sliced;

        sliced = (PyMemoryViewObject *)mbuf_add_view(self->mbuf, view);
        if (sliced == NULL)
            return NULL;
				
		// Call __index__ method which close the mmap memory while sliced still holds its pointer
        if (init_slice(&sliced->view, key, 0) < 0) {
            Py_DECREF(sliced);
            return NULL;
        }
        init_len(&sliced->view);
        init_flags(sliced);

        return (PyObject *)sliced;
    }
    else if (is_multiindex(key)) {
        return memory_item_multi(self, key);
    }
    else if (is_multislice(key)) {
        PyErr_SetString(PyExc_NotImplementedError,
            "multi-dimensional slicing is not implemented");
        return NULL;
    }

    PyErr_SetString(PyExc_TypeError, "memoryview: invalid slice key");
    return NULL;
}

Affected Versions:

Python Version Status Exit Code
Python 3.9.24+ (heads/3.9:9c4638d, Oct 17 2025, 11:19:30) ASAN 1
Python 3.10.19+ (heads/3.10:0142619, Oct 17 2025, 11:20:05) [GCC 13.3.0] ASAN 1
Python 3.11.14+ (heads/3.11:88f3f5b, Oct 17 2025, 11:20:44) [GCC 13.3.0] ASAN 1
Python 3.12.12+ (heads/3.12:8cb2092, Oct 17 2025, 11:21:35) [GCC 13.3.0] ASAN 1
Python 3.13.9+ (heads/3.13:0760a57, Oct 17 2025, 11:22:25) [GCC 13.3.0] ASAN 1
Python 3.14.0+ (heads/3.14:889e918, Oct 17 2025, 11:23:02) [GCC 13.3.0] ASAN 1
Python 3.15.0a1+ (heads/main:fbf0843, Oct 17 2025, 11:23:37) [GCC 13.3.0] ASAN 1

Sanitizer Output

=================================================================
==1532703==ERROR: AddressSanitizer: BUS on unknown address (pc 0x5ae17c7bd8ce bp 0x7fffb3a81570 sp 0x7fffb3a814b0 T0)
==1532703==The signal is caused by a READ memory access.
==1532703==Hint: this fault was caused by a dereference of a high value address (see register values below).  Disassemble the provided pc to learn which register was used.
    #0 0x5ae17c7bd8ce in unpack_single Objects/memoryobject.c:1842
    #1 0x5ae17c7be2c2 in memory_item Objects/memoryobject.c:2474
    #2 0x5ae17c7c31d6 in memory_subscript Objects/memoryobject.c:2610
    #3 0x5ae17c6e5393 in PyObject_GetItem Objects/abstract.c:163
    #4 0x5ae17c98a8f8 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:62
    #5 0x5ae17c9d7e54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #6 0x5ae17c9d8148 in _PyEval_Vector Python/ceval.c:2001
    #7 0x5ae17c9d83f8 in PyEval_EvalCode Python/ceval.c:884
    #8 0x5ae17cacf507 in run_eval_code_obj Python/pythonrun.c:1365
    #9 0x5ae17cacf723 in run_mod Python/pythonrun.c:1459
    #10 0x5ae17cad057a in pyrun_file Python/pythonrun.c:1293
    #11 0x5ae17cad3220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #12 0x5ae17cad34f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
    #13 0x5ae17cb2474d in pymain_run_file_obj Modules/main.c:410
    #14 0x5ae17cb249b4 in pymain_run_file Modules/main.c:429
    #15 0x5ae17cb261b2 in pymain_run_python Modules/main.c:691
    #16 0x5ae17cb26842 in Py_RunMain Modules/main.c:772
    #17 0x5ae17cb26a2e in pymain_main Modules/main.c:802
    #18 0x5ae17cb26db3 in Py_BytesMain Modules/main.c:826
    #19 0x5ae17c5aa645 in main Programs/python.c:15
    #20 0x715a34c2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #21 0x715a34c2a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #22 0x5ae17c5aa574 in _start (/home/jackfromeast/Desktop/entropy/tasks/grammar-afl++-latest/targets/cpython/python+0x2dd574) (BuildId: ff3dc40ea460bd4beb2c3a72283cca525b319bf0)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: BUS Objects/memoryobject.c:1842 in unpack_single
==1532703==ABORTING

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)type-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