Skip to content

Potential out-of-bounds read in pcre2_callout_enumerate() due to missing bounds validation for *_ARG opcodes #25400

Closed as not planned

Description

@Ti-Mis

Description

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) return PCRE2_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.

PHP Version

PHP 8.3.31

Operating System

No response

Activity

  1. devnexen commented on Nov 11, 2026

    @devnexen
    Member

    Hi @Ti-Mis if you feel strongly about this bug, this one is to report upstream here. Anyhow, php never uses it. Cheers !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions