Null pointer dereference in `array.array.tofile` via reentrant writer
Personne n'a encore pris cette issue.
- Langage dominant
- Python
- Étoiles
- 77.2k
- Forks
- 35.9k
- Métriques de merge des PR
- Métriques de PR en attente
Description
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
Guide de contribution
Ouvrir le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Piste de recherche
Commencez dans Modules/arraymodule.c, au niveau de array_array_tofile_impl, et reproduisez l’exemple fourni de writer réentrant, de préférence avec AddressSanitizer. Examinez les PRs liés avant de commencer le travail ; la réalisation doit empêcher le crash dû au pointeur nul tout en préservant le comportement normal de tofile et inclure un test de régression pour le cas réentrant.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- c, python
- Domaine
- backend, security
- Type d'issue
- Bug
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Activité
- À l'abandon
- Clarté
- Clairement spécifiée
- Accessibilité débutants
- 25/100