Skip to content

ext/gmp: operator-overloaded ** and << crash with SIGFPE on large operand (shift_operator_helper unbounded) #24713

Closed

Description

@kunwaratulhax0r

Description

GMP object operator overloading for ** (pow) and << (left shift) crashes the PHP process with an uncatchable SIGFPE when given a sufficiently large second operand. This is the same underlying GMP overflow-detection abort as GH-16384 (gmp_pow() function-call form), but reached through a different, separate code path: shift_operator_helper() in ext/gmp/gmp.c, which backs the operator-overloaded forms rather than the gmp_pow()/gmp_mul() function calls.

<?php
var_dump(gmp_init(2) ** 400000000000);
<?php
var_dump(gmp_init(2) << 400000000000);

Either script crashes the process immediately with no catchable PHP-level error.

Root cause

In shift_operator_helper():

if (!have_shift_ui) {
    if (shift < 0 || shift > ULONG_MAX) {
        gmp_shift_operator_range_error(opcode);
        return FAILURE;
    }
    shift_ui = (gmp_ulong) shift;
}

shift is a zend_long (signed 64-bit), so shift > ULONG_MAX can never be true — a signed 64-bit value can never reach the max of an unsigned 64-bit type. The effective upper bound is therefore nonexistent. This function is used for **, <<, and >> via gmp_do_operation_ex():

case ZEND_POW: return shift_operator_helper(mpz_pow_ui, ...);
case ZEND_SL:  return shift_operator_helper(mpz_mul_2exp, ...);
case ZEND_SR:  return shift_operator_helper(mpz_fdiv_q_2exp, ...);

A huge shift reaches mpz_pow_ui/mpz_mul_2exp directly. Both need to allocate a result whose size scales with shift itself, and GMP's own internal size bookkeeping detects the resulting overflow and raises SIGFPE via its internal exception path — uncatchable by PHP. (>>/mpz_fdiv_q_2exp doesn't hit this, since a huge right-shift just truncates to 0 in roughly constant time.)

Not a duplicate of GH-16384

GH-16384 is about ZEND_FUNCTION(gmp_pow) — the function-call form gmp_pow($base, $exp) — which has its own, separate copy of essentially the same broken bound check at a different line. This report is about the operator-overloaded form ($base ** $exp, $base << $n), a structurally distinct code path (shift_operator_helper) that was never touched by any prior fix for GH-16384.

Affected versions

Confirmed on current master. Also affects PHP-8.3/PHP-8.4 — the equivalent code on those branches has no upper-bound check at all (just if (shift < 0)), so the same mpz_pow_ui/mpz_mul_2exp call is reached with an attacker-controlled huge value there too.

PHP Version

master (and PHP-8.3, PHP-8.4)

Operating System

Linux (reproduced in a clean Ubuntu 24.04 container; not OS-specific)

Activity

  1. lazerg commented on Oct 26, 2026

    @lazerg
    Contributor

    This looks like the same GMP problem as in #22351 (comment). Girgias said there that PHP cannot fix it until GMP can handle these errors.

  2. kunwaratulhax0r commented on Oct 26, 2026

    @kunwaratulhax0r
    Author

    Hi @lazerg, thanks for the update. Can I close this as a known issue?

  3. lazerg commented on Oct 26, 2026

    @lazerg
    Contributor

    Yes.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions