Skip to content

ext/gmp: gmp_pow fix FPE with large values. #16384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can overflow; e.g. gmp_pow(gmp_init(PHP_INT_MAX), 256) is sufficient for overflow on x64 Windows; on LP64, gmp_pow(gmp_add(gmp_mul(gmp_init(PHP_INT_MAX), gmp_init(PHP_INT_MAX)), 3), 256) is likely to overflow here.

It seems to me that the libgmp/mpir developers where so focused to make it fast, that they forgot to make it right.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ll add those two cases, the latter does not crash on linux/64 locally.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that mpz_get_ui() returns unsigned long long int (at least on Windows) which is an unsigned 64bit value. If you assign that to an unsigned long, the value will be truncated (well-defined behavior, but likely not desired here). It might be better to declare mpir_ui gmpnum, and then go from there, but that might cause further issues down the line.

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
Loading