Skip to content

Commit 3c45d02

Browse files
GH-22905: avoid truncation on null bytes in reflection exceptions
In addition to the fixes within the reflection extension, support for error strings with null bytes was added to `zend_throw_error()` and `zend_throw_exception_ex()`. The changes to `zend_throw_exception_ex()` are a partial backport of #16684.
1 parent 9ba6ec3 commit 3c45d02

28 files changed

Lines changed: 149 additions & 59 deletions

Zend/zend.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1772,7 +1772,6 @@ ZEND_API void zend_free_recorded_errors(void)
17721772
ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const char *format, ...) /* {{{ */
17731773
{
17741774
va_list va;
1775-
char *message = NULL;
17761775

17771776
if (!exception_ce) {
17781777
exception_ce = zend_ce_error;
@@ -1784,16 +1783,20 @@ ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const c
17841783
}
17851784

17861785
va_start(va, format);
1787-
zend_vspprintf(&message, 0, format, va);
1786+
zend_string *message = zend_vstrpprintf(0, format, va);
17881787

17891788
//TODO: we can't convert compile-time errors to exceptions yet???
17901789
if (EG(current_execute_data) && !CG(in_compilation)) {
1791-
zend_throw_exception(exception_ce, message, 0);
1790+
// %S is used for zend_string pointers by smart str printing, but normally
1791+
// is for wide character strings and so compilers complain if this is inline
1792+
// Use "%S" so that the message can contain null bytes.
1793+
const char *format = "%S";
1794+
zend_throw_exception_ex(exception_ce, 0, format, message);
17921795
} else {
1793-
zend_error_noreturn(E_ERROR, "%s", message);
1796+
zend_error_noreturn(E_ERROR, "%s", ZSTR_VAL(message));
17941797
}
17951798

1796-
efree(message);
1799+
zend_string_release(message);
17971800
va_end(va);
17981801
}
17991802
/* }}} */

