mruby-regexp: fix leak and UAF on compile error paths

compile_error is the chokepoint for all regex-compile errors;
mrb_raisef longjmps out of re_compile, abandoning the stack-local
re_compiler struct. Three connected bugs:

1. Memory leak: c->code and c->classes (grown by emit/add_class
   via mrb_realloc) were never freed before raising, leaking on
   any compile error like /[/. c->stripped was already cleaned up
   here for the same reason; the other two buffers were missed.

2. Use-after-free: c->src aliases c->stripped when RE_FLAG_EXTENDED
   is set, but the original code freed c->stripped before passing
   c->src to mrb_raisef's "%s" formatter. Format the message into
   an mrb_value first (mruby's GC-managed string survives the
   longjmp), then free, then raise.

3. Heap-buffer-overflow: strip_extended returns a non-NUL-terminated
   buffer of size len. Even with format-before-free, "%s" called
   strlen and read past the buffer end. Use mruby's %l directive
   which takes an explicit (char*, size_t) and avoids strlen.

Reported by OSS-Fuzz (clusterfuzz testcase 5394267353972736).

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-05-05 09:31:38 +09:00
parent ddcbd2dc90
commit 3f321f09bc
+16 -1
View File
@@ -37,9 +37,24 @@ static void compile_alt(re_compiler *c); /* forward */
static void
compile_error(re_compiler *c, const char *msg)
{
/* Format the message before freeing c->stripped (which may alias c->src
in extended mode). c->src is not NUL-terminated, so use %l with the
explicit length from c->src_end. */
mrb_value emsg = mrb_format(c->mrb, "%s: /%l/",
msg, c->src, (size_t)(c->src_end - c->src));
/* Free compile buffers before raising, since mrb_exc_raise longjmps out
and the stack-local re_compiler is abandoned without a chance to clean
up. mrb_free doesn't trigger GC, so emsg stays valid across these. */
mrb_free(c->mrb, c->code);
c->code = NULL;
mrb_free(c->mrb, c->classes);
c->classes = NULL;
if (c->stripped) mrb_free(c->mrb, c->stripped);
c->stripped = NULL;
mrb_raisef(c->mrb, mrb_exc_get_id(c->mrb, MRB_SYM(RegexpError)), "%s: /%s/", msg, c->src);
mrb_exc_raise(c->mrb,
mrb_exc_new_str(c->mrb, mrb_exc_get_id(c->mrb, MRB_SYM(RegexpError)), emsg));
}
static uint32_t