Skip to content

Use-after-free in memory_hash via re-entrant __hash__ #142664

@jackfromeast

Description

@jackfromeast

What happened?

hash(memoryview) (read-only, byte format) calls PyObject_Hash(view->obj) inside memory_hash. If the exporter’s __hash__ mutates or frees its buffer (e.g., by releasing the memoryview and then resizing/clearing the underlying array), memory_hash continues and hashes from a stale view->buf, leading to a heap use-after-free.

Note that this is similar to #142665, while with a different reentrant place.

Proof of Concept:

import array

class Evil(array.array):
    def __hash__(self):
        global mv
        mv.release()      # drop the export so the buffer can be freed
        self.clear()      # free the backing storage
        return 123        # hash() still succeeds

data = Evil('B', b'A' * 4096)
mv = memoryview(data).toreadonly()   # must be read-only for hash()

print(hash(mv))

Affected Versions:

Python Version Status Exit Code
Python 3.9.24+ (heads/3.9:9c4638d, Oct 17 2025, 11:19:30) Exception 1
Python 3.10.19+ (heads/3.10:0142619, Oct 17 2025, 11:20:05) [GCC 13.3.0] Exception 1
Python 3.11.14+ (heads/3.11:88f3f5b, Oct 17 2025, 11:20:44) [GCC 13.3.0] Exception 1
Python 3.12.12+ (heads/3.12:8cb2092, Oct 17 2025, 11:21:35) [GCC 13.3.0] Exception 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

Related Code Snippet

static Py_hash_t
memory_hash(PyObject *_self)
{
    PyMemoryViewObject *self = (PyMemoryViewObject *)_self;
    if (self->hash == -1) {
        Py_buffer *view = &self->view;
        char *mem = view->buf;
        Py_ssize_t ret;
        char fmt;

        CHECK_RELEASED_INT(self);

        if (!view->readonly) {
            PyErr_SetString(PyExc_ValueError,
                "cannot hash writable memoryview object");
            return -1;
        }
        ret = get_native_fmtchar(&fmt, view->format);
        if (ret < 0 || !IS_BYTE_FORMAT(fmt)) {
            PyErr_SetString(PyExc_ValueError,
                "memoryview: hashing is restricted to formats 'B', 'b' or 'c'");
            return -1;
        }
        
        // Bug: PyObject_Hash clear the array elements to free view->buf
        if (view->obj != NULL && PyObject_Hash(view->obj) == -1) {
            /* Keep the original error message */
            return -1;
        }

        if (!MV_C_CONTIGUOUS(self->flags)) {
            mem = PyMem_Malloc(view->len);
            if (mem == NULL) {
                PyErr_NoMemory();
                return -1;
            }
            if (buffer_to_contiguous(mem, view, 'C') < 0) {
                PyMem_Free(mem);
                return -1;
            }
        }
				
        /* Can't fail */
        // Trigger: mem is now pointing to a freed buffer
        self->hash = Py_HashBuffer(mem, view->len);

        if (mem != view->buf)
            PyMem_Free(mem);
    }

    return self->hash;
}

Sanitizer Output:

=================================================================
==1543499==ERROR: AddressSanitizer: heap-use-after-free on address 0x52100001f510 at pc 0x58bc9b186b08 bp 0x7fff4881d0a0 sp 0x7fff4881d090
READ of size 8 at 0x52100001f510 thread T0
    #0 0x58bc9b186b07 in memcpy /usr/include/x86_64-linux-gnu/bits/string_fortified.h:29
    #1 0x58bc9b186b07 in siphash13 Python/pyhash.c:385
    #2 0x58bc9b186f3c in pysiphash Python/pyhash.c:481
    #3 0x58bc9b18728d in Py_HashBuffer Python/pyhash.c:190
    #4 0x58bc9ae96655 in memory_hash Objects/memoryobject.c:3246
    #5 0x58bc9aea63d6 in PyObject_Hash Objects/object.c:1157
    #6 0x58bc9b049fcc in builtin_hash Python/bltinmodule.c:1772
    #7 0x58bc9ae9c3fe in cfunction_vectorcall_O Objects/methodobject.c:536
    #8 0x58bc9ade9e7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #9 0x58bc9ade9f72 in PyObject_Vectorcall Objects/call.c:327
    #10 0x58bc9b068056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #11 0x58bc9b0abe54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #12 0x58bc9b0ac148 in _PyEval_Vector Python/ceval.c:2001
    #13 0x58bc9b0ac3f8 in PyEval_EvalCode Python/ceval.c:884
    #14 0x58bc9b1a3507 in run_eval_code_obj Python/pythonrun.c:1365
    #15 0x58bc9b1a3723 in run_mod Python/pythonrun.c:1459
    #16 0x58bc9b1a457a in pyrun_file Python/pythonrun.c:1293
    #17 0x58bc9b1a7220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #18 0x58bc9b1a74f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
    #19 0x58bc9b1f874d in pymain_run_file_obj Modules/main.c:410
    #20 0x58bc9b1f89b4 in pymain_run_file Modules/main.c:429
    #21 0x58bc9b1fa1b2 in pymain_run_python Modules/main.c:691
    #22 0x58bc9b1fa842 in Py_RunMain Modules/main.c:772
    #23 0x58bc9b1faa2e in pymain_main Modules/main.c:802
    #24 0x58bc9b1fadb3 in Py_BytesMain Modules/main.c:826
    #25 0x58bc9ac7e645 in main Programs/python.c:15
    #26 0x74bb6882a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #27 0x74bb6882a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #28 0x58bc9ac7e574 in _start (/home/jackfromeast/Desktop/entropy/tasks/grammar-afl++-latest/targets/cpython/python+0x2dd574) (BuildId: ff3dc40ea460bd4beb2c3a72283cca525b319bf0)

