Skip to content

Commit 5253647

Browse files
committed
ext/gmp: Fix segfault when null is encountered on an overloaded operator
And various other issues like inconsistent type errors Closes GH-16015
1 parent fe02fd5 commit 5253647

9 files changed

+110
-98
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ PHP NEWS
6969
(David Carlier)
7070
. Fixed gmp_pow() overflow bug with large base/exponents.
7171
(David Carlier)
72+
. Fixed segfaults and other issues related to operator overloading with
73+
GMP objects. (Girgias)
7274

7375
- MBstring:
7476
. Fixed bug GH-16361 (mb_substr overflow on start/length arguments).

ext/gmp/gmp.c

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -334,25 +334,89 @@ static zend_object *gmp_clone_obj(zend_object *obj) /* {{{ */
334334
}
335335
/* }}} */
336336

337-
static void shift_operator_helper(gmp_binary_ui_op_t op, zval *return_value, zval *op1, zval *op2, zend_uchar opcode) {
338-
zend_long shift = zval_get_long(op2);
337+
static zend_result shift_operator_helper(gmp_binary_ui_op_t op, zval *return_value, zval *op1, zval *op2, zend_uchar opcode) {
338+
zend_long shift = 0;
339+
340+
if (UNEXPECTED(Z_TYPE_P(op2) != IS_LONG)) {
341+
if (UNEXPECTED(!IS_GMP(op2))) {
342+
// For PHP 8.3 and up use zend_try_get_long()
343+
switch (Z_TYPE_P(op2)) {
344+
case IS_DOUBLE:
345+
shift = zval_get_long(op2);
346+
if (UNEXPECTED(EG(exception))) {
347+
return FAILURE;
348+
}
349+
break;
350+
case IS_STRING:
351+
if (is_numeric_str_function(Z_STR_P(op2), &shift, NULL) != IS_LONG) {
352+
goto valueof_op_failure;
353+
}
354+
break;
355+
default:
356+
goto typeof_op_failure;
357+
}
358+
} else {
359+
// TODO We shouldn't cast the GMP object to int here
360+
shift = zval_get_long(op2);
361+
}
362+
} else {
363+
shift = Z_LVAL_P(op2);
364+
}
339365

340366
if (shift < 0) {
341367
zend_throw_error(
342368
zend_ce_value_error, "%s must be greater than or equal to 0",
343369
opcode == ZEND_POW ? "Exponent" : "Shift"
344370
);
345371
ZVAL_UNDEF(return_value);
346-
return;
372+
return FAILURE;
347373
} else {
348374
mpz_ptr gmpnum_op, gmpnum_result;
349375
gmp_temp_t temp;
350376

351-
FETCH_GMP_ZVAL(gmpnum_op, op1, temp, 1);
377+
/* We do not use FETCH_GMP_ZVAL(...); here as we don't use convert_to_gmp()
378+
* as we want to handle the emitted exception ourself. */
379+
if (UNEXPECTED(!IS_GMP(op1))) {
380+
if (UNEXPECTED(Z_TYPE_P(op1) != IS_LONG)) {
381+
goto typeof_op_failure;
382+
}
383+
mpz_init(temp.num);
384+
mpz_set_si(temp.num, Z_LVAL_P(op1));
385+
temp.is_used = 1;
386+
gmpnum_op = temp.num;
387+
} else {
388+
gmpnum_op = GET_GMP_FROM_ZVAL(op1);
389+
temp.is_used = 0;
390+
}
352391
INIT_GMP_RETVAL(gmpnum_result);
353392
op(gmpnum_result, gmpnum_op, (gmp_ulong) shift);
354393
FREE_GMP_TEMP(temp);
394+
return SUCCESS;
395+
}
396+
397+
typeof_op_failure: ;
398+
/* Returning FAILURE without throwing an exception would emit the
399+
* Unsupported operand types: GMP OP TypeOfOp2
400+
* However, this leads to the engine trying to interpret the GMP object as an integer
401+
* and doing the operation that way, which is not something we want. */
402+
const char *op_sigil;
403+
switch (opcode) {
404+
case ZEND_POW:
405+
op_sigil = "**";
406+
break;
407+
case ZEND_SL:
408+
op_sigil = "<<";
409+
break;
410+
case ZEND_SR:
411+
op_sigil = ">>";
412+
break;
413+
EMPTY_SWITCH_DEFAULT_CASE();
355414
}
415+
zend_type_error("Unsupported operand types: %s %s %s", zend_zval_type_name(op1), op_sigil, zend_zval_type_name(op2));
416+
return FAILURE;
417+
valueof_op_failure:
418+
zend_value_error("Number is not an integer string");
419+
return FAILURE;
356420
}
357421

358422
#define DO_BINARY_UI_OP_EX(op, uop, check_b_zero) \
@@ -381,18 +445,15 @@ static zend_result gmp_do_operation_ex(zend_uchar opcode, zval *result, zval *op
381445
case ZEND_MUL:
382446
DO_BINARY_UI_OP(mpz_mul);
383447
case ZEND_POW:
384-
shift_operator_helper(mpz_pow_ui, result, op1, op2, opcode);
385-
return SUCCESS;
448+
return shift_operator_helper(mpz_pow_ui, result, op1, op2, opcode);
386449
case ZEND_DIV:
387450
DO_BINARY_UI_OP_EX(mpz_tdiv_q, gmp_mpz_tdiv_q_ui, 1);
388451
case ZEND_MOD:
389452
DO_BINARY_UI_OP_EX(mpz_mod, gmp_mpz_mod_ui, 1);
390453
case ZEND_SL:
391-
shift_operator_helper(mpz_mul_2exp, result, op1, op2, opcode);
392-
return SUCCESS;
454+
return shift_operator_helper(mpz_mul_2exp, result, op1, op2, opcode);
393455
case ZEND_SR:
394-
shift_operator_helper(mpz_fdiv_q_2exp, result, op1, op2, opcode);
395-
return SUCCESS;
456+
return shift_operator_helper(mpz_fdiv_q_2exp, result, op1, op2, opcode);
396457
case ZEND_BW_OR:
397458
DO_BINARY_OP(mpz_ior);
398459
case ZEND_BW_AND:
@@ -619,6 +680,13 @@ static zend_result convert_to_gmp(mpz_t gmpnumber, zval *val, zend_long base, ui
619680
case IS_STRING: {
620681
return convert_zstr_to_gmp(gmpnumber, Z_STR_P(val), base, arg_pos);
621682
}
683+
case IS_NULL:
684+
/* Just reject null for operator overloading */
685+
if (arg_pos == 0) {
686+
zend_type_error("Number must be of type GMP|string|int, %s given", zend_zval_type_name(val));
687+
return FAILURE;
688+
}
689+
ZEND_FALLTHROUGH;
622690
default: {
623691
zend_long lval;
624692
if (!zend_parse_arg_long_slow(val, &lval, arg_pos)) {

ext/gmp/tests/overloading_with_array.phpt

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,9 @@ TypeError: Number must be of type GMP|string|int, array given
7676
TypeError: Number must be of type GMP|string|int, array given
7777
TypeError: Number must be of type GMP|string|int, array given
7878
TypeError: Number must be of type GMP|string|int, array given
79-
object(GMP)#3 (1) {
80-
["num"]=>
81-
string(1) "1"
82-
}
79+
TypeError: Unsupported operand types: GMP ** array
8380
TypeError: Number must be of type GMP|string|int, array given
8481
TypeError: Number must be of type GMP|string|int, array given
8582
TypeError: Number must be of type GMP|string|int, array given
86-
object(GMP)#2 (1) {
87-
["num"]=>
88-
string(2) "42"
89-
}
90-
object(GMP)#2 (1) {
91-
["num"]=>
92-
string(2) "42"
93-
}
83+
TypeError: Unsupported operand types: GMP << array
84+
TypeError: Unsupported operand types: GMP >> array

ext/gmp/tests/overloading_with_float_string.phpt

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,9 @@ ValueError: Number is not an integer string
7676
ValueError: Number is not an integer string
7777
ValueError: Number is not an integer string
7878
ValueError: Number is not an integer string
79-
object(GMP)#3 (1) {
80-
["num"]=>
81-
string(4) "1764"
82-
}
8379
ValueError: Number is not an integer string
8480
ValueError: Number is not an integer string
8581
ValueError: Number is not an integer string
86-
object(GMP)#2 (1) {
87-
["num"]=>
88-
string(3) "168"
89-
}
90-
object(GMP)#2 (1) {
91-
["num"]=>
92-
string(2) "10"
93-
}
82+
ValueError: Number is not an integer string
83+
ValueError: Number is not an integer string
84+
ValueError: Number is not an integer string

