]> git.kaiwu.me - njs.git/commitdiff
Regex: fix decoding of named capture indexes
authorDmitry Volyntsev <xeioex@nginx.com>
Fri, 4 Sep 2026 01:48:11 +0000 (18:48 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Fri, 4 Sep 2026 21:53:08 +0000 (14:53 -0700)
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.

external/njs_regex.c
src/njs_regex.h
src/njs_regexp.c
src/test/njs_unit_test.c

index cd45afc051bd0bd67c5499ffff38e011795a6874..3b4e33391c2b95ee51f22ef94758bf6e7ef838eb 100644 (file)
@@ -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
 
-
index 5cf09cf28272c1a65ba04b491c5ceeb1d750e516..0039ba1fb60c55e0b20aedf1b21ca6bc78c9603d 100644 (file)
@@ -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,
index be3aa5629613cbf9520af9a5504ff97a9f10d5d1..3506451cda98448bd9438b9f08044bffb21f4606 100644 (file)
@@ -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++;
index 9d5d8b6f4324cc34f739b62aefb39b4dd887ac05..2f4b080ef667c6c3dda8b8c263957e794a3fe0b8 100644 (file)
@@ -10188,6 +10188,14 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("'ABC'.replace(/(?<b>B)/, '|$<BB$$|>@')"),
       njs_str("A|@C") },
 
+    { njs_str("var s = '(?:' + '()'.repeat(127) + '){0}(?<x>a)';"
+              "new RegExp(s).exec('a').groups.x"),
+      njs_str("a") },
+
+    { njs_str("var s = '(?:' + '()'.repeat(383) + '){0}(?<x>a)';"
+              "new RegExp(s).exec('a').groups.x"),
+      njs_str("a") },
+
     { njs_str("'ABCB'.replaceAll(/(?<b>B)/g, '|$<BB$$|>@')"),
       njs_str("A|@C|@") },