Details from PCRE2 team
We have released PCRE2 10.46, which is a security-only release, to address CVE-2025-58050.
Compared to 10.45, this release has only a minimal code change to prevent a read-past-the-end memory error, of arbitrary length. An attacker-controlled regex pattern is required, and it cannot be triggered by providing crafted subject (match) text. The (*ACCEPT) and (*scs:) pattern features must be used together.
Release 10.44 and earlier are not affected.
This could have implications of denial-of-service or information disclosure, and could potentially be used to escalate other vulnerabilities in a system (such as information disclosure being used to escalate the severity of an unrelated bug in another system).
Details as reported by Google Big Sleep security team below
We are tracking this issue with the public ID BIGSLEEP-438254142
. Please use this identifier for reference in any future communication.
Vulnerability Details
A heap-buffer-overflow read vulnerability exists in the PCRE2 regular expression matching engine, specifically within the handling of the (*scs:...)
(Scan SubString) verb when combined with (*ACCEPT)
in src/pcre2_match.c
.
The (*scs:...)
verb is used to match a portion of the pattern against a specific captured substring rather than the main subject string. To achieve this, the engine temporarily modifies the match boundaries, specifically the mb->end_subject
and mb->true_end_subject
pointers in the match block, restricting them to the bounds of the specified substring[1]
.
5815: case OP_ASSERT_SCS:
5816: {
...
5876: Lsaved_end_subject = mb->end_subject;
5877: Ltrue_end_extra = mb->true_end_subject - mb->end_subject;
5878: Lsaved_eptr = Feptr;
5879: Lsaved_moptions = mb->moptions;
5880:
5881: Feptr = mb->start_subject + Fovector[offset];
5882: mb->true_end_subject = mb->end_subject =
5883: mb->start_subject + Fovector[offset + 1]; // *** 1 ***
5884: mb->moptions &= ~PCRE2_NOTEOL;
The vulnerability occurs when the pattern inside the (*scs:...)
block succeeds via an immediate (*ACCEPT) [2]
. In this specific scenario,, the engine correctly restores the current subject pointer (Feptr
) to its position before the SCS assertion[3]
, but it fails to restore the original mb->end_subject
and mb->true_end_subject
values. These pointers remain incorrectly pointing to the end of the scanned substring.
5886: Lframe_type = GF_NOCAPTURE | Fop;
5887: for (;;)
5888: {
5889: group_frame_type = Lframe_type;
5890: RMATCH(Fecode + 1 + LINK_SIZE + Lextra_size, RM38);
5891: if (rrc == MATCH_ACCEPT)
5892: {
5893: memcpy(Fovector,
5894: (char *)assert_accept_frame + offsetof(heapframe, ovector),
5895: assert_accept_frame->offset_top * sizeof(PCRE2_SIZE));
5896: Foffset_top = assert_accept_frame->offset_top;
5897: Fmark = assert_accept_frame->mark;
5898: break; // *** 2 ***
5899: }
5900:
5901: if (rrc != MATCH_NOMATCH && rrc != MATCH_THEN)
5902: {
5903: mb->end_subject = Lsaved_end_subject;
5904: mb->true_end_subject = mb->end_subject + Ltrue_end_extra;
5905: mb->moptions = Lsaved_moptions;
5906: RRETURN(rrc);
5907: }
5908:
5909: Fecode += GET(Fecode, 1);
5910: if (*Fecode != OP_ALT)
5911: {
5912: mb->end_subject = Lsaved_end_subject;
5913: mb->true_end_subject = mb->end_subject + Ltrue_end_extra;
5914: mb->moptions = Lsaved_moptions;
5915: RRETURN(MATCH_NOMATCH);
5916: }
5917: Lextra_size = 0;
5918: }
5919:
5920: do Fecode += GET(Fecode, 1); while (*Fecode == OP_ALT);
5921: Fecode += 1 + LINK_SIZE;
5922: Feptr = Lsaved_eptr; // *** 3 ***
5923: break;
If a subsequent operation in the pattern, such as a backreference (e.g., \2
), is attempted, the match_ref
function is called. This function performs a bounds check (pcre2_match.c:488
) before comparing the backreference:
488: if ((PCRE2_SIZE)(mb->end_subject - eptr) < length) return 1;
489: if (memcmp(p, eptr, CU2BYTES(length)) != 0) return -1; /* No match */
If the current pointer (eptr
) is already beyond the incorrectly shortened mb->end_subject
, the subtraction results in an underflow. Because the result is cast to PCRE2_SIZE
(an unsigned type), the result is a very large positive number. This causes the bounds check to pass incorrectly (the large number is not less than length
).
The code then proceeds to call memcmp
using the current pointer eptr
, which is beyond the actual end of the allocated haystack buffer, leading to a heap-buffer-overflow read.
This vulnerability may potentially lead to information disclosure if the out-of-bounds data read during the memcmp
affects the final match result in a way observable by the attacker.
Affected Version(s)
The issue has been successfully reproduced:
Reproduction
The vulnerability can be demonstrated by running pcre2test
binary compiled with AddressSanitizer on a malicious input.
Test Case
/(a)(b+)(*scs:(1)a(*ACCEPT))(\2)/
abbb
Build Instructions
git clone https://github.com/PCRE2Project/pcre2.git
cd pcre2
./autogen.sh
CFLAGS="-fsanitize=address -g -fno-omit-frame-pointer" LDFLAGS="-fsanitize=address" ./configure
make -j$(nproc)
Command
LD_LIBRARY_PATH=./.libs ./.libs/pcre2test repro.txt
ASan Report
==2249605==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x529000004200 at pc 0x7fdc916c4162 bp 0x7ffe6a0356c0 sp 0x7ffe6a034e80
READ of size 3 at 0x529000004200 thread T0
#0 0x7fdc916c4161 in MemcmpInterceptorCommon(void*, int (*)(void const*, void const*, unsigned long), void const*, void const*, unsigned long) ../../..
/../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:814
#1 0x7fdc916c4b33 in memcmp ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:845
#2 0x7fdc916c4b33 in memcmp ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:840
#3 0x7fdc91543cee in match_ref src/pcre2_match.c:489
#4 0x7fdc9159f629 in match src/pcre2_match.c:5262
#5 0x7fdc915b2c4f in pcre2_match_8 src/pcre2_match.c:7901
#6 0x563f4b3ac41c in process_data src/pcre2test.c:9145
#7 0x563f4b3b3154 in main src/pcre2test.c:10869
#8 0x7fdc912faca7 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#9 0x7fdc912fad64 in __libc_start_main_impl ../csu/libc-start.c:360
#10 0x563f4b38f9c0 in _start (pcre2/.libs/pcre2test+0x199c0) (BuildId: 327568290
45f06cb308e0e3da83b56f6be796b50)
0x529000004200 is located 0 bytes after 16384-byte region [0x529000000200,0x529000004200)
allocated by thread T0 here:
#0 0x7fdc916f3b58 in realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:85
#1 0x563f4b3a7700 in process_data src/pcre2test.c:8063
#2 0x563f4b3b3154 in main src/pcre2test.c:10869
#3 0x7fdc912faca7 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
SUMMARY: AddressSanitizer: heap-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:814 in MemcmpInterceptorCom
mon(void*, int (*)(void const*, void const*, unsigned long), void const*, void const*, unsigned long)
Shadow bytes around the buggy address:
0x529000003f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x529000004000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x529000004080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x529000004100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x529000004180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x529000004200:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x529000004280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x529000004300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x529000004380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x529000004400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x529000004480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==2249605==ABORTING
Reporter Credit
Google Big Sleep
Disclosure Policy
This bug is subject to a 90-day disclosure deadline. If a fix for this issue is made available to users before the end of the 90-day deadline, this bug report will become public 30 days after the fix was made available. Otherwise, this bug report will become public at the deadline. The scheduled deadline is 2025-11-10.
For more information, visit https://goo.gle/bigsleep
Details from PCRE2 team
We have released PCRE2 10.46, which is a security-only release, to address CVE-2025-58050.
Compared to 10.45, this release has only a minimal code change to prevent a read-past-the-end memory error, of arbitrary length. An attacker-controlled regex pattern is required, and it cannot be triggered by providing crafted subject (match) text. The (*ACCEPT) and (*scs:) pattern features must be used together.
Release 10.44 and earlier are not affected.
This could have implications of denial-of-service or information disclosure, and could potentially be used to escalate other vulnerabilities in a system (such as information disclosure being used to escalate the severity of an unrelated bug in another system).
Details as reported by Google Big Sleep security team below
We are tracking this issue with the public ID
BIGSLEEP-438254142
. Please use this identifier for reference in any future communication.Vulnerability Details
A heap-buffer-overflow read vulnerability exists in the PCRE2 regular expression matching engine, specifically within the handling of the
(*scs:...)
(Scan SubString) verb when combined with(*ACCEPT)
insrc/pcre2_match.c
.The
(*scs:...)
verb is used to match a portion of the pattern against a specific captured substring rather than the main subject string. To achieve this, the engine temporarily modifies the match boundaries, specifically themb->end_subject
andmb->true_end_subject
pointers in the match block, restricting them to the bounds of the specified substring[1]
.The vulnerability occurs when the pattern inside the
(*scs:...)
block succeeds via an immediate(*ACCEPT) [2]
. In this specific scenario,, the engine correctly restores the current subject pointer (Feptr
) to its position before the SCS assertion[3]
, but it fails to restore the originalmb->end_subject
andmb->true_end_subject
values. These pointers remain incorrectly pointing to the end of the scanned substring.If a subsequent operation in the pattern, such as a backreference (e.g.,
\2
), is attempted, thematch_ref
function is called. This function performs a bounds check (pcre2_match.c:488
) before comparing the backreference:If the current pointer (
eptr
) is already beyond the incorrectly shortenedmb->end_subject
, the subtraction results in an underflow. Because the result is cast toPCRE2_SIZE
(an unsigned type), the result is a very large positive number. This causes the bounds check to pass incorrectly (the large number is not less thanlength
).The code then proceeds to call
memcmp
using the current pointereptr
, which is beyond the actual end of the allocated haystack buffer, leading to a heap-buffer-overflow read.This vulnerability may potentially lead to information disclosure if the out-of-bounds data read during the
memcmp
affects the final match result in a way observable by the attacker.Affected Version(s)
The issue has been successfully reproduced:
at HEAD (commit
7c7a27442279f5b53cd9c1c320e7a5fa662b48e7
)in stable release
10.45
Reproduction
The vulnerability can be demonstrated by running
pcre2test
binary compiled with AddressSanitizer on a malicious input.Test Case
Build Instructions
Command
ASan Report
Reporter Credit
Google Big Sleep
Disclosure Policy
This bug is subject to a 90-day disclosure deadline. If a fix for this issue is made available to users before the end of the 90-day deadline, this bug report will become public 30 days after the fix was made available. Otherwise, this bug report will become public at the deadline. The scheduled deadline is 2025-11-10.
For more information, visit https://goo.gle/bigsleep