Zend/zend_exceptions.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -888,14 +888,13 @@ ZEND_API ZEND_COLD zend_object *zend_throw_exception(zend_class_entry *exception
888888
ZEND_API ZEND_COLD zend_object *zend_throw_exception_ex(zend_class_entry *exception_ce, zend_long code, const char *format, ...) /* {{{ */
889889
{
890890
va_list arg;
891-
char *message;
892891
zend_object *obj;
893892

894893
va_start(arg, format);
895-
zend_vspprintf(&message, 0, format, arg);
894+
zend_string *msg_str = zend_vstrpprintf(0, format, arg);
896895
va_end(arg);
897-
obj = zend_throw_exception(exception_ce, message, code);
898-
efree(message);
896+
obj = zend_throw_exception_zstr(exception_ce, msg_str, code);
897+
zend_string_release(msg_str);
899898
return obj;
900899
}
901900
/* }}} */

ext/reflection/php_reflection.c

Lines changed: 113 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,7 +1298,10 @@ static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attribut
12981298
if (name && (flags & REFLECTION_ATTRIBUTE_IS_INSTANCEOF)) {
12991299
if (NULL == (base = zend_lookup_class(name))) {
13001300
if (!EG(exception)) {
1301-
zend_throw_error(NULL, "Class \"%s\" not found", ZSTR_VAL(name));
1301+
// %S is used for zend_string pointers by smart str printing, but normally
1302+
// is for wide character strings and so compilers complain if this is inline
1303+
const char *format = "Class \"%S\" not found";
1304+
zend_throw_error(NULL, format, name);
13021305
}
13031306

13041307
RETURN_THROWS();
@@ -1704,8 +1707,11 @@ ZEND_METHOD(ReflectionFunction, __construct)
17041707
}
17051708

17061709
if (fptr == NULL) {
1710+
// %S is used for zend_string pointers by smart str printing, but normally
1711+
// is for wide character strings and so compilers complain if this is inline
1712+
const char *format = "Function %S() does not exist";
17071713
zend_throw_exception_ex(reflection_exception_ptr, 0,
1708-
"Function %s() does not exist", ZSTR_VAL(fname));
1714+
format, fname);
17091715
RETURN_THROWS();
17101716
}
17111717
}
@@ -2522,8 +2528,11 @@ ZEND_METHOD(ReflectionParameter, __construct)
25222528
zend_string_release(lcname);
25232529
}
25242530
if (!fptr) {
2531+
// %S is used for zend_string pointers by smart str printing, but normally
2532+
// is for wide character strings and so compilers complain if this is inline
2533+
const char *format = "Function %S() does not exist";
25252534
zend_throw_exception_ex(reflection_exception_ptr, 0,
2526-
"Function %s() does not exist", Z_STRVAL_P(reference));
2535+
format, Z_STR_P(reference));
25272536
RETURN_THROWS();
25282537
}
25292538
ce = fptr->common.scope;
@@ -2550,8 +2559,11 @@ ZEND_METHOD(ReflectionParameter, __construct)
25502559
return;
25512560
}
25522561
if ((ce = zend_lookup_class(name)) == NULL) {
2562+
// %S is used for zend_string pointers by smart str printing, but normally
2563+
// is for wide character strings and so compilers complain if this is inline
2564+
const char *format = "Class \"%S\" does not exist";
25532565
zend_throw_exception_ex(reflection_exception_ptr, 0,
2554-
"Class \"%s\" does not exist", ZSTR_VAL(name));
2566+
format, name);
25552567
zend_string_release(name);
25562568
RETURN_THROWS();
25572569
}
@@ -2570,8 +2582,11 @@ ZEND_METHOD(ReflectionParameter, __construct)
25702582
/* nothing to do. don't set is_closure since is the invoke handler,
25712583
not the closure itself */
25722584
} else if ((fptr = zend_hash_find_ptr(&ce->function_table, lcname)) == NULL) {
2585+
// %S is used for zend_string pointers by smart str printing, but normally
2586+
// is for wide character strings and so compilers complain if this is inline
2587+
const char *format = "Method %S::%S() does not exist";
25732588
zend_throw_exception_ex(reflection_exception_ptr, 0,
2574-
"Method %s::%s() does not exist", ZSTR_VAL(ce->name), ZSTR_VAL(name));
2589+
format, ce->name, name);
25752590
zend_string_release(name);
25762591
zend_string_release(lcname);
25772592
RETURN_THROWS();
@@ -3388,7 +3403,10 @@ static void instantiate_reflection_method(INTERNAL_FUNCTION_PARAMETERS, bool is_
33883403
if (class_name) {
33893404
if ((ce = zend_lookup_class(class_name)) == NULL) {
33903405
if (!EG(exception)) {
3391-
zend_throw_exception_ex(reflection_exception_ptr, 0, "Class \"%s\" does not exist", ZSTR_VAL(class_name));
3406+
// %S is used for zend_string pointers by smart str printing, but normally
3407+
// is for wide character strings and so compilers complain if this is inline
3408+
const char *format = "Class \"%S\" does not exist";
3409+
zend_throw_exception_ex(reflection_exception_ptr, 0, format, class_name);
33923410
}
33933411
zend_string_release(class_name);
33943412
RETURN_THROWS();
@@ -3414,8 +3432,16 @@ static void instantiate_reflection_method(INTERNAL_FUNCTION_PARAMETERS, bool is_
34143432
/* do nothing, mptr already set */
34153433
} else if ((mptr = zend_hash_str_find_ptr(&ce->function_table, lcname, method_name_len)) == NULL) {
34163434
efree(lcname);
3435+
// %S is used for zend_string pointers by smart str printing, but normally
3436+
// is for wide character strings and so compilers complain if this is inline
3437+
const char *format = "Method %S::%S() does not exist";
3438+
ALLOCA_FLAG(use_heap);
3439+
zend_string *method_name_zstr;
3440+
ZSTR_ALLOCA_INIT(method_name_zstr, method_name, method_name_len, use_heap);
34173441
zend_throw_exception_ex(reflection_exception_ptr, 0,
3418-
"Method %s::%s() does not exist", ZSTR_VAL(ce->name), method_name);
3442+
format, ce->name, method_name_zstr);
3443+
ZSTR_ALLOCA_FREE(method_name_zstr, use_heap);
3444+
34193445
RETURN_THROWS();
34203446
}
34213447
efree(lcname);
@@ -3919,7 +3945,10 @@ ZEND_METHOD(ReflectionClassConstant, __construct)
39193945
ce = classname_obj->ce;
39203946
} else {
39213947
if ((ce = zend_lookup_class(classname_str)) == NULL) {
3922-
zend_throw_exception_ex(reflection_exception_ptr, 0, "Class \"%s\" does not exist", ZSTR_VAL(classname_str));
3948+
// %S is used for zend_string pointers by smart str printing, but normally
3949+
// is for wide character strings and so compilers complain if this is inline
3950+
const char *format = "Class \"%S\" does not exist";
3951+
zend_throw_exception_ex(reflection_exception_ptr, 0, format, classname_str);
39233952
RETURN_THROWS();
39243953
}
39253954
}
@@ -3928,7 +3957,10 @@ ZEND_METHOD(ReflectionClassConstant, __construct)
39283957
intern = Z_REFLECTION_P(object);
39293958

39303959
if ((constant = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), constname)) == NULL) {
3931-
zend_throw_exception_ex(reflection_exception_ptr, 0, "Constant %s::%s does not exist", ZSTR_VAL(ce->name), ZSTR_VAL(constname));
3960+
// %S is used for zend_string pointers by smart str printing, but normally
3961+
// is for wide character strings and so compilers complain if this is inline
3962+
const char *format = "Constant %S::%S does not exist";
3963+
zend_throw_exception_ex(reflection_exception_ptr, 0, format, ce->name, constname);
39323964
RETURN_THROWS();
39333965
}
39343966

