From: Dmitry Volyntsev Date: Fri, 4 Sep 2026 01:48:11 +0000 (-0700) Subject: Regex: fix decoding of named capture indexes X-Git-Tag: 1.0.2~75 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=be7faf3dc64e72e1a77e0e28dca2d81abefaaea0;p=njs.git Regex: fix decoding of named capture indexes Previously, PCRE name-table capture indexes were decoded through plain char. On signed-char targets, bytes with the high bit set were sign-extended and produced a wild or incorrect capture index used to read a match array. The fix is to decode unsigned bytes through a checked output parameter and to validate every named capture index against the compiled capture count. The issue was introduced in 9496944a (0.3.2). This closes #1088 issue on GitHub. --- diff --git a/external/njs_regex.c b/external/njs_regex.c index cd45afc0..3b4e3339 100644 --- a/external/njs_regex.c +++ b/external/njs_regex.c @@ -573,9 +573,10 @@ njs_regex_is_valid(njs_regex_t *regex) njs_int_t -njs_regex_named_captures(njs_regex_t *regex, njs_str_t *name, int n) +njs_regex_named_capture(njs_regex_t *regex, njs_str_t *name, int n, + uint32_t *capture) { - char *entry; + u_char *entry; if (name == NULL) { return regex->nentries; @@ -587,10 +588,12 @@ njs_regex_named_captures(njs_regex_t *regex, njs_str_t *name, int n) entry = regex->entries + regex->entry_size * n; - name->start = (u_char *) entry + 2; + name->start = entry + 2; name->length = njs_strlen(name->start); - return (entry[0] << 8) + entry[1]; + *capture = (entry[0] << 8) + entry[1]; + + return NJS_OK; } @@ -744,4 +747,3 @@ njs_pcre_free(void *p) #endif - diff --git a/src/njs_regex.h b/src/njs_regex.h index 5cf09cf2..0039ba1f 100644 --- a/src/njs_regex.h +++ b/src/njs_regex.h @@ -32,7 +32,7 @@ typedef struct { int backrefmax; int nentries; int entry_size; - char *entries; + u_char *entries; } njs_regex_t; @@ -76,8 +76,8 @@ NJS_EXPORT njs_int_t njs_regex_compile(njs_regex_t *regex, u_char *source, size_t len, njs_regex_flags_t flags, njs_regex_compile_ctx_t *ctx, njs_trace_t *trace); NJS_EXPORT njs_bool_t njs_regex_is_valid(njs_regex_t *regex); -NJS_EXPORT njs_int_t njs_regex_named_captures(njs_regex_t *regex, - njs_str_t *name, int n); +NJS_EXPORT njs_int_t njs_regex_named_capture(njs_regex_t *regex, + njs_str_t *name, int n, uint32_t *capture); NJS_EXPORT njs_regex_match_data_t *njs_regex_match_data(njs_regex_t *regex, njs_regex_generic_ctx_t *ctx); NJS_EXPORT void njs_regex_match_data_free(njs_regex_match_data_t *match_data, diff --git a/src/njs_regexp.c b/src/njs_regexp.c index be3aa562..3506451c 100644 --- a/src/njs_regexp.c +++ b/src/njs_regexp.c @@ -383,7 +383,7 @@ njs_regexp_pattern_create(njs_vm_t *vm, u_char *start, size_t length, goto fail; } - pattern->ngroups = njs_regex_named_captures(regex, NULL, 0); + pattern->ngroups = njs_regex_named_capture(regex, NULL, 0, NULL); if (pattern->ngroups != 0) { size = sizeof(njs_regexp_group_t) * pattern->ngroups; @@ -399,7 +399,15 @@ njs_regexp_pattern_create(njs_vm_t *vm, u_char *start, size_t length, do { group = &pattern->groups[n]; - group->capture = njs_regex_named_captures(regex, &group->name, n); + ret = njs_regex_named_capture(regex, &group->name, n, + &group->capture); + if (njs_slow_path(ret != NJS_OK + || group->capture >= pattern->ncaptures)) + { + njs_internal_error(vm, "invalid named capture index"); + goto fail; + } + group->hash = njs_djb_hash(group->name.start, group->name.length); n++; diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 9d5d8b6f..2f4b080e 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -10188,6 +10188,14 @@ static njs_unit_test_t njs_test[] = { njs_str("'ABC'.replace(/(?B)/, '|$@')"), njs_str("A|@C") }, + { njs_str("var s = '(?:' + '()'.repeat(127) + '){0}(?a)';" + "new RegExp(s).exec('a').groups.x"), + njs_str("a") }, + + { njs_str("var s = '(?:' + '()'.repeat(383) + '){0}(?a)';" + "new RegExp(s).exec('a').groups.x"), + njs_str("a") }, + { njs_str("'ABCB'.replaceAll(/(?B)/g, '|$@')"), njs_str("A|@C|@") },