Skip to content

Commit

Permalink
ext/gmp: gmp_pow fix FPE with large values.
Browse files Browse the repository at this point in the history
even without sanitizers, it is reproducible but with the following

```
<?php
$g = gmp_init(256);
var_dump(gmp_pow($g, PHP_INT_MAX));
```

we get this

```
AddressSanitizer:DEADLYSIGNAL
=================================================================
==286922==ERROR: AddressSanitizer: FPE on unknown address 0x03e8000460ca (pc 0x7faf6c69de5c bp 0x400000000000004 sp 0x7ffe9843c740 T0)
    #0 0x7faf6c69de5c in __pthread_kill_implementation nptl/pthread_kill.c:44
    #1 0x7faf6c649c81 in __GI_raise ../sysdeps/posix/raise.c:26
    #2 0x7faf6db9386c in __gmp_exception (/lib/x86_64-linux-gnu/libgmp.so.10+0xd86c) (BuildId: 1af68a49fe041a5bb48a2915c3d47541f713bb38)
    #3 0x7faf6db938d3 in __gmp_overflow_in_mpz (/lib/x86_64-linux-gnu/libgmp.so.10+0xd8d3) (BuildId: 1af68a49fe041a5bb48a2915c3d47541f713bb38)
    #4 0x7faf6dbac95c in __gmpz_realloc (/lib/x86_64-linux-gnu/libgmp.so.10+0x2695c) (BuildId: 1af68a49fe041a5bb48a2915c3d47541f713bb38)
    #5 0x7faf6dba9038 in __gmpz_n_pow_ui (/lib/x86_64-linux-gnu/libgmp.so.10+0x23038) (BuildId: 1af68a49fe041a5bb48a2915c3d47541f713bb38)
    #6 0x5565ae1ccd9f in zif_gmp_pow /home/dcarlier/Contribs/php-src/ext/gmp/gmp.c:1286
    #7 0x5565aee96ea9 in ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER /home/dcarlier/Contribs/php-src/Zend/zend_vm_execute.h:1312
    #8 0x5565af144320 in execute_ex /home/dcarlier/Contribs/php-src/Zend/zend_vm_execute.h:56075
    #9 0x5565af160f07 in zend_execute /home/dcarlier/Contribs/php-src/Zend/zend_vm_execute.h:60439
    #10 0x5565aed6fafe in zend_execute_scripts /home/dcarlier/Contribs/php-src/Zend/zend.c:1842
    #11 0x5565aeae70a8 in php_execute_script /home/dcarlier/Contribs/php-src/main/main.c:2578
    #12 0x5565af532f4e in do_cli /home/dcarlier/Contribs/php-src/sapi/cli/php_cli.c:964
    #13 0x5565af535877 in main /home/dcarlier/Contribs/php-src/sapi/cli/php_cli.c:1334
    #14 0x7faf6c633d67 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #15 0x7faf6c633e24 in __libc_start_main_impl ../csu/libc-start.c:360
    #16 0x5565adc04040 in _start (/home/dcarlier/Contribs/php-src/sapi/cli/php+0x2604040) (BuildId: 949049955bdf8b7197390b1978a1dfc3ef6fdf38)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: FPE nptl/pthread_kill.c:44 in __pthread_kill_implementation
==286922==ABORTING
```

close GH-16384
  • Loading branch information
devnexen committed Oct 25, 2024
1 parent e1e1e64 commit e0a0e21
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 35 deletions.
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ PHP NEWS
. Fixed bug GH-16411 (gmp_export() can cause overflow). (cmb)
. Fixed bug GH-16501 (gmp_random_bits() can cause overflow).
(David Carlier)
. Fixed gmp_pow() overflow bug with large base/exponents.
(David Carlier)

- MBstring:
. Fixed bug GH-16361 (mb_substr overflow on start/length arguments).
Expand Down
38 changes: 13 additions & 25 deletions ext/gmp/gmp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1282,39 +1282,27 @@ ZEND_FUNCTION(gmp_pow)
RETURN_THROWS();
}

double powmax = log((double)ZEND_LONG_MAX);