ext/gmp/tests/overloading_with_non_numeric_string.phpt

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,9 @@ ValueError: Number is not an integer string
7676
ValueError: Number is not an integer string
7777
ValueError: Number is not an integer string
7878
ValueError: Number is not an integer string
79-
object(GMP)#3 (1) {
80-
["num"]=>
81-
string(1) "1"
82-
}
8379
ValueError: Number is not an integer string
8480
ValueError: Number is not an integer string
8581
ValueError: Number is not an integer string
86-
object(GMP)#2 (1) {
87-
["num"]=>
88-
string(2) "42"
89-
}
90-
object(GMP)#2 (1) {
91-
["num"]=>
92-
string(2) "42"
93-
}
82+
ValueError: Number is not an integer string
83+
ValueError: Number is not an integer string
84+
ValueError: Number is not an integer string

ext/gmp/tests/overloading_with_null.phpt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
GMP operator overloading does not support null
33
--EXTENSIONS--
44
gmp
5-
--XFAIL--
6-
Test showcasing segfaulting behaviour
75
--FILE--
86
<?php
97

@@ -73,4 +71,14 @@ try {
7371

7472
?>
7573
--EXPECT--
76-
SEGFAULT
74+
TypeError: Number must be of type GMP|string|int, null given
75+
TypeError: Number must be of type GMP|string|int, null given
76+
TypeError: Number must be of type GMP|string|int, null given
77+
TypeError: Number must be of type GMP|string|int, null given
78+
TypeError: Number must be of type GMP|string|int, null given
79+
TypeError: Unsupported operand types: GMP ** null
80+
TypeError: Number must be of type GMP|string|int, null given
81+
TypeError: Number must be of type GMP|string|int, null given
82+
TypeError: Number must be of type GMP|string|int, null given
83+
TypeError: Unsupported operand types: GMP << null
84+
TypeError: Unsupported operand types: GMP >> null

ext/gmp/tests/overloading_with_object_not_stringable.phpt

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -71,30 +71,15 @@ try {
7171
}
7272

7373
?>
74-
--EXPECTF--
74+
--EXPECT--
7575
TypeError: Number must be of type GMP|string|int, stdClass given
7676
TypeError: Number must be of type GMP|string|int, stdClass given
7777
TypeError: Number must be of type GMP|string|int, stdClass given
7878
TypeError: Number must be of type GMP|string|int, stdClass given
7979
TypeError: Number must be of type GMP|string|int, stdClass given
80-
81-
Warning: Object of class stdClass could not be converted to int in %s on line %d
82-
object(GMP)#4 (1) {
83-
["num"]=>
84-
string(2) "42"
85-
}
80+
TypeError: Unsupported operand types: GMP ** stdClass
8681
TypeError: Number must be of type GMP|string|int, stdClass given
8782
TypeError: Number must be of type GMP|string|int, stdClass given
8883
TypeError: Number must be of type GMP|string|int, stdClass given
89-
90-
Warning: Object of class stdClass could not be converted to int in %s on line %d
91-
object(GMP)#3 (1) {
92-
["num"]=>
93-
string(2) "84"
94-
}
95-
96-
Warning: Object of class stdClass could not be converted to int in %s on line %d
97-
object(GMP)#3 (1) {
98-
["num"]=>
99-
string(2) "21"
100-
}
84+
TypeError: Unsupported operand types: GMP << stdClass
85+
TypeError: Unsupported operand types: GMP >> stdClass

