Avoid excessive backtracking in regex engine during fuzzing

The regex engine is prone to excessive backtracking, leading to
timeouts, especially while fuzzing.
This commit introduces a backtracking counter and a limit of 1000
backtracking steps. When this limit is exceeded during fuzzing, the
regex engine aborts to prevent excessive backtracking. For this, the
FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION macro is used, as per
suggested by the documentation of libFuzzer.
This commit is contained in:
Renata Hodovan 2024-05-29 01:42:44 +02:00
parent 012451d5f3
commit 33367bbfc6
2 changed files with 18 additions and 5 deletions

View file

@ -154,6 +154,7 @@ CFLAGS_DEBUG=$(CFLAGS) -O0
CFLAGS_SMALL=$(CFLAGS) -Os CFLAGS_SMALL=$(CFLAGS) -Os
CFLAGS_OPT=$(CFLAGS) -O2 CFLAGS_OPT=$(CFLAGS) -O2
CFLAGS_NOLTO:=$(CFLAGS_OPT) CFLAGS_NOLTO:=$(CFLAGS_OPT)
CFLAGS_FUZZ=$(CFLAGS_OPT) -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1
ifdef CONFIG_COSMO ifdef CONFIG_COSMO
LDFLAGS+=-s # better to strip by default LDFLAGS+=-s # better to strip by default
else else
@ -255,13 +256,13 @@ qjsc$(EXE): $(OBJDIR)/qjsc.o $(QJS_LIB_OBJS)
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
fuzz_eval: $(OBJDIR)/fuzz_eval.o $(OBJDIR)/fuzz_common.o libquickjs.fuzz.a fuzz_eval: $(OBJDIR)/fuzz_eval.o $(OBJDIR)/fuzz_common.o libquickjs.fuzz.a
$(CC) $(CFLAGS_OPT) $^ -o fuzz_eval $(LIB_FUZZING_ENGINE) $(CC) $(CFLAGS_FUZZ) $^ -o fuzz_eval $(LIB_FUZZING_ENGINE)
fuzz_compile: $(OBJDIR)/fuzz_compile.o $(OBJDIR)/fuzz_common.o libquickjs.fuzz.a fuzz_compile: $(OBJDIR)/fuzz_compile.o $(OBJDIR)/fuzz_common.o libquickjs.fuzz.a
$(CC) $(CFLAGS_OPT) $^ -o fuzz_compile $(LIB_FUZZING_ENGINE) $(CC) $(CFLAGS_FUZZ) $^ -o fuzz_compile $(LIB_FUZZING_ENGINE)
fuzz_regexp: $(OBJDIR)/fuzz_regexp.o $(OBJDIR)/libregexp.fuzz.o $(OBJDIR)/cutils.fuzz.o $(OBJDIR)/libunicode.fuzz.o fuzz_regexp: $(OBJDIR)/fuzz_regexp.o $(OBJDIR)/libregexp.fuzz.o $(OBJDIR)/cutils.fuzz.o $(OBJDIR)/libunicode.fuzz.o
$(CC) $(CFLAGS_OPT) $^ -o fuzz_regexp $(LIB_FUZZING_ENGINE) $(CC) $(CFLAGS_FUZZ) $^ -o fuzz_regexp $(LIB_FUZZING_ENGINE)
libfuzzer: fuzz_eval fuzz_compile fuzz_regexp libfuzzer: fuzz_eval fuzz_compile fuzz_regexp
@ -338,7 +339,7 @@ $(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS_OPT) -c -o $@ $< $(CC) $(CFLAGS_OPT) -c -o $@ $<
$(OBJDIR)/fuzz_%.o: fuzz/fuzz_%.c | $(OBJDIR) $(OBJDIR)/fuzz_%.o: fuzz/fuzz_%.c | $(OBJDIR)
$(CC) $(CFLAGS_OPT) -c -I. -o $@ $< $(CC) $(CFLAGS_FUZZ) -c -I. -o $@ $<
$(OBJDIR)/%.host.o: %.c | $(OBJDIR) $(OBJDIR)/%.host.o: %.c | $(OBJDIR)
$(HOST_CC) $(CFLAGS_OPT) -c -o $@ $< $(HOST_CC) $(CFLAGS_OPT) -c -o $@ $<
@ -359,7 +360,7 @@ $(OBJDIR)/%.debug.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS_DEBUG) -c -o $@ $< $(CC) $(CFLAGS_DEBUG) -c -o $@ $<
$(OBJDIR)/%.fuzz.o: %.c | $(OBJDIR) $(OBJDIR)/%.fuzz.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS_OPT) -fsanitize=fuzzer-no-link -c -o $@ $< $(CC) $(CFLAGS_FUZZ) -fsanitize=fuzzer-no-link -c -o $@ $<
$(OBJDIR)/%.check.o: %.c | $(OBJDIR) $(OBJDIR)/%.check.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -DCONFIG_CHECK_JSVALUE -c -o $@ $< $(CC) $(CFLAGS) -DCONFIG_CHECK_JSVALUE -c -o $@ $<

View file

@ -1927,6 +1927,9 @@ typedef struct {
/* 0 = 8 bit chars, 1 = 16 bit chars, 2 = 16 bit chars, UTF-16 */ /* 0 = 8 bit chars, 1 = 16 bit chars, 2 = 16 bit chars, UTF-16 */
int cbuf_type; int cbuf_type;
int capture_count; int capture_count;
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
int backtrack_count;
#endif
int stack_size_max; int stack_size_max;
BOOL multi_line; BOOL multi_line;
BOOL ignore_case; BOOL ignore_case;
@ -1993,6 +1996,12 @@ static intptr_t lre_exec_backtrack(REExecContext *s, uint8_t **capture,
for(;;) { for(;;) {
// printf("top=%p: pc=%d\n", th_list.top, (int)(pc - (bc_buf + RE_HEADER_LEN))); // printf("top=%p: pc=%d\n", th_list.top, (int)(pc - (bc_buf + RE_HEADER_LEN)));
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (++s->backtrack_count > 1000) {
return -1; // backtracking limit exceeded
}
#endif
opcode = *pc++; opcode = *pc++;
switch(opcode) { switch(opcode) {
case REOP_match: case REOP_match:
@ -2399,6 +2408,9 @@ int lre_exec(uint8_t **capture,
s->ignore_case = (re_flags & LRE_FLAG_IGNORECASE) != 0; s->ignore_case = (re_flags & LRE_FLAG_IGNORECASE) != 0;
s->is_unicode = (re_flags & LRE_FLAG_UNICODE) != 0; s->is_unicode = (re_flags & LRE_FLAG_UNICODE) != 0;
s->capture_count = bc_buf[RE_HEADER_CAPTURE_COUNT]; s->capture_count = bc_buf[RE_HEADER_CAPTURE_COUNT];
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
s->backtrack_count = 0;
#endif
s->stack_size_max = bc_buf[RE_HEADER_STACK_SIZE]; s->stack_size_max = bc_buf[RE_HEADER_STACK_SIZE];
s->cbuf = cbuf; s->cbuf = cbuf;
s->cbuf_end = cbuf + (clen << cbuf_type); s->cbuf_end = cbuf + (clen << cbuf_type);