Skip to content

Commit 5aeb46a

Browse files
ext/bcmath: guard BcMath\Number against an uninitialized bc_num
A BcMath\Number whose constructor or __unserialize() never ran has a NULL bc_num. This cannot happen through normal PHP (the class is final with a custom create_object, so newInstanceWithoutConstructor() is rejected and unserialize() routes through __unserialize()), but C code such as an extension calling create_object directly can build one, after which every operation dereferences the NULL bc_num and crashes. Guard the entry points (value stringification, clone, comparison, arithmetic, the operator overloads, the bool cast and property checks) so they throw a clean Error instead. Normal, fully constructed instances are unaffected.
1 parent 95b5b48 commit 5aeb46a

1 file changed

Lines changed: 73 additions & 3 deletions

File tree

ext/bcmath/bcmath.c

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -882,8 +882,23 @@ static zend_always_inline bcmath_number_obj_t *get_bcmath_number_from_zval(const
882882
return get_bcmath_number_from_obj(Z_OBJ_P(zv));
883883
}
884884

885+
/* A BcMath\Number whose constructor or __unserialize() never ran has a NULL
886+
* bc_num. This cannot happen through normal PHP (the class is final with a
887+
* custom create_object, so newInstanceWithoutConstructor is rejected and
888+
* unserialize() routes through __unserialize()), but C code may build one via
889+
* create_object directly. Every operation dereferences ->num, so guard the
890+
* entry points and throw rather than crash. */
891+
static zend_never_inline ZEND_COLD void bcmath_number_throw_uninitialized(void)
892+
{
893+
zend_throw_error(NULL, "The BcMath\\Number object has not been correctly initialized by its constructor");
894+
}
895+
885896
static zend_always_inline zend_string *bcmath_number_value_to_str(bcmath_number_obj_t *intern)
886897
{
898+
if (UNEXPECTED(intern->num == NULL)) {
899+
bcmath_number_throw_uninitialized();
900+
return zend_empty_string;
901+
}
887902
if (intern->value == NULL) {
888903
intern->value = bc_num2str_ex(intern->num, intern->scale);
889904
}
@@ -921,6 +936,11 @@ static zend_object *bcmath_number_clone(zend_object *obj)
921936
bcmath_number_obj_t *original = get_bcmath_number_from_obj(obj);
922937
bcmath_number_obj_t *clone = get_bcmath_number_from_obj(bcmath_number_create(bcmath_number_ce));
923938

939+
if (UNEXPECTED(original->num == NULL)) {
940+
bcmath_number_throw_uninitialized();
941+
return &clone->std;
942+
}
943+
924944
clone->num = bc_copy_num(original->num);
925945
if (original->value) {
926946
clone->value = zend_string_copy(original->value);
@@ -993,6 +1013,10 @@ static int bcmath_number_has_property(zend_object *obj, zend_string *name, int c
9931013
bcmath_number_obj_t *intern = get_bcmath_number_from_obj(obj);
9941014

9951015
if (zend_string_equals(name, ZSTR_KNOWN(ZEND_STR_VALUE))) {
1016+
if (UNEXPECTED(intern->num == NULL)) {
1017+
bcmath_number_throw_uninitialized();
1018+
return 0;
1019+
}
9961020
return !bc_is_zero(intern->num);
9971021
}
9981022

@@ -1007,6 +1031,14 @@ static zend_result bcmath_number_cast_object(zend_object *obj, zval *ret, int ty
10071031
{
10081032
if (type == _IS_BOOL) {
10091033
bcmath_number_obj_t *intern = get_bcmath_number_from_obj(obj);
1034+
if (UNEXPECTED(intern->num == NULL)) {
1035+
/* Return SUCCESS with a placeholder so the engine does not add its
1036+
* own "could not be converted to bool" error on top of ours; the
1037+
* pending exception propagates and the value is discarded. */
1038+
bcmath_number_throw_uninitialized();
1039+
ZVAL_FALSE(ret);
1040+
return SUCCESS;
1041+
}
10101042
ZVAL_BOOL(ret, !bc_is_zero(intern->num));
10111043
return SUCCESS;
10121044
}
@@ -1212,6 +1244,10 @@ static zend_result bc_num_from_obj_or_str_or_long(
12121244
{
12131245
if (obj) {
12141246
const bcmath_number_obj_t *intern = get_bcmath_number_from_obj(obj);
1247+
if (UNEXPECTED(intern->num == NULL)) {
1248+
bcmath_number_throw_uninitialized();
1249+
return FAILURE;
1250+
}
12151251
*num = intern->num;
12161252
if (full_scale) {
12171253
*full_scale = intern->scale;
@@ -1259,11 +1295,15 @@ static zend_result bcmath_number_do_operation(uint8_t opcode, zval *ret_val, zva
12591295
size_t n1_full_scale;
12601296
size_t n2_full_scale;
12611297
if (UNEXPECTED(bc_num_from_obj_or_str_or_long(&n1, &n1_full_scale, obj1, str1, lval1) == FAILURE)) {
1262-
zend_value_error("Left string operand cannot be converted to BcMath\\Number");
1298+
if (!EG(exception)) {
1299+
zend_value_error("Left string operand cannot be converted to BcMath\\Number");
1300+
}
12631301
goto fail;
12641302
}
12651303
if (UNEXPECTED(bc_num_from_obj_or_str_or_long(&n2, &n2_full_scale, obj2, str2, lval2) == FAILURE)) {
1266-
zend_value_error("Right string operand cannot be converted to BcMath\\Number");
1304+
if (!EG(exception)) {
1305+
zend_value_error("Right string operand cannot be converted to BcMath\\Number");
1306+
}
12671307
goto fail;
12681308
}
12691309

@@ -1394,7 +1434,9 @@ static zend_always_inline zend_result bc_num_from_obj_or_str_or_long_with_err(
13941434
{
13951435
size_t full_scale = 0;
13961436
if (UNEXPECTED(bc_num_from_obj_or_str_or_long(num, &full_scale, obj, str, lval) == FAILURE)) {
1397-
zend_argument_value_error(arg_num, "is not well-formed");
1437+
if (!EG(exception)) {
1438+
zend_argument_value_error(arg_num, "is not well-formed");
1439+
}
13981440
return FAILURE;
13991441
}
14001442
if (UNEXPECTED(CHECK_SCALE_OVERFLOW(full_scale))) {
@@ -1459,6 +1501,10 @@ static void bcmath_number_calc_method(INTERNAL_FUNCTION_PARAMETERS, uint8_t opco
14591501
bc_num ret = NULL;
14601502
size_t scale = scale_lval;
14611503
bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1504+
if (UNEXPECTED(intern->num == NULL)) {
1505+
bcmath_number_throw_uninitialized();
1506+
goto fail;
1507+
}
14621508

14631509
switch (opcode) {
14641510
case ZEND_ADD:
@@ -1561,6 +1607,10 @@ PHP_METHOD(BcMath_Number, divmod)
15611607
bc_num rem = NULL;
15621608
size_t scale = scale_lval;
15631609
bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1610+
if (UNEXPECTED(intern->num == NULL)) {
1611+
bcmath_number_throw_uninitialized();
1612+
goto fail;
1613+
}
15641614

15651615
if (scale_is_null) {
15661616
scale = MAX(intern->scale, num_full_scale);
@@ -1626,6 +1676,10 @@ PHP_METHOD(BcMath_Number, powmod)
16261676
}
16271677

16281678
bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1679+
if (UNEXPECTED(intern->num == NULL)) {
1680+
bcmath_number_throw_uninitialized();
1681+
goto cleanup;
1682+
}
16291683
bc_num ret = NULL;
16301684
size_t scale = scale_lval;
16311685
raise_mod_status status = bc_raisemod(intern->num, exponent_num, modulus_num, &ret, scale);
@@ -1687,6 +1741,10 @@ PHP_METHOD(BcMath_Number, sqrt)
16871741
}
16881742

16891743
bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1744+
if (UNEXPECTED(intern->num == NULL)) {
1745+
bcmath_number_throw_uninitialized();
1746+
RETURN_THROWS();
1747+
}
16901748

16911749
size_t scale;
16921750
if (scale_is_null) {
@@ -1742,6 +1800,10 @@ PHP_METHOD(BcMath_Number, compare)
17421800

17431801
size_t scale;
17441802
bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1803+
if (UNEXPECTED(intern->num == NULL)) {
1804+
bcmath_number_throw_uninitialized();
1805+
goto fail;
1806+
}
17451807
if (scale_is_null) {
17461808
scale = MAX(intern->num->n_scale, num->n_scale);
17471809
} else {
@@ -1766,6 +1828,10 @@ static void bcmath_number_floor_or_ceil(INTERNAL_FUNCTION_PARAMETERS, bool is_fl
17661828
ZEND_PARSE_PARAMETERS_NONE();
17671829

17681830
bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1831+
if (UNEXPECTED(intern->num == NULL)) {
1832+
bcmath_number_throw_uninitialized();
1833+
RETURN_THROWS();
1834+
}
17691835

17701836
bc_num ret = bc_floor_or_ceil(intern->num, is_floor);
17711837

@@ -1811,6 +1877,10 @@ PHP_METHOD(BcMath_Number, round)
18111877
}
18121878

18131879
bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1880+
if (UNEXPECTED(intern->num == NULL)) {
1881+
bcmath_number_throw_uninitialized();
1882+
RETURN_THROWS();
1883+
}
18141884

18151885
bc_num ret = NULL;
18161886
size_t scale = bc_round(intern->num, precision, rounding_mode, &ret);

0 commit comments

Comments
 (0)