Segfault: 4+ unchecked `PyBytes_AsStringAndSize` on user `read()` return uses uninitialized memory
I maintainer di solito rispondono entro 4 giorni
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 76/100
Direzione di ricerca
Inizia in c-ext/compressor.c, c-ext/decompressor.c, c-ext/compressoriterator.c e c-ext/decompressoriterator.c, in corrispondenza delle chiamate PyBytes_AsStringAndSize indicate, quindi esamina le chiamate aggiuntive in read_compressor_input e nel suo equivalente per il decompressor. Riproduci il problema con gli esempi BadSource forniti e verifica che i valori restituiti che non sono bytes generino TypeError senza causare un crash o un’interruzione dovuta a un’asserzione in ogni percorso di streaming interessato.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Summary
Several streaming paths call PyBytes_AsStringAndSize(result, &readBuffer, &readSize) on the return value of a caller-supplied source.read() and do not check the return value. When read() returns a non-bytes object (e.g., str, None, bytearray), readBuffer and readSize are left with their prior/uninitialized contents; the next ZSTD_compressStream2 call reads from those addresses. Observed: SEGV on release builds, _Py_CheckFunctionResult abort on debug builds.
Impact
- Severity: SEGV on release builds; assertion abort on debug builds.
- Reachability: Any caller-supplied
source.read()whose return is notbytes. A trivial wrapper around a text file or a mistakenly-returnedbytearraytriggers it. - Version: 0.25.0 (commit
7a77a75). - Platform: Confirmed Linux x86_64 / CPython 3.14 debug; bug is platform-independent.
Reproducers
SEGV on release — via copy_stream:
import zstandard, io
class BadSource:
def read(self, size):
return 'not bytes' # str, not bytes
comp = zstandard.ZstdCompressor()
comp.copy_stream(BadSource(), io.BytesIO())
# Segmentation fault
Assertion abort on debug — via iterator:
import zstandard
class BadSource:
def read(self, size):
return 'not bytes'
comp = zstandard.ZstdCompressor()
it = comp.read_to_iter(BadSource())
next(it)
# Fatal Python error: _Py_CheckFunctionResult: a function returned a result with an exception set
# TypeError: expected bytes, str found
Root cause
PyBytes_AsStringAndSize returns -1 and sets a TypeError when its argument is not a bytes object. On failure, the by-address output parameters readBuffer / readSize are not written. zstandard ignores the return code and proceeds to use those addresses, passing them to ZSTD_compressStream2 which reads from whatever happens to be on the stack / in registers.
Affected sites
| File | Line | Function |
|---|---|---|
c-ext/compressor.c |
349 | copy_stream |
c-ext/decompressor.c |
202 | decompressor_copy_stream |
c-ext/compressoriterator.c |
98 | ZstdCompressorIterator_iternext |
c-ext/decompressoriterator.c |
134 | ZstdDecompressorIterator_iternext |
Plus 2 additional sites reported in the full analysis (in read_compressor_input and the decompressor equivalent).
Suggested fix
Mechanical — add the standard error check after every call:
if (PyBytes_AsStringAndSize(result, &readBuffer, &readSize) < 0) {
Py_DECREF(result);
goto finally;
}
Optionally: tighten the documented API contract on source.read() to specify that the return must be a bytes object (the C code already expects this). Enforcing it at the boundary would be a small additional cleanup.
Methodology
Found via cext-review-toolkit (Tree-sitter-based static analysis with structured naive/informed review passes). SEGV on release verified at the copy_stream site; assertion abort on debug verified at the iterator site. Four sites confirmed via direct reproducer; two more confirmed via static review. Happy to open a PR — the fix is a ~8-line diff.
Discovery, root-cause analysis, and issue drafting were performed by Claude Code and reviewed by a human before filing.
Full report
Complete multi-agent analysis (48 FIX findings across 13 categories, plus a reproducer appendix): https://gist.github.com/devdanzin/b86039ac097141579590c1a0f3a43605
- Lingua principale
- C
- Stelle
- 641
- Fork
- 117
- Merge medio
- 8g 2h
- PR unite (30g)
- 7
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di indygreg/python-zstandard
-
`multi_decompress_to_buffer([])` terminates the process with SIGFPEForse già presa @mikamikasuki l’ha presa 9 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
indygreg/python-zstandard#335 ·
I maintainer di solito rispondono entro 4 giorni
-
Silent data-correctness bug: `readinto()` / `readinto1()` on `stream_reader` return `tell() == 0` after successful readsForse già presa @CAOShurong l’ha presa 3 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
indygreg/python-zstandard#295 ·
I maintainer di solito rispondono entro 4 giorni
-
Python 3.15 wheelsAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 55/100
indygreg/python-zstandard#354 ·
I maintainer di solito rispondono entro 4 giorni
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 64/100
indygreg/python-zstandard#345 ·
I maintainer di solito rispondono entro 4 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 55/100
indygreg/python-zstandard#334 ·
I maintainer di solito rispondono entro 4 giorni
Tutte le issue di indygreg/python-zstandard
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
osam-cologne/archlinux-proaudio#909 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
ARM-software/CMSIS-DSP#354 ·
-
ci: Verify install never runs examples_install or tests_install, so a clear they widen passesApertaseverity: low
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
luainkernel/lunatik#1876 ·
I maintainer di solito rispondono entro 1 giorno
-
good first issue help wanted
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
I maintainer di solito rispondono entro 1 giorno
-
documentation good first issue help wanted
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
ai-quokka-wannabe/link#40 ·