CVE-2026-107891: musl libc: multiple integer overflow in regcomp,
possibly leading to incorrect allocation sizes and out-of-bounds heap
writes


Overview:

The regcomp function in musl libc is affected by multiple integer
overflow issues which can lead to incorrectly-sized allocations and
possibly out-of-bounds heap writes. The conditions for overflow are
triggered by the size and complexity of the regular expression passed
to regcomp, not the data to be matched. In order to be vulnerable, an
application must be processing regular expressions from an untrusted
source using the libc regcomp function.


Impact:

At least denial-of-service in the form of a predictable crash is
possible. Depending on the application and its use of objects on the
heap and of concurrent execution, arbitrary code execution may be
possible.


Affected versions:

The vulnerable code has been present since version 0.5.0.

All subsequent versions up through 1.2.6 are affected.


Mitigation:

All users should apply the attached patch, or upgrade to 1.2.7 once it
becomes available.

In contexts where patching is not an option, measures should be taken
to prevent the processing of regular expressions from untrusted
sources.


Credits:

The first of these issues was reported in private by Julien Voisin.
Subsequent analysis by Szabolcs Nagy and Rich Felker found a pattern
of similar overflows.
>From 7813e9119eb5fe681b6750b8a033a0490bca0941 Mon Sep 17 00:00:00 2001
From: Szabolcs Nagy <[email protected]>
Date: Thu, 23 Apr 2026 21:22:18 +0000
Subject: [PATCH] regex: avoid int overflow in regcomp

regcomp has many int arith ops that may overflow. before tre_expand_ast
int values are limited by 2x the input length n, e.g.:

  input ERE        int field    max value
  aaaaaaaaaaaa...  position     n
  ()()()()()()...  submatch_id  n/2+1
  ()||||||||||...  num_tags     2n-3

so overflow before expand requires large n (and a lot of memory). later
some values can become big due to repetitions:

  input ERE                     int field that overflows
  (((a{255}){255}){255}){255}   position
  ((a|b*){255,}){255,}          num_transitions

num_transitions and memory use can be big without {k,} repetitions too:

  input ERE        what             max value
  (a|a|a|a...|a)*  num_transitions  n^2/4
  (()|a|a|...|a)*  sum tnfa tags    > n^3/16 - n^2

so memory can run out quickly as n grows (with 31bit va space 1700 long
input is enough even without {k,}) about half is tnfa and half is ast
firstpos/lastpos data. but overflow can happen before allocations fail.

fix early overflow by limiting n. the stack usage is in [n,11n] without
repetitions, usually 7n, so the stack limit got adjusted too. the limit
is such that almost all inputs that worked before still work.

overflows during and after expand are fixed by explicit checks via a
check_add helper and xcalloc instead of xmalloc.
---
 src/regex/regcomp.c | 41 +++++++++++++++++++++++++++++++++++------
 1 file changed, 35 insertions(+), 6 deletions(-)

diff --git a/src/regex/regcomp.c b/src/regex/regcomp.c
index b4b81968..a7115b12 100644
--- a/src/regex/regcomp.c
+++ b/src/regex/regcomp.c
@@ -1680,6 +1680,12 @@ tre_add_tags(tre_mem_t mem, tre_stack_t *stack, 
tre_ast_node_t *tree,
   AST to TNFA compilation routines.
 */
 
+/* check if a+b overflows for a,b in [0,INT_MAX] */
+static int check_add(unsigned a, unsigned b)
+{
+       return a + b > INT_MAX;
+}
+
 typedef enum {
   COPY_RECURSE,
   COPY_SET_RESULT_PTR
@@ -1731,6 +1737,8 @@ tre_copy_ast(tre_mem_t mem, tre_stack_t *stack, 
tre_ast_node_t *ast,
                    /* XXX - e.g. [ab] has only one position but two
                       nodes, so we are creating holes in the state space
                       here.  Not fatal, just wastes memory. */
+                   if (check_add(pos, *pos_add))
+                     return REG_ESPACE;
                    pos += *pos_add;
                    num_copied++;
                  }
@@ -1826,6 +1834,8 @@ tre_copy_ast(tre_mem_t mem, tre_stack_t *stack, 
tre_ast_node_t *ast,
          break;
        }
     }
+  if (check_add(*pos_add, num_copied))
+    return REG_ESPACE;
   *pos_add += num_copied;
   return status;
 }