@@ -4208,7 +4240,10 @@ static void reflection_class_object_ctor(INTERNAL_FUNCTION_PARAMETERS, int is_ob
42084240
} else {
42094241
if ((ce = zend_lookup_class(arg_class)) == NULL) {
42104242
if (!EG(exception)) {
4211-
zend_throw_exception_ex(reflection_exception_ptr, -1, "Class \"%s\" does not exist", ZSTR_VAL(arg_class));
4243+
// %S is used for zend_string pointers by smart str printing, but normally
4244+
// is for wide character strings and so compilers complain if this is inline
4245+
const char *format = "Class \"%S\" does not exist";
4246+
zend_throw_exception_ex(reflection_exception_ptr, -1, format, arg_class);
42124247
}
42134248
RETURN_THROWS();
42144249
}
@@ -4348,8 +4383,11 @@ ZEND_METHOD(ReflectionClass, getStaticPropertyValue)
43484383
RETURN_COPY(def_value);
43494384
}
43504385

4386+
// %S is used for zend_string pointers by smart str printing, but normally
4387+
// is for wide character strings and so compilers complain if this is inline
4388+
const char *format = "Property %S::$%S does not exist";
43514389
zend_throw_exception_ex(reflection_exception_ptr, 0,
4352-
"Property %s::$%s does not exist", ZSTR_VAL(ce->name), ZSTR_VAL(name));
4390+
format, ce->name, name);
43534391
}
43544392
/* }}} */
43554393

@@ -4377,8 +4415,11 @@ ZEND_METHOD(ReflectionClass, setStaticPropertyValue)
43774415
EG(fake_scope) = old_scope;
43784416
if (!variable_ptr) {
43794417
zend_clear_exception();
4418+
// %S is used for zend_string pointers by smart str printing, but normally
4419+
// is for wide character strings and so compilers complain if this is inline
4420+
const char *format = "Class %S does not have a property named %S";
43804421
zend_throw_exception_ex(reflection_exception_ptr, 0,
4381-
"Class %s does not have a property named %s", ZSTR_VAL(ce->name), ZSTR_VAL(name));
4422+
format, ce->name, name);
43824423
RETURN_THROWS();
43834424
}
43844425

