Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion mrbgems/mruby-regexp/src/re_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,19 @@ compile_error(re_compiler *c, const char *msg)
mrb_exc_new_str(c->mrb, mrb_exc_get_id(c->mrb, MRB_SYM(RegexpError)), emsg));
}

/* Maximum number of instructions in a compiled pattern. Every jump target
lives in re_inst.offset (uint16_t) and a target may be one past the last
instruction, so the whole program has to be addressable by that field.
Without the cap the targets wrap on the way in and the engine jumps to an
unrelated instruction: no exception, no memory error, just a pattern that
stops matching text it describes. The check sits in emit(), the one place
code_len grows, so it covers every producer including insert_inst(). */
#define RE_MAX_CODE_LEN 0xffff

static uint32_t
emit(re_compiler *c, uint8_t op, uint8_t a, uint16_t offset)
{
if (c->code_len >= RE_MAX_CODE_LEN) compile_error(c, "regexp too large");
if (c->code_len >= c->code_capa) {
c->code_capa = c->code_capa ? c->code_capa * 2 : 64;
c->code = (re_inst*)mrb_realloc(c->mrb, c->code, sizeof(re_inst) * c->code_capa);
Expand Down Expand Up @@ -112,7 +122,6 @@ insert_inst(re_compiler *c, uint32_t pos, uint8_t op, uint8_t a, uint16_t offset
if (i == pos) continue;
switch (c->code[i].op) {
case RE_JMP: case RE_SPLIT: case RE_SPLITNG:
if (c->code[i].offset >= 0xffff) break;
if (c->code[i].offset > pos || (c->code[i].offset == pos && i > pos)) {
c->code[i].offset++;
}
Expand Down
22 changes: 22 additions & 0 deletions mrbgems/mruby-regexp/test/regexp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1864,6 +1864,28 @@ def -(other)
assert_equal 0, ("ab\xf0" =~ /[^cd]+$/)
end

assert("Regexp - pattern too large for its jump targets is refused") do
# Jump targets live in a 16-bit field, so a program that outgrows the field
# used to wrap them and jump to an unrelated instruction: the pattern then
# quietly stopped matching text it describes instead of reporting anything.
# Each (?:abc) unit costs three instructions and the bound is on the whole
# program, so the two counts below sit either side of it.
assert_kind_of Regexp, Regexp.new("(?:abc){21844}")
assert_raise_with_message(RegexpError, "regexp too large: /(?:abc){21845}/") do
Regexp.new("(?:abc){21845}")
end

# the shapes that used to answer wrongly rather than raise: a quantifier
# whose skip target is patched past the bound, and an alternation whose
# branch and exit targets both wrap
assert_raise(RegexpError) { Regexp.new("(?:abc){21844}x*y") }
assert_raise(RegexpError) { Regexp.new("(?:abc){30000}(?:y|z)") }

# a quantifier the parser still accepts reaches the bound on its own once
# the repeated atom costs more than one instruction
assert_raise(RegexpError) { Regexp.new("(?:ab){32768}") }
end

assert("Regexp - large non-ASCII character class does not overflow") do
# a class listing tens of thousands of non-ASCII codepoints used to
# overflow the 16-bit range capacity (32768 * 2 wrapped to 0, feeding a
Expand Down
Loading