From 3f321f09bcb2129a925a22e577b5488168161f0e Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Tue, 5 May 2026 09:31:38 +0900 Subject: [PATCH] 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 --- mrbgems/mruby-regexp/src/re_compile.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/mrbgems/mruby-regexp/src/re_compile.c b/mrbgems/mruby-regexp/src/re_compile.c index 3732f8255..cb5c3b07d 100644 --- a/mrbgems/mruby-regexp/src/re_compile.c +++ b/mrbgems/mruby-regexp/src/re_compile.c @@ -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