0x52100001f510 is located 16 bytes inside of 4379-byte region [0x52100001f500,0x52100002061b)
freed by thread T0 here:
    #0 0x74bb68cfc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
    #1 0x58bc9aeb096d in _PyMem_RawFree Objects/obmalloc.c:91
    #2 0x58bc9aeb2cd9 in _PyMem_DebugRawFree Objects/obmalloc.c:2955
    #3 0x58bc9aeb2d1a in _PyMem_DebugFree Objects/obmalloc.c:3100
    #4 0x58bc9aeda348 in PyMem_Free Objects/obmalloc.c:1070
    #5 0x74bb67da2c97 in array_resize Modules/arraymodule.c:159
    #6 0x74bb67da337e in array_array_clear_impl Modules/arraymodule.c:894
    #7 0x74bb67da33a0 in array_array_clear Modules/clinic/arraymodule.c.h:26
    #8 0x58bc9ae09571 in method_vectorcall_NOARGS Objects/descrobject.c:448
    #9 0x58bc9ade9e7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #10 0x58bc9ade9f72 in PyObject_Vectorcall Objects/call.c:327
    #11 0x58bc9b068056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #12 0x58bc9b0abe54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #13 0x58bc9b0ac148 in _PyEval_Vector Python/ceval.c:2001
    #14 0x58bc9ade99b8 in _PyFunction_Vectorcall Objects/call.c:413
    #15 0x58bc9ade9e7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #16 0x58bc9adea03f in PyObject_CallOneArg Objects/call.c:395
    #17 0x58bc9aefc648 in call_unbound_noarg Objects/typeobject.c:3040
    #18 0x58bc9af17fa0 in maybe_call_special_no_args Objects/typeobject.c:3153
    #19 0x58bc9af185e4 in slot_tp_hash Objects/typeobject.c:10564
    #20 0x58bc9aea63d6 in PyObject_Hash Objects/object.c:1157
    #21 0x58bc9ae965cc in memory_hash Objects/memoryobject.c:3228
    #22 0x58bc9aea63d6 in PyObject_Hash Objects/object.c:1157
    #23 0x58bc9b049fcc in builtin_hash Python/bltinmodule.c:1772
    #24 0x58bc9ae9c3fe in cfunction_vectorcall_O Objects/methodobject.c:536
    #25 0x58bc9ade9e7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #26 0x58bc9ade9f72 in PyObject_Vectorcall Objects/call.c:327
    #27 0x58bc9b068056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #28 0x58bc9b0abe54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #29 0x58bc9b0ac148 in _PyEval_Vector Python/ceval.c:2001

previously allocated by thread T0 here:
    #0 0x74bb68cfd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x58bc9aeb1284 in _PyMem_RawMalloc Objects/obmalloc.c:63
    #2 0x58bc9aeb0655 in _PyMem_DebugRawAlloc Objects/obmalloc.c:2887
    #3 0x58bc9aeb364f in _PyMem_DebugRawRealloc Objects/obmalloc.c:2963
    #4 0x58bc9aeb3823 in _PyMem_DebugRealloc Objects/obmalloc.c:3108
    #5 0x58bc9aeda2e7 in PyMem_Realloc Objects/obmalloc.c:1063
    #6 0x74bb67da2b78 in array_resize Modules/arraymodule.c:183
    #7 0x74bb67da59dd in frombytes Modules/arraymodule.c:1721
    #8 0x74bb67da5a63 in array_array_frombytes_impl Modules/arraymodule.c:1746
    #9 0x74bb67da5b97 in array_array_frombytes Modules/clinic/arraymodule.c.h:539
    #10 0x74bb67da657d in array_new Modules/arraymodule.c:2812
    #11 0x58bc9af10346 in type_call Objects/typeobject.c:2448
    #12 0x58bc9ade9c71 in _PyObject_MakeTpCall Objects/call.c:242
    #13 0x58bc9ade9f19 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:167
    #14 0x58bc9ade9f72 in PyObject_Vectorcall Objects/call.c:327
    #15 0x58bc9b068056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #16 0x58bc9b0abe54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #17 0x58bc9b0ac148 in _PyEval_Vector Python/ceval.c:2001
    #18 0x58bc9b0ac3f8 in PyEval_EvalCode Python/ceval.c:884
    #19 0x58bc9b1a3507 in run_eval_code_obj Python/pythonrun.c:1365
    #20 0x58bc9b1a3723 in run_mod Python/pythonrun.c:1459
    #21 0x58bc9b1a457a in pyrun_file Python/pythonrun.c:1293
    #22 0x58bc9b1a7220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #23 0x58bc9b1a74f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
    #24 0x58bc9b1f874d in pymain_run_file_obj Modules/main.c:410
    #25 0x58bc9b1f89b4 in pymain_run_file Modules/main.c:429
    #26 0x58bc9b1fa1b2 in pymain_run_python Modules/main.c:691
    #27 0x58bc9b1fa842 in Py_RunMain Modules/main.c:772
    #28 0x58bc9b1faa2e in pymain_main Modules/main.c:802
    #29 0x58bc9b1fadb3 in Py_BytesMain Modules/main.c:826

SUMMARY: AddressSanitizer: heap-use-after-free /usr/include/x86_64-linux-gnu/bits/string_fortified.h:29 in memcpy
Shadow bytes around the buggy address:
  0x52100001f280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x52100001f300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x52100001f380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x52100001f400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x52100001f480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x52100001f500: fd fd[fd]fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x52100001f580: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x52100001f600: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x52100001f680: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x52100001f700: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x52100001f780: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
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
==1543499==ABORTIN

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