python / python/cpython

Null pointer dereference in `array_repeat` via re-entrant `__index__` during array multiplication

Ouverte
#142,883 1 commentaire 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

extension-modules type-crash
Langage dominant
Python
Étoiles
77.2k
Forks
35.9k
Métriques de merge des PR
Métriques de PR en attente

Description

[!IMPORTANT]
This issue is not for newcomers.

What happened?

A custom __index__ can swap the MRO of an array.array subclass mid multiplication, so array_repeat calls find_array_state_by_type on a type without the array module and receives a NULL module state. The subsequent dereference of state->ArrayType then hits freed memory and triggers a null pointer read.

Proof of Concept:

import array

class Good(array.array):
    pass

class Hide(type):
    def mro(cls):
        return (cls, object)

class Bad(Good, metaclass=Hide):
    pass

arr = Good('b', b'x')

class Count:
    def __index__(self):
        arr.__class__ = Bad     # Drop array.array from the MRO mid-op
        return 2

arr * Count()

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

Related Code Snippet

#define find_array_state_by_type(tp) \
      (get_array_state(PyType_GetModuleByDef(tp, &arraymodule)))   // requires array.array in the MRO

static array_state *
get_array_state(PyObject *module)
{
    return (array_state *)_PyModule_GetState(module);            // assumes module is non-NULL
}

static PyObject *
array_repeat(PyObject *op, Py_ssize_t n)
{
    arrayobject *a = arrayobject_CAST(op);
    array_state *state = find_array_state_by_type(Py_TYPE(a));

    if (n < 0)
        n = 0;
    const Py_ssize_t array_length = Py_SIZE(a);
    if ((array_length != 0) && (n > PY_SSIZE_T_MAX / array_length)) {
        return PyErr_NoMemory();
    }
    Py_ssize_t size = array_length * n;
    arrayobject* np = (arrayobject *) newarrayobject(state->ArrayType, size, a->ob_descr);
    if (np == NULL)
        return NULL;
    if (size == 0)
        return (PyObject *)np;

    const Py_ssize_t oldbytes = array_length * a->ob_descr->itemsize;
    const Py_ssize_t newbytes = oldbytes * n;
    _PyBytes_Repeat(np->ob_item, newbytes, a->ob_item, oldbytes);

    return (PyObject *)np;
}

Sanitizer Report

=================================================================
==105120==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000008 (pc 0x77b5659592d8 bp 0x7ffe28c17cf0 sp 0x7ffe28c17cf0 T0)
==105120==The signal is caused by a READ memory access.
==105120==Hint: address points to the zero page.
    #0 0x77b5659592d8 in _Py_TYPE Include/object.h:277
    #1 0x77b5659592d8 in Py_IS_TYPE Include/object.h:302
    #2 0x77b5659592d8 in PyObject_TypeCheck Include/object.h:432
    #3 0x77b565959317 in _PyModule_GetState Include/internal/pycore_moduleobject.h:38
    #4 0x77b56595936b in get_array_state Modules/arraymodule.c:75
    #5 0x77b56595f09d in array_repeat Modules/arraymodule.c:970
    #6 0x584ac280512b in wrap_indexargfunc Objects/typeobject.c:9651
    #7 0x584ac270d606 in wrapperdescr_raw_call Objects/descrobject.c:532
    #8 0x584ac270df48 in wrapperdescr_call Objects/descrobject.c:570
    #9 0x584ac26eec71 in _PyObject_MakeTpCall Objects/call.c:242
    #10 0x584ac2801605 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:167
    #11 0x584ac2801680 in vectorcall_unbound Objects/typeobject.c:3033
    #12 0x584ac281def4 in vectorcall_maybe Objects/typeobject.c:3130
    #13 0x584ac2821819 in slot_nb_multiply Objects/typeobject.c:10397
    #14 0x584ac26b8613 in binary_op1 Objects/abstract.c:966
    #15 0x584ac26bcc64 in PyNumber_Multiply Objects/abstract.c:1170
    #16 0x584ac29638f8 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:62
    #17 0x584ac29b0e54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #18 0x584ac29b1148 in _PyEval_Vector Python/ceval.c:2001
    #19 0x584ac29b13f8 in PyEval_EvalCode Python/ceval.c:884
    #20 0x584ac2aa8507 in run_eval_code_obj Python/pythonrun.c:1365
    #21 0x584ac2aa8723 in run_mod Python/pythonrun.c:1459
    #22 0x584ac2aa957a in pyrun_file Python/pythonrun.c:1293
    #23 0x584ac2aac220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #24 0x584ac2aac4f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
    #25 0x584ac2afd74d in pymain_run_file_obj Modules/main.c:410
    #26 0x584ac2afd9b4 in pymain_run_file Modules/main.c:429
    #27 0x584ac2aff1b2 in pymain_run_python Modules/main.c:691
    #28 0x584ac2aff842 in Py_RunMain Modules/main.c:772
    #29 0x584ac2affa2e in pymain_main Modules/main.c:802
    #30 0x584ac2affdb3 in Py_BytesMain Modules/main.c:826
    #31 0x584ac2583645 in main Programs/python.c:15
    #32 0x77b56622a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #33 0x77b56622a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #34 0x584ac2583574 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 Include/object.h:277 in _Py_TYPE
==105120==ABORTING
Linked PRs
  • gh-143776

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par Modules/arraymodule.c, en particulier array_repeat et le chemin find_array_state_by_type, puis exécutez le reproducteur re-entrant index fourni avec la configuration de sanitizer. Consultez la PR liée gh-143776 pour connaître l’orientation actuelle ; c’est terminé lorsque le reproducteur ne déclenche plus l’échec de sanitizer signalé et que les tests pertinents passent.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
c, python
Domaine
backend
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
À l'abandon
Clarté
Clairement spécifiée
Accessibilité débutants
20/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.