if (Z_TYPE_P(base_arg) == IS_LONG && Z_LVAL_P(base_arg) >= 0) {
INIT_GMP_RETVAL(gmpnum_result);
if (exp >= INT_MAX) {
mpz_t base_num, exp_num, mod;
mpz_init(base_num);
mpz_init(exp_num);
mpz_init(mod);
mpz_set_si(base_num, Z_LVAL_P(base_arg));
mpz_set_si(exp_num, exp);
mpz_set_ui(mod, UINT_MAX);
mpz_powm(gmpnum_result, base_num, exp_num, mod);
mpz_clear(mod);
mpz_clear(exp_num);
mpz_clear(base_num);
} else {
mpz_ui_pow_ui(gmpnum_result, Z_LVAL_P(base_arg), exp);
if ((log(Z_LVAL_P(base_arg)) * exp) > powmax) {
zend_value_error("base and exponent overflow");
RETURN_THROWS();
}
mpz_ui_pow_ui(gmpnum_result, Z_LVAL_P(base_arg), exp);
} else {
mpz_ptr gmpnum_base;
zend_ulong gmpnum;
FETCH_GMP_ZVAL(gmpnum_base, base_arg, temp_base, 1);
INIT_GMP_RETVAL(gmpnum_result);
if (exp >= INT_MAX) {
mpz_t exp_num, mod;
mpz_init(exp_num);
mpz_init(mod);
mpz_set_si(exp_num, exp);
mpz_set_ui(mod, UINT_MAX);
mpz_powm(gmpnum_result, gmpnum_base, exp_num, mod);
mpz_clear(mod);
mpz_clear(exp_num);
} else {
mpz_pow_ui(gmpnum_result, gmpnum_base, exp);
gmpnum = mpz_get_ui(gmpnum_base);
if ((log(gmpnum) * exp) > powmax) {
FREE_GMP_TEMP(temp_base);
zend_value_error("base and exponent overflow");
RETURN_THROWS();
}
mpz_pow_ui(gmpnum_result, gmpnum_base, exp);
FREE_GMP_TEMP(temp_base);
}
}
Expand Down
2 changes: 2 additions & 0 deletions ext/gmp/tests/gmp_pow.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
gmp_pow() basic tests
--EXTENSIONS--
gmp
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die("skip this test is for 64bit platform only"); ?>
--FILE--
<?php

Expand Down
77 changes: 77 additions & 0 deletions ext/gmp/tests/gmp_pow_32bits.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
--TEST--
gmp_pow() basic tests
--EXTENSIONS--
gmp
--SKIPIF--
<?php if (PHP_INT_SIZE != 4) die("skip this test is for 32bit platform only"); ?>
--FILE--
<?php

var_dump(gmp_strval(gmp_pow(2,10)));
var_dump(gmp_strval(gmp_pow(-2,10)));
var_dump(gmp_strval(gmp_pow(-2,11)));
var_dump(gmp_strval(gmp_pow("2",10)));
var_dump(gmp_strval(gmp_pow("2",0)));
try {
gmp_pow("2", -1);
} catch (ValueError $exception) {
echo $exception->getMessage() . "\n";
}
var_dump(gmp_strval(gmp_pow("-2",10)));
try {
gmp_pow(20,10);
} catch (ValueError $exception) {
echo $exception->getMessage() . "\n";
}
try {
gmp_pow(50,10);
} catch (ValueError $exception) {
echo $exception->getMessage() . "\n";
}
try {
gmp_pow(50,-5);
} catch (ValueError $exception) {
echo $exception->getMessage() . "\n";
}
try {
$n = gmp_init("20");
gmp_pow($n,10);
} catch (ValueError $exception) {
echo $exception->getMessage() . "\n";
}
try {
$n = gmp_init("-20");
gmp_pow($n,10);
} catch (ValueError $exception) {
echo $exception->getMessage() . "\n";
}
try {
var_dump(gmp_pow(2,array()));
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}

try {
var_dump(gmp_pow(array(),10));
} catch (\TypeError $e) {
echo $e->getMessage() . \PHP_EOL;
}

echo "Done\n";
?>
--EXPECT--
string(4) "1024"
string(4) "1024"
string(5) "-2048"
string(4) "1024"
string(1) "1"
gmp_pow(): Argument #2 ($exponent) must be greater than or equal to 0
string(4) "1024"
base and exponent overflow
base and exponent overflow
gmp_pow(): Argument #2 ($exponent) must be greater than or equal to 0
base and exponent overflow
base and exponent overflow
gmp_pow(): Argument #2 ($exponent) must be of type int, array given
gmp_pow(): Argument #1 ($num) must be of type GMP|string|int, array given
Done
35 changes: 25 additions & 10 deletions ext/gmp/tests/gmp_pow_fpe.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,30 @@ gmp
<?php
$g = gmp_init(256);

var_dump(gmp_pow($g, PHP_INT_MAX));
var_dump(gmp_pow(256, PHP_INT_MAX));
?>
--EXPECTF--
object(GMP)#2 (1) {
["num"]=>
string(%d) "%s"
try {
gmp_pow($g, PHP_INT_MAX);
} catch (\ValueError $e) {
echo $e->getMessage() . PHP_EOL;
}
object(GMP)#2 (1) {
["num"]=>
string(%d) "%s"
try {
gmp_pow(256, PHP_INT_MAX);
} catch (\ValueError $e) {
echo $e->getMessage() . PHP_EOL;
}

try {
gmp_pow(gmp_add(gmp_mul(gmp_init(PHP_INT_MAX), gmp_init(PHP_INT_MAX)), 3), 256);
} catch (\ValueError $e) {
echo $e->getMessage() . PHP_EOL;
}
try {
gmp_pow(gmp_init(PHP_INT_MAX), 256);
} catch (\ValueError $e) {
echo $e->getMessage();
}
?>
--EXPECTF--
base and exponent overflow
base and exponent overflow
base and exponent overflow
base and exponent overflow

0 comments on commit e0a0e21

Please sign in to comment.