@@ -4642,8 +4683,11 @@ ZEND_METHOD(ReflectionClass, getMethod)
46424683
} else if ((mptr = zend_hash_find_ptr(&ce->function_table, lc_name)) != NULL) {
46434684
reflection_method_factory(ce, mptr, NULL, return_value);
46444685
} else {
4686+
// %S is used for zend_string pointers by smart str printing, but normally
4687+
// is for wide character strings and so compilers complain if this is inline
4688+
const char *format = "Method %S::%S() does not exist";
46454689
zend_throw_exception_ex(reflection_exception_ptr, 0,
4646-
"Method %s::%s() does not exist", ZSTR_VAL(ce->name), ZSTR_VAL(name));
4690+
format, ce->name, name);
46474691
}
46484692
zend_string_release(lc_name);
46494693
}
@@ -4769,7 +4813,9 @@ ZEND_METHOD(ReflectionClass, getProperty)
47694813
}
47704814
}
47714815
str_name = ZSTR_VAL(name);
4816+
bool fully_qualified = false;
47724817
if ((tmp = strstr(ZSTR_VAL(name), "::")) != NULL) {
4818+
fully_qualified = true;
47734819
classname_len = tmp - ZSTR_VAL(name);
47744820
classname = zend_string_init(ZSTR_VAL(name), classname_len, 0);
47754821
str_name_len = ZSTR_LEN(name) - (classname_len + 2);
@@ -4786,7 +4832,15 @@ ZEND_METHOD(ReflectionClass, getProperty)
47864832
zend_string_release_ex(classname, 0);
47874833

47884834
if (!instanceof_function(ce, ce2)) {
4789-
zend_throw_exception_ex(reflection_exception_ptr, -1, "Fully qualified property name %s::$%s does not specify a base class of %s", ZSTR_VAL(ce2->name), str_name, ZSTR_VAL(ce->name));
4835+
// %S is used for zend_string pointers by smart str printing, but normally
4836+
// is for wide character strings and so compilers complain if this is inline
4837+
const char *format = "Fully qualified property name %S::$%S does not specify a base class of %S";
4838+
ALLOCA_FLAG(use_heap);
4839+
zend_string *prop_name_zstr;
4840+
ZSTR_ALLOCA_INIT(prop_name_zstr, str_name, str_name_len, use_heap);
4841+
zend_throw_exception_ex(reflection_exception_ptr, -1, format, ce2->name, prop_name_zstr, ce->name);
4842+
ZSTR_ALLOCA_FREE(prop_name_zstr, use_heap);
4843+
47904844
RETURN_THROWS();
47914845
}
47924846
ce = ce2;
@@ -4799,7 +4853,19 @@ ZEND_METHOD(ReflectionClass, getProperty)
47994853
return;
48004854
}
48014855
}
4802-
zend_throw_exception_ex(reflection_exception_ptr, 0, "Property %s::$%s does not exist", ZSTR_VAL(ce->name), str_name);
4856+
// %S is used for zend_string pointers by smart str printing, but normally
4857+
// is for wide character strings and so compilers complain if this is inline
4858+
const char *format = "Property %S::$%S does not exist";
4859+
// Can only use the existing `name` string if it wasn't fully qualified
4860+
if (fully_qualified) {
4861+
ALLOCA_FLAG(use_heap);
4862+
zend_string *prop_name_zstr;
4863+
ZSTR_ALLOCA_INIT(prop_name_zstr, str_name, str_name_len, use_heap);
4864+
zend_throw_exception_ex(reflection_exception_ptr, 0, format, ce->name, prop_name_zstr);
4865+
ZSTR_ALLOCA_FREE(prop_name_zstr, use_heap);
4866+
} else {
4867+
zend_throw_exception_ex(reflection_exception_ptr, 0, format, ce->name, name);
4868+
}
48034869
}
48044870
/* }}} */
48054871

