-
-
Notifications
You must be signed in to change notification settings - Fork 34.4k
Null pointer dereference in BufferedWriter.seek during re-entrant close #143375
Copy link
Copy link
Open
Labels
3.13bugs and security fixesbugs and security fixes3.14bugs and security fixesbugs and security fixes3.15new features, bugs and security fixesnew features, bugs and security fixesextension-modulesC modules in the Modules dirC modules in the Modules dirtopic-IOtype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Description
What happened?
BufferedWriter.seek asks the offset object for __index__ before it flushes pending bytes, so a crafted type can close the writer during that conversion, leaving the buffered write state freed while _bufferedwriter_raw_write retries the partial write and dereferences the stale pointer.
Proof of Concept:
import io
class R:
def __init__(self):
self.calls = 0
self.closed = False
def writable(self): return True
def seekable(self): return True
def seek(self, off, whence=0): return 0
def write(self, mv):
self.calls += 1
if self.calls == 1: return len(mv) - 1
if self.calls == 2: raise BlockingIOError
mv.tobytes()
return len(mv)
bw = io.BufferedWriter(R())
bw.write(b"ABCD")
class T:
def __index__(self):
try: bw.close()
except Exception: pass
return 0
bw.seek(T())Vulnerable Code Snippet:
Click to expand
/* Buggy Re-entrant Path */
static PyObject *
_io__Buffered_seek_impl(buffered *self, PyObject *targetobj, int whence)
{
Py_off_t target;
/* ... */
target = PyNumber_AsOff_t(targetobj, PyExc_ValueError); /* Reentrant call site */
if (target == -1 && PyErr_Occurred())
return NULL;
/* ... */
if (self->writable) {
res = _bufferedwriter_flush_unlocked(self);
/* ... */
}
/* ... */
}
static PyObject *
_bufferedwriter_flush_unlocked(buffered *self)
{
/* ... */
while (self->write_pos < self->write_end) {
n = _bufferedwriter_raw_write(self,
self->buffer + self->write_pos, /* crashing pointer derived */
Py_SAFE_DOWNCAST(self->write_end - self->write_pos,
Py_off_t, Py_ssize_t));
/* ... */
}
/* ... */
}
static Py_ssize_t
_bufferedwriter_raw_write(buffered *self, char *start, Py_ssize_t len)
{
Py_buffer buf;
PyObject *memobj, *res;
/* NOTE: the buffer needn't be released as its object is NULL. */
if (PyBuffer_FillInfo(&buf, NULL, start, len, 1, PyBUF_CONTIG_RO) == -1)
return -1;
memobj = PyMemoryView_FromBuffer(&buf);
/* ... raw.write(memobj) ... */
return n;
}
int
PyBuffer_ToContiguous(void *buf, const Py_buffer *src, Py_ssize_t len, char order)
{
if (PyBuffer_IsContiguous(src, order)) {
memcpy((char *)buf, src->buf, len); /* Crash site */
return 0;
}
/* ... */
return 0;
}
/* Clobbering Path */
static PyObject *
_io__Buffered_close_impl(buffered *self)
{
/* ... */
if (self->buffer) {
PyMem_Free(self->buffer); /* state mutate site */
self->buffer = NULL;
}
/* ... */
return res;
}Sanitizer Output:
Click to expand
AddressSanitizer:DEADLYSIGNAL
=================================================================
==2188767==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000003 (pc 0x79e00cf88a84 bp 0x7ffd2d3db980 sp 0x7ffd2d3db928 T0)
==2188767==The signal is caused by a READ memory access.
==2188767==Hint: address points to the zero page.
#0 0x79e00cf88a84 in __memmove_avx_unaligned_erms ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:318
#1 0x654cee9496ed in memcpy /usr/include/x86_64-linux-gnu/bits/string_fortified.h:29
#2 0x654cee9496ed in PyBuffer_ToContiguous Objects/memoryobject.c:1063
#3 0x654cee949fd9 in memoryview_tobytes_impl Objects/memoryobject.c:2310
#4 0x654cee949fd9 in memoryview_tobytes Objects/clinic/memoryobject.c.h:335
#5 0x654cee83b3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#6 0x654cee83b3e7 in PyObject_Vectorcall Objects/call.c:327
#7 0x654cee6ef5a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#8 0x654ceebba2a5 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#9 0x654ceebba2a5 in _PyEval_Vector Python/ceval.c:2001
#10 0x654cee83d863 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#11 0x654cee83d863 in PyObject_VectorcallMethod Objects/call.c:859
#12 0x654ceee3bfd5 in PyObject_CallMethodOneArg Include/cpython/abstract.h:74
#13 0x654ceee3bfd5 in _bufferedwriter_raw_write Modules/_io/bufferedio.c:1985
#14 0x654ceee3d3b6 in _bufferedwriter_flush_unlocked Modules/_io/bufferedio.c:2029
#15 0x654ceee4613e in _io__Buffered_seek_impl Modules/_io/bufferedio.c:1425
#16 0x654ceee4613e in _io__Buffered_seek Modules/_io/clinic/bufferedio.c.h:885
#17 0x654cee83b3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#18 0x654cee83b3e7 in PyObject_Vectorcall Objects/call.c:327
#19 0x654cee6ef5a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#20 0x654ceebb9ad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#21 0x654ceebb9ad6 in _PyEval_Vector Python/ceval.c:2001
#22 0x654ceebb9ad6 in PyEval_EvalCode Python/ceval.c:884
#23 0x654ceecff16e in run_eval_code_obj Python/pythonrun.c:1365
#24 0x654ceecff16e in run_mod Python/pythonrun.c:1459
#25 0x654ceed03e17 in pyrun_file Python/pythonrun.c:1293
#26 0x654ceed03e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#27 0x654ceed0493c in _PyRun_AnyFileObject Python/pythonrun.c:81
#28 0x654ceed77e3c in pymain_run_file_obj Modules/main.c:410
#29 0x654ceed77e3c in pymain_run_file Modules/main.c:429
#30 0x654ceed77e3c in pymain_run_python Modules/main.c:691
#31 0x654ceed7971e in Py_RunMain Modules/main.c:772
#32 0x654ceed7971e in pymain_main Modules/main.c:802
#33 0x654ceed7971e in Py_BytesMain Modules/main.c:826
#34 0x79e00ce2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#35 0x79e00ce2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#36 0x654cee713634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:318 in __memmove_avx_unaligned_erms
==2188767==ABORTING
CPython versions tested on:
Details
| Python Version | Status | Exit Code |
|---|---|---|
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 28 2025, 16:51:20) |
ASAN | 1 |
Python 3.10.19+ (heads/3.10:014261980b1, Oct 28 2025, 16:52:08) [Clang 18.1.3 (1ubuntu1)] |
ASAN | 1 |
Python 3.11.14+ (heads/3.11:88f3f5b5f11, Oct 28 2025, 16:53:08) [Clang 18.1.3 (1ubuntu1)] |
ASAN | 1 |
Python 3.12.12+ (heads/3.12:8cb2092bd8c, Oct 28 2025, 16:54:14) [Clang 18.1.3 (1ubuntu1)] |
ASAN | 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 |
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 19:29:54) [GCC 13.3.0]
Linked PRs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.13bugs and security fixesbugs and security fixes3.14bugs and security fixesbugs and security fixes3.15new features, bugs and security fixesnew features, bugs and security fixesextension-modulesC modules in the Modules dirC modules in the Modules dirtopic-IOtype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump