python / python/cpython

Null pointer dereference in `array.array.tofile` via reentrant writer

未關閉
#142,884 2 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

extension-modules type-crash
主要語言
Python
星號
77.2k
分支
36k
PR 合併指標
PR 指標待擷取

描述

What happened?

array.array.tofile computes its chunk count before calling the sink’s write method, so it keeps iterating even if the buffer disappears. A reentrant writer can clear the array on the first callback, leaving self->ob_item null, yet the loop still calls PyBytes_FromStringAndSize on that pointer and crashes in the memcpy path.

Proof of Concept:

import array

CHUNK = 64 * 1024
victim = array.array('B', b'\0' * (CHUNK * 2))

class Writer:
    armed = True
    def write(self, data):
        if Writer.armed:
            Writer.armed = False
            victim.clear()
        return 0

victim.tofile(Writer())

Affected Versions:

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)] Exception 1
Python 3.11.14+ (heads/3.11:88f3f5b5f11, Oct 28 2025, 16:53:08) [Clang 18.1.3 (1ubuntu1)] Exception 1
Python 3.12.12+ (heads/3.12:8cb2092bd8c, Oct 28 2025, 16:54:14) [Clang 18.1.3 (1ubuntu1)] Exception 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

Vulnerable Code:

static PyObject *
array_array_tofile_impl(arrayobject *self, PyTypeObject *cls, PyObject *f)
/*[clinic end generated code: output=4560c628d9c18bc2 input=5a24da7a7b407b52]*/
{
    Py_ssize_t nbytes = Py_SIZE(self) * self->ob_descr->itemsize;
    /* Write 64K blocks at a time */
    /* XXX Make the block size settable */
    int BLOCKSIZE = 64*1024;
    /* Bug: Caches the nblocks at the beginning */
    Py_ssize_t nblocks = (nbytes + BLOCKSIZE - 1) / BLOCKSIZE;
    Py_ssize_t i;

    if (Py_SIZE(self) == 0)
        goto done;


    array_state *state = get_array_state_by_class(cls);
    assert(state != NULL);

    for (i = 0; i < nblocks; i++) {
        /* self->ob_item has been null out */
        char* ptr = self->ob_item + i*BLOCKSIZE;
        Py_ssize_t size = BLOCKSIZE;
        PyObject *bytes, *res;

        if (i*BLOCKSIZE + size > nbytes)
            size = nbytes - i*BLOCKSIZE;
        bytes = PyBytes_FromStringAndSize(ptr, size);
        if (bytes == NULL)
            return NULL;
        res = PyObject_CallMethodOneArg(f, state->str_write, bytes);
        Py_DECREF(bytes);
        if (res == NULL)
            return NULL;
        Py_DECREF(res); /* drop write result */
    }

  done:
    Py_RETURN_NONE;
}

Sanitizer Output:

AddressSanitizer:DEADLYSIGNAL
=================================================================
==1299008==ERROR: AddressSanitizer: SEGV on unknown address 0x000000010000 (pc 0x745059388a4d bp 0x7ffeb5f943e0 sp 0x7ffeb5f943b8 T0)
==1299008==The signal is caused by a READ memory access.
    #0 0x745059388a4d in __memmove_avx_unaligned_erms ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:265
    #1 0x5e74a5aadddd in memcpy /usr/include/x86_64-linux-gnu/bits/string_fortified.h:29
    #2 0x5e74a5aadddd in PyBytes_FromStringAndSize Objects/bytesobject.c:162
    #3 0x745058bca48b in array_array_tofile_impl Modules/arraymodule.c:1611
    #4 0x745058bca675 in array_array_tofile Modules/clinic/arraymodule.c.h:472
    #5 0x5e74a5ade3c9 in method_vectorcall_FASTCALL_KEYWORDS_METHOD Objects/descrobject.c:381
    #6 0x5e74a5abee7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #7 0x5e74a5abef72 in PyObject_Vectorcall Objects/call.c:327
    #8 0x5e74a5d3d056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #9 0x5e74a5d80e54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #10 0x5e74a5d81148 in _PyEval_Vector Python/ceval.c:2001
    #11 0x5e74a5d813f8 in PyEval_EvalCode Python/ceval.c:884
    #12 0x5e74a5e78507 in run_eval_code_obj Python/pythonrun.c:1365
    #13 0x5e74a5e78723 in run_mod Python/pythonrun.c:1459
    #14 0x5e74a5e7957a in pyrun_file Python/pythonrun.c:1293
    #15 0x5e74a5e7c220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #16 0x5e74a5e7c4f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
    #17 0x5e74a5ecd74d in pymain_run_file_obj Modules/main.c:410
    #18 0x5e74a5ecd9b4 in pymain_run_file Modules/main.c:429
    #19 0x5e74a5ecf1b2 in pymain_run_python Modules/main.c:691
    #20 0x5e74a5ecf842 in Py_RunMain Modules/main.c:772
    #21 0x5e74a5ecfa2e in pymain_main Modules/main.c:802
    #22 0x5e74a5ecfdb3 in Py_BytesMain Modules/main.c:826
    #23 0x5e74a5953645 in main Programs/python.c:15
    #24 0x74505922a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #25 0x74505922a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #26 0x5e74a5953574 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x2dd574) (BuildId: 202d5dbb945f6d5f5a66ad50e2688d56affd6ecb)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:265 in __memmove_avx_unaligned_erms
==1299008==ABORTING
Linked PRs
  • gh-142920
  • gh-143238
  • gh-144925

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

從 Modules/arraymodule.c 中的 array_array_tofile_impl 開始,重現所提供的可重入 writer 範例,最好使用 AddressSanitizer。在開始工作之前先檢視連結的 PR;完成後應防止空指標當機,同時保留正常的 tofile 行為,並包含針對可重入情況的回歸測試。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
c, python
領域
backend, security
Issue 類型
缺陷
難度
4/5
預估耗時
3-5 天
活躍度
停滯
描述清晰度
描述清楚
新手友好度
25/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。