@@ -5665,7 +5731,10 @@ ZEND_METHOD(ReflectionClass, isSubclassOf)
56655731
class_ce = argument->ptr;
56665732
} else {
56675733
if ((class_ce = zend_lookup_class(class_str)) == NULL) {
5668-
zend_throw_exception_ex(reflection_exception_ptr, 0, "Class \"%s\" does not exist", ZSTR_VAL(class_str));
5734+
// %S is used for zend_string pointers by smart str printing, but normally
5735+
// is for wide character strings and so compilers complain if this is inline
5736+
const char *format = "Class \"%S\" does not exist";
5737+
zend_throw_exception_ex(reflection_exception_ptr, 0, format, class_str);
56695738
RETURN_THROWS();
56705739
}
56715740
}
@@ -5698,7 +5767,10 @@ ZEND_METHOD(ReflectionClass, implementsInterface)
56985767
interface_ce = argument->ptr;
56995768
} else {
57005769
if ((interface_ce = zend_lookup_class(interface_str)) == NULL) {
5701-
zend_throw_exception_ex(reflection_exception_ptr, 0, "Interface \"%s\" does not exist", ZSTR_VAL(interface_str));
5770+
// %S is used for zend_string pointers by smart str printing, but normally
5771+
// is for wide character strings and so compilers complain if this is inline
5772+
const char *format = "Interface \"%S\" does not exist";
5773+
zend_throw_exception_ex(reflection_exception_ptr, 0, format, interface_str);
57025774
RETURN_THROWS();
57035775
}
57045776
}
@@ -5866,7 +5938,10 @@ ZEND_METHOD(ReflectionProperty, __construct)
58665938
ce = classname_obj->ce;
58675939
} else {
58685940
if ((ce = zend_lookup_class(classname_str)) == NULL) {
5869-
zend_throw_exception_ex(reflection_exception_ptr, 0, "Class \"%s\" does not exist", ZSTR_VAL(classname_str));
5941+
// %S is used for zend_string pointers by smart str printing, but normally
5942+
// is for wide character strings and so compilers complain if this is inline
5943+
const char *format = "Class \"%S\" does not exist";
5944+
zend_throw_exception_ex(reflection_exception_ptr, 0, format, classname_str);
58705945
RETURN_THROWS();
58715946
}
58725947
}
@@ -5882,7 +5957,10 @@ ZEND_METHOD(ReflectionProperty, __construct)
58825957
}
58835958
}
58845959
if (dynam_prop == 0) {
5885-
zend_throw_exception_ex(reflection_exception_ptr, 0, "Property %s::$%s does not exist", ZSTR_VAL(ce->name), ZSTR_VAL(name));
5960+
// %S is used for zend_string pointers by smart str printing, but normally
5961+
// is for wide character strings and so compilers complain if this is inline
5962+
const char *format = "Property %S::$%S does not exist";
5963+
zend_throw_exception_ex(reflection_exception_ptr, 0, format, ce->name, name);
58865964
RETURN_THROWS();
58875965
}
58885966
}
@@ -6244,13 +6322,17 @@ static zend_result reflection_property_check_lazy_compatible(
62446322
zend_property_info *prop, zend_string *unmangled_name,
62456323
reflection_object *intern, zend_object *object, const char *method)
62466324
{
6247-
if (!prop) {
6325+
if (!prop) {
6326+
// %S is used for zend_string pointers by smart str printing, but normally
6327+
// is for wide character strings and so compilers complain if this is inline
6328+
const char *format = "Can not use %s on dynamic property %S::$%S";
62486329
zend_throw_exception_ex(reflection_exception_ptr, 0,
6249-
"Can not use %s on dynamic property %s::$%s",
6250-
method, ZSTR_VAL(intern->ce->name),
6251-
ZSTR_VAL(unmangled_name));
6330+
format,
6331+
method, intern->ce->name,
6332+
unmangled_name);
62526333
return FAILURE;
62536334
}
6335+
// Non-dynamic properties cannot have null bytes so %s is fine
62546336

62556337
if (prop->flags & ZEND_ACC_STATIC) {
62566338
zend_throw_exception_ex(reflection_exception_ptr, 0,
@@ -7569,7 +7651,10 @@ ZEND_METHOD(ReflectionEnum, getCase)
75697651

75707652
zend_class_constant *constant = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), name);
75717653
if (constant == NULL) {
7572-
zend_throw_exception_ex(reflection_exception_ptr, 0, "Case %s::%s does not exist", ZSTR_VAL(ce->name), ZSTR_VAL(name));
7654+
// %S is used for zend_string pointers by smart str printing, but normally
7655+
// is for wide character strings and so compilers complain if this is inline
7656+
const char *format = "Case %S::%S does not exist";
7657+
zend_throw_exception_ex(reflection_exception_ptr, 0, format, ce->name, name);
75737658
RETURN_THROWS();
75747659
}
75757660
if (!(ZEND_CLASS_CONST_FLAGS(constant) & ZEND_CLASS_CONST_IS_CASE)) {
@@ -7879,7 +7964,10 @@ ZEND_METHOD(ReflectionConstant, __construct)
78797964
zend_constant *const_ = zend_get_constant_ptr(lc_name);
78807965
zend_string_release_ex(lc_name, /* persistent */ false);
78817966
if (!const_) {
7882-
zend_throw_exception_ex(reflection_exception_ptr, 0, "Constant \"%s\" does not exist", ZSTR_VAL(name));
7967+
// %S is used for zend_string pointers by smart str printing, but normally
7968+
// is for wide character strings and so compilers complain if this is inline
7969+
const char *format = "Constant \"%S\" does not exist";
7970+
zend_throw_exception_ex(reflection_exception_ptr, 0, format, name);
78837971
RETURN_THROWS();
78847972
}
78857973

ext/reflection/tests/gh22905/ReflectionClassConstant_construct_class.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ new ReflectionClassConstant("foo\0bar", "");
77

88
?>
99
--EXPECTF--
10-
Fatal error: Uncaught ReflectionException: Class "foo" does not exist in %s:%d
10+
Fatal error: Uncaught ReflectionException: Class "foo%0bar" does not exist in %s:%d
1111
Stack trace:
1212
#0 %s(%d): ReflectionClassConstant->__construct('foo\x00bar', '')
1313
#1 {main}

ext/reflection/tests/gh22905/ReflectionClassConstant_construct_constant.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ new ReflectionClassConstant(Demo::class, "foo\0bar");
88

99
?>
1010
--EXPECTF--
11-
Fatal error: Uncaught ReflectionException: Constant Demo::foo does not exist in %s:%d
11+
Fatal error: Uncaught ReflectionException: Constant Demo::foo%0bar does not exist in %s:%d
1212
Stack trace:
1313
#0 %s(%d): ReflectionClassConstant->__construct('Demo', 'foo\x00bar')
1414
#1 {main}

ext/reflection/tests/gh22905/ReflectionClass_construct.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ new ReflectionClass("foo\0bar");
77

88
?>
99
--EXPECTF--
10-
Fatal error: Uncaught ReflectionException: Class "foo" does not exist in %s:%d
10+
Fatal error: Uncaught ReflectionException: Class "foo%0bar" does not exist in %s:%d
1111
Stack trace:
1212
#0 %s(%d): ReflectionClass->__construct('foo\x00bar')
1313
#1 {main}

ext/reflection/tests/gh22905/ReflectionClass_getMethod.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ $r->getMethod("foo\0bar");
99

1010
?>
1111
--EXPECTF--
12-
Fatal error: Uncaught ReflectionException: Method Demo::foo() does not exist in %s:%d
12+
Fatal error: Uncaught ReflectionException: Method Demo::foo%0bar() does not exist in %s:%d
1313
Stack trace:
1414
#0 %s(%d): ReflectionClass->getMethod('foo\x00bar')
1515
#1 {main}

0 commit comments

Comments
 (0)