[php-src] Issue #24713: ext/gmp: operator-overloaded ** and << crash with SIGFPE on large operand (shift_operator_helper unbounded)
| From: | kunwaratulhax0r | Date: | Mon, 26 Oct 2026 04:06:08 +0000 |
| Subject: | [php-src] Issue #24713: ext/gmp: operator-overloaded ** and << crash with SIGFPE on large operand (shift_operator_helper unbounded) | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-253203@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24713
Author: 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
<?php
var_dump(gmp_init(2) ** 400000000000);
```
```php
<?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():
```c
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():
```c
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)