ext/gmp/tests/overloading_with_object_stringable.phpt

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -83,24 +83,9 @@ TypeError: Number must be of type GMP|string|int, T given
8383
TypeError: Number must be of type GMP|string|int, T given
8484
TypeError: Number must be of type GMP|string|int, T given
8585
TypeError: Number must be of type GMP|string|int, T given
86-
87-
Warning: Object of class T could not be converted to int in %s on line %d
88-
object(GMP)#4 (1) {
89-
["num"]=>
90-
string(2) "42"
91-
}
86+
TypeError: Unsupported operand types: GMP ** T
9287
TypeError: Number must be of type GMP|string|int, T given
9388
TypeError: Number must be of type GMP|string|int, T given
9489
TypeError: Number must be of type GMP|string|int, T given
95-
96-
Warning: Object of class T could not be converted to int in %s on line %d
97-
object(GMP)#3 (1) {
98-
["num"]=>
99-
string(2) "84"
100-
}
101-
102-
Warning: Object of class T could not be converted to int in %s on line %d
103-
object(GMP)#3 (1) {
104-
["num"]=>
105-
string(2) "21"
106-
}
90+
TypeError: Unsupported operand types: GMP << T
91+
TypeError: Unsupported operand types: GMP >> T

ext/gmp/tests/overloading_with_resource.phpt

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,9 @@ TypeError: Number must be of type GMP|string|int, resource given
7676
TypeError: Number must be of type GMP|string|int, resource given
7777
TypeError: Number must be of type GMP|string|int, resource given
7878
TypeError: Number must be of type GMP|string|int, resource given
79-
object(GMP)#3 (1) {
80-
["num"]=>
81-
string(5) "74088"
82-
}
79+
TypeError: Unsupported operand types: GMP ** resource
8380
TypeError: Number must be of type GMP|string|int, resource given
8481
TypeError: Number must be of type GMP|string|int, resource given
8582
TypeError: Number must be of type GMP|string|int, resource given
86-
object(GMP)#2 (1) {
87-
["num"]=>
88-
string(3) "336"
89-
}
90-
object(GMP)#2 (1) {
91-
["num"]=>
92-
string(1) "5"
93-
}
83+
TypeError: Unsupported operand types: GMP << resource
84+
TypeError: Unsupported operand types: GMP >> resource

0 commit comments

Comments
 (0)