`Zlib::Deflate#params` segfaults when called before any output buffer exists
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 72/100
Línea de trabajo
Comienza con ext/zlib/zlib.c, especialmente con rb_deflate_params y el manejo del búfer en zstream_run. Ejecuta la reproducción zlib-params-segv.rb adjunta contra la extensión y, después, verifica las pruebas existentes de zlib y el caso de compresión en mitad del flujo comunicado. Se considera terminado cuando todos los casos finalizan sin SIGSEGV y los datos comprimidos siguen pudiendo descomprimirse al input original.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Zlib::Deflate#params crashes the interpreter with SIGSEGV when it is called on a deflate stream that has not produced output yet. No invalid argument is involved — the same level the stream already has crashes it too.
require 'zlib'
Zlib::Deflate.new(4, 15).params(5, 0)
# => [BUG] Segmentation fault at 0x0000000000000014
Reproduction
The attached zlib-params-segv.rb runs each case in a forked child, so one file reports every result:
1. the crash, and that it does not depend on the argument values
Deflate.new(4, 15).params(5, 0) SIGSEGV
Deflate.new.params(5, 0) SIGSEGV
params with the SAME level it has SIGSEGV
raw stream, windowBits -15 SIGSEGV
gzip stream, windowBits 31 SIGSEGV
after finish, buffer consumed SIGSEGV
2. the condition: it crashes exactly when avail_out is 0
freshly constructed avail_out=0
after writing 100 bytes avail_out=1022
params after writing data no crash (exit 0)
params after avail_out = 4096 no crash (exit 0)
3. control: constructing without calling params is fine
Deflate.new(4, 15) and nothing else no crash (exit 0)
Zlib.deflate("foo", 4) no crash (exit 0)
The after finish case matters: this is not only a "never used the stream yet" state. Any moment where the output buffer has been consumed reaches it again.
Cause
ext/zlib/zlib.c, rb_deflate_params at :1923:
n = z->stream.avail_out; /* :1934 */
err = deflateParams(&z->stream, level, strategy); /* :1935 */
filled = n - z->stream.avail_out;
while (err == Z_BUF_ERROR) { /* :1937 */
rb_warning("deflateParams() returned Z_BUF_ERROR");
zstream_expand_buffer(z); /* :1939 */
deflateParams() may emit pending output through deflate(), so it needs next_out/avail_out to be set. zstream_init leaves them at Z_NULL and 0 (:652-653), so on a stream that has not produced output the write goes to a null pointer — the faulting address 0x14 is the offset into that null struct.
The Z_BUF_ERROR loop does call zstream_expand_buffer, but only after the first deflateParams() call, which is the one that crashes.
Every other path guards this. zstream_run at :1163-1164:
if (z->stream.avail_out == 0) {
zstream_expand_buffer(z);
}
Suggested fix
Apply the same guard before the first deflateParams() call:
level = ARG_LEVEL(v_level);
strategy = ARG_STRATEGY(v_strategy);
if (z->stream.avail_out == 0) {
zstream_expand_buffer(z);
}
n = z->stream.avail_out;
err = deflateParams(&z->stream, level, strategy);
I applied that patch to a build of the released 3.2.3 gem and re-ran the cases above against it, with $LOADED_FEATURES confirming the patched extension was the one loaded. All six crashing cases return normally, the previously working cases are unchanged, and compression still behaves correctly across a mid-stream parameter change: deflating "A" * 2000, calling params(9, Zlib::DEFAULT_STRATEGY), then deflating "B" * 2000 produces 45 bytes that inflate back to the exact input.
Versions
Reproduced on:
- ruby 3.4.5 with the default gem,
Zlib::VERSION3.2.1, libz 1.3.2 - the published
zlibgem 3.2.3 built from source, same host - ruby 4.1.0dev built from
ruby/rubymaster973c45fcb3, in-treeZlib::VERSION3.2.3
rb_deflate_params is byte-identical between the published 3.2.3 gem and master's in-tree copy, so this is not fixed by the unreleased changes in master. Note that those two 3.2.3 code bases are not otherwise identical: master adds z->stream.state = Z_NULL; to zstream_init, which fixes a separate crash (Zlib.gzip("foo", level: 100)) that the published 3.2.3 still has, and which test/zlib/test_zlib.rb already expects to raise Zlib::StreamError.
- Lenguaje dominante
- C
- Estrellas
- 73
- Forks
- 40
- Merge medio
- 10 h 32 min
- PR fusionados (30 d)
- 2
Guía de contribución
No hay ninguna guía de contribución indexada para este repositorio
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de ruby/zlib
-
Dificultad 3/5 1-2 días Aptitud para principiantes 55/100
-
Dificultad 3/5 1-2 días Aptitud para principiantes 42/100
-
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
-
Dificultad 4/5 3-5 días Aptitud para principiantes 25/100
-
Dificultad 4/5 3-5 días Aptitud para principiantes 30/100
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
duckdb/duckdb-wasm#2258 ·
-
how does install.sh work Abierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 90/100
rofl0r/microsocks#110 ·
-
OTA is not aborted correctly Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
espressif/esp-aws-iot#261 ·
-
os:linux
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
mpv-player/mpv#18510 · 2 comentarios ·
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100