@@ -1870,6 +1880,8 @@ tre_expand_ast(tre_mem_t mem, tre_stack_t *stack, 
tre_ast_node_t *ast,
                tre_literal_t *lit= node->obj;
                if (!IS_SPECIAL(lit) || IS_BACKREF(lit))
                  {
+                   if (check_add(lit->position, pos_add))
+                     return REG_ESPACE;
                    lit->position += pos_add;
                    if (lit->position > max_pos)
                      max_pos = lit->position;
@@ -1998,6 +2010,8 @@ tre_expand_ast(tre_mem_t mem, tre_stack_t *stack, 
tre_ast_node_t *ast,
              }
 
            iter_depth--;
+           if (check_add(pos_add_total, pos_add - pos_add_last))
+             return REG_ESPACE;
            pos_add_total += pos_add - pos_add_last;
            if (iter_depth == 0)
              pos_add = pos_add_total;
@@ -2010,6 +2024,8 @@ tre_expand_ast(tre_mem_t mem, tre_stack_t *stack, 
tre_ast_node_t *ast,
        }
     }
 
+  if (check_add(*position, pos_add_total))
+    return REG_ESPACE;
   *position += pos_add_total;
 
   /* `max_pos' should never be larger than `*position' if the above
@@ -2711,9 +2727,19 @@ regcomp(regex_t *restrict preg, const char *restrict 
regex, int cflags)
   /* Parse context. */
   tre_parse_ctx_t parse_ctx;
 
+  /* Limits: before tre_expand_ast, int values like position, subid or
+   * num_tags are bounded by 2x input length, used stack entries are
+   * bounded by 16x input length. input length is checked upfront to
+   * avoid overflows before expand. later, values that can grow big are
+   * bounds checked via check_add and xcalloc. */
+  const int input_limit = 1024000;
+  const int stack_limit = 8*input_limit;
+  if (strnlen(regex, input_limit) >= input_limit)
+    return REG_ESPACE;
+
   /* Allocate a stack used throughout the compilation process for various
      purposes. */
-  stack = tre_stack_new(512, 1024000, 128);
+  stack = tre_stack_new(512, stack_limit, 128);
   if (!stack)
     return REG_ESPACE;
   /* Allocate a fast memory allocator. */
@@ -2801,9 +2827,12 @@ regcomp(regex_t *restrict preg, const char *restrict 
regex, int cflags)
           for example "a*" or "ab*".   Figure out a simple way to detect
           this possibility. */
   tmp_ast_l = tree;
-  tmp_ast_r = tre_ast_new_literal(mem, 0, 0, parse_ctx.position++);
+  tmp_ast_r = tre_ast_new_literal(mem, 0, 0, parse_ctx.position);
   if (tmp_ast_r == NULL)
     ERROR_EXIT(REG_ESPACE);
+  if (check_add(parse_ctx.position, 1))
+    ERROR_EXIT(REG_ESPACE);
+  parse_ctx.position++;
 
   tree = tre_ast_new_catenation(mem, tmp_ast_l, tmp_ast_r);
   if (tree == NULL)
@@ -2813,22 +2842,22 @@ regcomp(regex_t *restrict preg, const char *restrict 
regex, int cflags)
   if (errcode != REG_OK)
     ERROR_EXIT(errcode);
 
-  counts = xmalloc(sizeof(int) * parse_ctx.position);
+  counts = xcalloc(sizeof(int), parse_ctx.position);
   if (counts == NULL)
     ERROR_EXIT(REG_ESPACE);
 
-  offs = xmalloc(sizeof(int) * parse_ctx.position);
+  offs = xcalloc(sizeof(int), parse_ctx.position);
   if (offs == NULL)
     ERROR_EXIT(REG_ESPACE);
 
-  for (i = 0; i < parse_ctx.position; i++)
-    counts[i] = 0;
   tre_ast_to_tnfa(tree, NULL, counts, NULL);
 
   add = 0;
   for (i = 0; i < parse_ctx.position; i++)
     {
       offs[i] = add;
+      if (check_add(add, counts[i] + 1))
+        ERROR_EXIT(REG_ESPACE);
       add += counts[i] + 1;
       counts[i] = 0;
     }
-- 
2.21.0

Reply via email to