Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

`Zlib::Deflate#params` segfaults when called before any output buffer exists

Abierto
#143 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
3/5
Tiempo estimado
1-2 días
Aptitud para principiantes
72/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Tranquilo
Stack tecnológico
c, ruby
Área
backend

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::VERSION 3.2.1, libz 1.3.2
  • the published zlib gem 3.2.3 built from source, same host
  • ruby 4.1.0dev built from ruby/ruby master 973c45fcb3, in-tree Zlib::VERSION 3.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.

poc.txt

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

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de ruby/zlib

Todos los issues de ruby/zlib

Issues similares

Más issues de C

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.