[ruby/zlib] [Bug #18358] Fix crash in zlib when in progress

When Zlib::Inflate#inflate or Zlib::Deflate#deflate is called
recursively inside the block, a crash can occur because of an
use-after-free bug.

https://github.com/ruby/zlib/commit/50fb8a0338
This commit is contained in:
Peter Zhu 2021-11-23 13:14:45 -05:00 committed by git
parent 5445d33be2
commit c51b92c18d
2 changed files with 85 additions and 42 deletions

View File

@ -288,6 +288,7 @@ static VALUE rb_gzreader_readlines(int, VALUE*, VALUE);
* - Zlib::MemError * - Zlib::MemError
* - Zlib::BufError * - Zlib::BufError
* - Zlib::VersionError * - Zlib::VersionError
* - Zlib::InProgressError
* *
* (if you have GZIP_SUPPORT) * (if you have GZIP_SUPPORT)
* - Zlib::GzipReader * - Zlib::GzipReader
@ -304,7 +305,7 @@ void Init_zlib(void);
/*--------- Exceptions --------*/ /*--------- Exceptions --------*/
static VALUE cZError, cStreamEnd, cNeedDict; static VALUE cZError, cStreamEnd, cNeedDict;
static VALUE cStreamError, cDataError, cMemError, cBufError, cVersionError; static VALUE cStreamError, cDataError, cMemError, cBufError, cVersionError, cInProgressError;
static void static void
raise_zlib_error(int err, const char *msg) raise_zlib_error(int err, const char *msg)
@ -557,14 +558,15 @@ struct zstream {
} *func; } *func;
}; };
#define ZSTREAM_FLAG_READY 0x1 #define ZSTREAM_FLAG_READY (1 << 0)
#define ZSTREAM_FLAG_IN_STREAM 0x2 #define ZSTREAM_FLAG_IN_STREAM (1 << 1)
#define ZSTREAM_FLAG_FINISHED 0x4 #define ZSTREAM_FLAG_FINISHED (1 << 2)
#define ZSTREAM_FLAG_CLOSING 0x8 #define ZSTREAM_FLAG_CLOSING (1 << 3)
#define ZSTREAM_FLAG_GZFILE 0x10 /* disallows yield from expand_buffer for #define ZSTREAM_FLAG_GZFILE (1 << 4) /* disallows yield from expand_buffer for
gzip*/ gzip*/
#define ZSTREAM_REUSE_BUFFER 0x20 #define ZSTREAM_REUSE_BUFFER (1 << 5)
#define ZSTREAM_FLAG_UNUSED 0x40 #define ZSTREAM_IN_PROGRESS (1 << 6)
#define ZSTREAM_FLAG_UNUSED (1 << 7)
#define ZSTREAM_READY(z) ((z)->flags |= ZSTREAM_FLAG_READY) #define ZSTREAM_READY(z) ((z)->flags |= ZSTREAM_FLAG_READY)
#define ZSTREAM_IS_READY(z) ((z)->flags & ZSTREAM_FLAG_READY) #define ZSTREAM_IS_READY(z) ((z)->flags & ZSTREAM_FLAG_READY)
@ -593,7 +595,9 @@ static const struct zstream_funcs inflate_funcs = {
}; };
struct zstream_run_args { struct zstream_run_args {
struct zstream * z; struct zstream *const z;
Bytef *src;
long len;
int flush; /* stream flush value for inflate() or deflate() */ int flush; /* stream flush value for inflate() or deflate() */
int interrupt; /* stop processing the stream and return to ruby */ int interrupt; /* stop processing the stream and return to ruby */
int jump_state; /* for buffer expansion block break or exception */ int jump_state; /* for buffer expansion block break or exception */
@ -1058,19 +1062,18 @@ zstream_unblock_func(void *ptr)
args->interrupt = 1; args->interrupt = 1;
} }
static void static VALUE
zstream_run0(struct zstream *z, Bytef *src, long len, int flush) zstream_run_try(VALUE value_arg)
{ {
struct zstream_run_args args; struct zstream_run_args *args = (struct zstream_run_args *)value_arg;
struct zstream *z = args->z;
Bytef *src = args->src;
long len = args->len;
int flush = args->flush;
int err; int err;
VALUE old_input = Qnil; VALUE old_input = Qnil;
args.z = z;
args.flush = flush;
args.interrupt = 0;
args.jump_state = 0;
args.stream_output = !ZSTREAM_IS_GZFILE(z) && rb_block_given_p();
if (NIL_P(z->input) && len == 0) { if (NIL_P(z->input) && len == 0) {
z->stream.next_in = (Bytef*)""; z->stream.next_in = (Bytef*)"";
z->stream.avail_in = 0; z->stream.avail_in = 0;
@ -1092,17 +1095,17 @@ zstream_run0(struct zstream *z, Bytef *src, long len, int flush)
loop: loop:
#ifndef RB_NOGVL_UBF_ASYNC_SAFE #ifndef RB_NOGVL_UBF_ASYNC_SAFE
err = (int)(VALUE)rb_thread_call_without_gvl(zstream_run_func, (void *)&args, err = (int)(VALUE)rb_thread_call_without_gvl(zstream_run_func, (void *)args,
zstream_unblock_func, (void *)&args); zstream_unblock_func, (void *)args);
#else #else
err = (int)(VALUE)rb_nogvl(zstream_run_func, (void *)&args, err = (int)(VALUE)rb_nogvl(zstream_run_func, (void *)args,
zstream_unblock_func, (void *)&args, zstream_unblock_func, (void *)args,
RB_NOGVL_UBF_ASYNC_SAFE); RB_NOGVL_UBF_ASYNC_SAFE);
#endif #endif
/* retry if no exception is thrown */ /* retry if no exception is thrown */
if (err == Z_OK && args.interrupt) { if (err == Z_OK && args->interrupt) {
args.interrupt = 0; args->interrupt = 0;
goto loop; goto loop;
} }
@ -1138,34 +1141,52 @@ loop:
rb_str_resize(old_input, 0); rb_str_resize(old_input, 0);
} }
if (args.jump_state) if (args->jump_state)
rb_jump_tag(args.jump_state); rb_jump_tag(args->jump_state);
return Qnil;
} }
struct zstream_run_synchronized_args { static VALUE
struct zstream *z; zstream_run_ensure(VALUE value_arg)
Bytef *src; {
long len; struct zstream_run_args *args = (struct zstream_run_args *)value_arg;
int flush;
}; /* Remove ZSTREAM_IN_PROGRESS flag to signal that this zstream is not in use. */
args->z->flags &= ~ZSTREAM_IN_PROGRESS;
return Qnil;
}
static VALUE static VALUE
zstream_run_synchronized(VALUE value_arg) zstream_run_synchronized(VALUE value_arg)
{ {
struct zstream_run_synchronized_args *run_args = (struct zstream_run_synchronized_args *)value_arg; struct zstream_run_args *args = (struct zstream_run_args *)value_arg;
zstream_run0(run_args->z, run_args->src, run_args->len, run_args->flush);
/* Cannot start zstream while it is in progress. */
if (args->z->flags & ZSTREAM_IN_PROGRESS) {
rb_raise(cInProgressError, "zlib stream is in progress");
}
args->z->flags |= ZSTREAM_IN_PROGRESS;
rb_ensure(zstream_run_try, value_arg, zstream_run_ensure, value_arg);
return Qnil; return Qnil;
} }
static void static void
zstream_run(struct zstream *z, Bytef *src, long len, int flush) zstream_run(struct zstream *z, Bytef *src, long len, int flush)
{ {
struct zstream_run_synchronized_args run_args; struct zstream_run_args args = {
run_args.z = z; .z = z,
run_args.src = src; .src = src,
run_args.len = len; .len = len,
run_args.flush = flush; .flush = flush,
rb_mutex_synchronize(z->mutex, zstream_run_synchronized, (VALUE)&run_args); .interrupt = 0,
.jump_state = 0,
.stream_output = !ZSTREAM_IS_GZFILE(z) && rb_block_given_p(),
};
rb_mutex_synchronize(z->mutex, zstream_run_synchronized, (VALUE)&args);
} }
static VALUE static VALUE
@ -4615,6 +4636,7 @@ Init_zlib(void)
cMemError = rb_define_class_under(mZlib, "MemError", cZError); cMemError = rb_define_class_under(mZlib, "MemError", cZError);
cBufError = rb_define_class_under(mZlib, "BufError", cZError); cBufError = rb_define_class_under(mZlib, "BufError", cZError);
cVersionError = rb_define_class_under(mZlib, "VersionError", cZError); cVersionError = rb_define_class_under(mZlib, "VersionError", cZError);
cInProgressError = rb_define_class_under(mZlib, "InProgressError", cZError);
rb_define_module_function(mZlib, "zlib_version", rb_zlib_version, 0); rb_define_module_function(mZlib, "zlib_version", rb_zlib_version, 0);
rb_define_module_function(mZlib, "adler32", rb_zlib_adler32, -1); rb_define_module_function(mZlib, "adler32", rb_zlib_adler32, -1);
@ -4922,6 +4944,7 @@ Init_zlib(void)
* - Zlib::MemError * - Zlib::MemError
* - Zlib::BufError * - Zlib::BufError
* - Zlib::VersionError * - Zlib::VersionError
* - Zlib::InProgressError
* *
*/ */
@ -4996,6 +5019,20 @@ Init_zlib(void)
* *
*/ */
/*
* Document-class: Zlib::InProgressError
*
* Subclass of Zlib::Error. This error is raised when the zlib
* stream is currently in progress.
*
* For example:
*
* inflater = Zlib::Inflate.new
* inflater.inflate(compressed) do
* inflater.inflate(compressed) # Raises Zlib::InProgressError
* end
*/
/* /*
* Document-class: Zlib::GzipFile::Error * Document-class: Zlib::GzipFile::Error
* *

View File

@ -538,30 +538,36 @@ if defined? Zlib
end end
def test_recursive_deflate def test_recursive_deflate
original_gc_stress = GC.stress
GC.stress = true
zd = Zlib::Deflate.new zd = Zlib::Deflate.new
s = SecureRandom.random_bytes(1024**2) s = SecureRandom.random_bytes(1024**2)
assert_raise(Zlib::BufError) do assert_raise(Zlib::InProgressError) do
zd.deflate(s) do zd.deflate(s) do
zd.deflate(s) zd.deflate(s)
end end
end end
ensure ensure
GC.stress = original_gc_stress
zd&.finish zd&.finish
zd&.close zd&.close
end end
def test_recursive_inflate def test_recursive_inflate
original_gc_stress = GC.stress
GC.stress = true
zi = Zlib::Inflate.new zi = Zlib::Inflate.new
s = Zlib.deflate(SecureRandom.random_bytes(1024**2)) s = Zlib.deflate(SecureRandom.random_bytes(1024**2))
assert_raise(Zlib::DataError) do assert_raise(Zlib::InProgressError) do
zi.inflate(s) do zi.inflate(s) do
zi.inflate(s) zi.inflate(s)
end end
end end
ensure ensure
GC.stress = original_gc_stress
zi&.close zi&.close
end end
end end