You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Potential out-of-bounds read in pcre2_callout_enumerate() due to missing bounds validation for *_ARG opcodes #25400
Hi, I found a potential bug. Could you take a look and provide some feedback?
File: pcre2lib/pcre2_pattern_info.c
Description
The function php_pcre2_callout_enumerate() walks the compiled bytecode without validating bounds. For opcodes with arguments (OP_MARK, OP_COMMIT_ARG, OP_PRUNE_ARG, OP_SKIP_ARG, OP_THEN_ARG), the pointer cc is advanced by PRIV(OP_lengths)[*cc] + cc[1]:
case OP_MARK:
case OP_COMMIT_ARG:
case OP_PRUNE_ARG:
case OP_SKIP_ARG:
case OP_THEN_ARG:
cc += PRIV(OP_lengths)[*cc] + cc[1];
break;
<?php
There is no check that cc[1] stays within a plausible range or that the resulting cc does not skip past the terminating OP_END. If cc[1] holds a large value (e.g., 255), the pointer can jump over OP_END and the while (TRUE) loop continues reading past the end of the valid buffer.
Impact
Denial of Service: crash on reading an unmapped page, or a hang if the random bytes happen to produce a zero/self-referential offset.
The magic_number check does not protect against this: magic_number is just a constant that can be written into attacker-controlled memory. If pcre2_code comes from an untrusted source or the memory is corrupted, the magic check passes while the bytecode body contains arbitrary values.
The issue is relevant when php_pcre2_callout_enumerate is called on patterns derived from untrusted input, and more generally it is a secure-coding defect in a library function that walks bytecode.
Proposed Solution
Add bounds validation when handling *_ARG opcodes, for example:
case OP_MARK:
case OP_COMMIT_ARG:
case OP_PRUNE_ARG:
case OP_SKIP_ARG:
case OP_THEN_ARG:
{
PCRE2_SIZE arglen = cc[1];
if (arglen > 4096) returnPCRE2_ERROR_BADMAGIC; /* conservative bound */
cc += PRIV(OP_lengths)[*cc] + arglen;
}
break;
<?php
A longer-term solution is to pass the size of code into the API, or to document that pcre2_callout_enumerate() requires a trusted pcre2_code produced only by pcre2_compile() in the same process.
Activity
devnexen commentedon Nov 11, 2026
Hi @Ti-Mis if you feel strongly about this bug, this one is to report upstream here. Anyhow, php never uses it. Cheers !