mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
mruby-regexp: cap bt_match recursion depth
The backtracking engine recurses via C function calls at RE_SPLIT, RE_SPLITNG, RE_SAVE, RE_LOOKAHEAD, RE_NEG_LOOKAHEAD, RE_LOOKBEHIND, and RE_NEG_LOOKBEHIND. Patterns like `(?=)+` make the engine recurse without consuming input, exhausting the C stack and triggering SIGSEGV long before MRB_REGEXP_STEP_LIMIT is reached (each recursion charges only ~1 step, but each frame costs ~150 bytes of stack). Reported by ClusterFuzz testcase clusterfuzz-testcase-minimized-mruby_fuzzer-4653331195953152. Add an integer recursion-depth counter passed alongside the step counter, and abort the current branch with FALSE when it exceeds MRB_REGEXP_RECURSION_LIMIT (default 1000, configurable like STEP_LIMIT). Legitimate patterns nest only a few levels; pathological inputs bail without crashing the VM. Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,12 @@ typedef struct mrb_regexp_pattern {
|
||||
#define MRB_REGEXP_STEP_LIMIT 1000000
|
||||
#endif
|
||||
|
||||
/* Recursion-depth limit for bt_match: bounds C stack growth on
|
||||
patterns like `(?=)+` that recurse without consuming input. */
|
||||
#ifndef MRB_REGEXP_RECURSION_LIMIT
|
||||
#define MRB_REGEXP_RECURSION_LIMIT 1000
|
||||
#endif
|
||||
|
||||
/* Maximum captures */
|
||||
#define RE_MAX_CAPTURES 32
|
||||
|
||||
|
||||
@@ -388,8 +388,10 @@ pike_vm(mrb_state *mrb, const mrb_regexp_pattern *pat,
|
||||
*/
|
||||
static mrb_bool
|
||||
bt_match(const mrb_regexp_pattern *pat, const char *str, const char *str_end,
|
||||
const char *sp, uint32_t pc, int *captures, int ncap, int *steps)
|
||||
const char *sp, uint32_t pc, int *captures, int ncap, int *steps,
|
||||
int depth)
|
||||
{
|
||||
if (depth > MRB_REGEXP_RECURSION_LIMIT) return FALSE;
|
||||
while (pc < pat->code_len) {
|
||||
if (++(*steps) > MRB_REGEXP_STEP_LIMIT) return FALSE;
|
||||
|
||||
@@ -428,12 +430,12 @@ bt_match(const mrb_regexp_pattern *pat, const char *str, const char *str_end,
|
||||
break;
|
||||
|
||||
case RE_SPLIT:
|
||||
if (bt_match(pat, str, str_end, sp, pc + 1, captures, ncap, steps)) return TRUE;
|
||||
if (bt_match(pat, str, str_end, sp, pc + 1, captures, ncap, steps, depth + 1)) return TRUE;
|
||||
pc = inst.offset;
|
||||
break;
|
||||
|
||||
case RE_SPLITNG:
|
||||
if (bt_match(pat, str, str_end, sp, inst.offset, captures, ncap, steps)) return TRUE;
|
||||
if (bt_match(pat, str, str_end, sp, inst.offset, captures, ncap, steps, depth + 1)) return TRUE;
|
||||
pc++;
|
||||
break;
|
||||
|
||||
@@ -443,7 +445,7 @@ bt_match(const mrb_regexp_pattern *pat, const char *str, const char *str_end,
|
||||
if (slot < ncap) {
|
||||
int old = captures[slot];
|
||||
captures[slot] = (int)(sp - str);
|
||||
if (bt_match(pat, str, str_end, sp, pc + 1, captures, ncap, steps)) return TRUE;
|
||||
if (bt_match(pat, str, str_end, sp, pc + 1, captures, ncap, steps, depth + 1)) return TRUE;
|
||||
captures[slot] = old;
|
||||
}
|
||||
return FALSE;
|
||||
@@ -502,13 +504,13 @@ bt_match(const mrb_regexp_pattern *pat, const char *str, const char *str_end,
|
||||
break;
|
||||
|
||||
case RE_LOOKAHEAD:
|
||||
if (!bt_match(pat, str, str_end, sp, pc + 1, captures, ncap, steps))
|
||||
if (!bt_match(pat, str, str_end, sp, pc + 1, captures, ncap, steps, depth + 1))
|
||||
return FALSE;
|
||||
pc = inst.offset;
|
||||
break;
|
||||
|
||||
case RE_NEG_LOOKAHEAD:
|
||||
if (bt_match(pat, str, str_end, sp, pc + 1, captures, ncap, steps))
|
||||
if (bt_match(pat, str, str_end, sp, pc + 1, captures, ncap, steps, depth + 1))
|
||||
return FALSE;
|
||||
pc = inst.offset;
|
||||
break;
|
||||
@@ -517,7 +519,7 @@ bt_match(const mrb_regexp_pattern *pat, const char *str, const char *str_end,
|
||||
{
|
||||
int lb_len = inst.a;
|
||||
if (sp - str < lb_len) return FALSE; /* not enough text before */
|
||||
if (!bt_match(pat, str, str_end, sp - lb_len, pc + 1, captures, ncap, steps))
|
||||
if (!bt_match(pat, str, str_end, sp - lb_len, pc + 1, captures, ncap, steps, depth + 1))
|
||||
return FALSE;
|
||||
pc = inst.offset;
|
||||
}
|
||||
@@ -527,7 +529,7 @@ bt_match(const mrb_regexp_pattern *pat, const char *str, const char *str_end,
|
||||
{
|
||||
int lb_len = inst.a;
|
||||
if (sp - str >= lb_len) {
|
||||
if (bt_match(pat, str, str_end, sp - lb_len, pc + 1, captures, ncap, steps))
|
||||
if (bt_match(pat, str, str_end, sp - lb_len, pc + 1, captures, ncap, steps, depth + 1))
|
||||
return FALSE;
|
||||
}
|
||||
/* if not enough text before, negative lookbehind succeeds */
|
||||
@@ -567,7 +569,7 @@ backtrack_exec(mrb_state *mrb, const mrb_regexp_pattern *pat,
|
||||
memset(caps, -1, sizeof(int) * ncap);
|
||||
int steps = 0;
|
||||
|
||||
if (bt_match(pat, str, str_end, sp, 0, caps, ncap, &steps)) {
|
||||
if (bt_match(pat, str, str_end, sp, 0, caps, ncap, &steps, 0)) {
|
||||
if (captures) {
|
||||
int copy = ncap < captures_size ? ncap : captures_size;
|
||||
memcpy(captures, caps, sizeof(int) * copy);
|
||||
|
||||
Reference in New Issue
Block a user