Skip to content

Commit 53eee29

Browse files
committed
Completely remove disabled functions from function table
Currently, disabling a function only replaces the internal function handler with one that throws a warning, and a few places in the engine special-case such functions, such as function_exists. This leaves us with a Schrödinger's function, which both does not exist (function_exists returns false) and does exist (you cannot define a function with the same name). In particular, this prevents the implementation of robust polyfills, as reported in https://bugs.php.net/bug.php?id=79382: if (!function_exists('getallheaders')) { function getallheaders(...) { ... } } If getallheaders() is a disabled function, this code will break. This patch changes disable_functions to remove the functions from the function table completely. For all intents and purposes, it will look like the function does not exist. This also renders two bits of PHP functionality obsolete and thus deprecated: * ReflectionFunction::isDisabled(), as it will no longer be possible to construct the ReflectionFunction of a disabled function in the first place. * get_defined_functions() with $exclude_disabled=false, as get_defined_functions() now never returns disabled functions. Fixed bug #79382. Closes GH-5473.
1 parent 8cb2373 commit 53eee29

22 files changed

+139
-130
lines changed

UPGRADING

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ PHP 8.0 UPGRADE NOTES
178178
}
179179

180180
RFC: https://wiki.php.net/rfc/abstract_trait_method_validation
181+
. Disabled functions are now treated exactly like non-existent functions.
182+
Calling a disabled function will report it as unknown, and redefining a
183+
disabled function is now possible.
181184

182185
- COM:
183186
. Removed the ability to import case-insensitive constants from type
@@ -500,6 +503,11 @@ PHP 8.0 UPGRADE NOTES
500503
4. Deprecated Functionality
501504
========================================
502505

506+
- Core:
507+
. Calling get_defined_functions() with $exclude_disabled explicitly set to
508+
false is deprecated. get_defined_functions() will never include disabled
509+
functions.
510+
503511
- Enchant:
504512
. enchant_broker_set_dict_path and enchant_broker_get_dict_path
505513
not available in libenchant < 1.5 nor in libenchant-2
@@ -511,6 +519,11 @@ PHP 8.0 UPGRADE NOTES
511519
do not accept empty files as valid zip archives any longer.
512520
Existing workaround will be removed in next version.
513521

522+
- Reflection:
523+
. ReflectionFunction::isDisabled() is deprecated, as it is no longer possible
524+
to create a ReflectionFunction for a disabled function. This method now
525+
always returns false.
526+
514527
========================================
515528
5. Changed Functions
516529
========================================

Zend/tests/bug69315.phpt

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,44 @@ disable_functions=strlen,defined,call_user_func,constant,is_string
77

88
var_dump(function_exists("strlen"));
99
var_dump(is_callable("strlen"));
10-
var_dump(strlen("xxx"));
11-
var_dump(defined("PHP_VERSION"));
12-
var_dump(constant("PHP_VERSION"));
13-
var_dump(call_user_func("strlen"));
14-
var_dump(is_string("xxx"));
15-
var_dump(is_string());
10+
try {
11+
var_dump(strlen("xxx"));
12+
} catch (Error $e) {
13+
echo $e->getMessage(), "\n";
14+
}
15+
try {
16+
var_dump(defined("PHP_VERSION"));
17+
} catch (Error $e) {
18+
echo $e->getMessage(), "\n";
19+
}
20+
try {
21+
var_dump(constant("PHP_VERSION"));
22+
} catch (Error $e) {
23+
echo $e->getMessage(), "\n";
24+
}
25+
try {
26+
var_dump(call_user_func("strlen"));
27+
} catch (Error $e) {
28+
echo $e->getMessage(), "\n";
29+
}
30+
try {
31+
var_dump(is_string("xxx"));
32+
} catch (Error $e) {
33+
echo $e->getMessage(), "\n";
34+
}
35+
try {
36+
var_dump(is_string());
37+
} catch (Error $e) {
38+
echo $e->getMessage(), "\n";
39+
}
1640

1741
?>
18-
--EXPECTF--
42+
--EXPECT--
1943
bool(false)
20-
bool(true)
21-
22-
Warning: strlen() has been disabled for security reasons in %sbug69315.php on line %d
23-
NULL
24-
25-
Warning: defined() has been disabled for security reasons in %sbug69315.php on line %d
26-
NULL
27-
28-
Warning: constant() has been disabled for security reasons in %sbug69315.php on line %d
29-
NULL
30-
31-
Warning: call_user_func() has been disabled for security reasons in %sbug69315.php on line %d
32-
NULL
33-
34-
Warning: is_string() has been disabled for security reasons in %sbug69315.php on line %d
35-
NULL
36-
37-
Warning: is_string() has been disabled for security reasons in %s on line %d
38-
NULL
44+
bool(false)
45+
Call to undefined function strlen()
46+
Call to undefined function defined()
47+
Call to undefined function constant()
48+
Call to undefined function call_user_func()
49+
Call to undefined function is_string()
50+
Call to undefined function is_string()

Zend/tests/bug79382.phpt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Bug #79382: Cannot redeclare disabled function
3+
--INI--
4+
disable_functions=strlen
5+
--FILE--
6+
<?php
7+
8+
function strlen(string $x): int {
9+
$len = 0;
10+
while (isset($x[$len])) $len++;
11+
return $len;
12+
}
13+
14+
var_dump(strlen("foobar"));
15+
16+
?>
17+
--EXPECT--
18+
int(6)

Zend/tests/errmsg_020.phpt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ disable_functions=phpinfo
55
--FILE--
66
<?php
77

8-
phpinfo();
8+
try {
9+
phpinfo();
10+
} catch (Error $e) {
11+
echo $e->getMessage(), "\n";
12+
}
913

10-
echo "Done\n";
1114
?>
12-
--EXPECTF--
13-
Warning: phpinfo() has been disabled for security reasons in %s on line %d
14-
Done
15+
--EXPECT--
16+
Call to undefined function phpinfo()

Zend/zend_API.c

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2686,27 +2686,9 @@ ZEND_API int zend_set_hash_symbol(zval *symbol, const char *name, int name_lengt
26862686

26872687
/* Disabled functions support */
26882688

2689-
/* {{{ proto void display_disabled_function(void)
2690-
Dummy function which displays an error when a disabled function is called. */
2691-
ZEND_API ZEND_COLD ZEND_FUNCTION(display_disabled_function)
2692-
{
2693-
zend_error(E_WARNING, "%s() has been disabled for security reasons", get_active_function_name());
2694-
}
2695-
/* }}} */
2696-
26972689
ZEND_API int zend_disable_function(char *function_name, size_t function_name_length) /* {{{ */
26982690
{
2699-
zend_internal_function *func;
2700-
if ((func = zend_hash_str_find_ptr(CG(function_table), function_name, function_name_length))) {
2701-
zend_free_internal_arg_info(func);
2702-
func->fn_flags &= ~(ZEND_ACC_VARIADIC | ZEND_ACC_HAS_TYPE_HINTS | ZEND_ACC_HAS_RETURN_TYPE);
2703-
func->num_args = 0;
2704-
func->required_num_args = 0;
2705-
func->arg_info = NULL;
2706-
func->handler = ZEND_FN(display_disabled_function);
2707-
return SUCCESS;
2708-
}
2709-
return FAILURE;
2691+
return zend_hash_str_del(CG(function_table), function_name, function_name_length);
27102692
}
27112693
/* }}} */
27122694

Zend/zend_API.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -593,8 +593,6 @@ ZEND_API zend_bool zend_is_iterable(zval *iterable);
593593

594594
ZEND_API zend_bool zend_is_countable(zval *countable);
595595

596-
ZEND_API ZEND_FUNCTION(display_disabled_function);
597-
598596
ZEND_API int zend_get_default_from_internal_arg_info(
599597
zval *default_value_zval, zend_internal_arg_info *arg_info);
600598

Zend/zend_builtin_functions.c

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,7 +1127,7 @@ ZEND_FUNCTION(trait_exists)
11271127
ZEND_FUNCTION(function_exists)
11281128
{
11291129
zend_string *name;
1130-
zend_function *func;
1130+
zend_bool exists;
11311131
zend_string *lcname;
11321132

11331133
ZEND_PARSE_PARAMETERS_START(1, 1)
@@ -1142,15 +1142,10 @@ ZEND_FUNCTION(function_exists)
11421142
lcname = zend_string_tolower(name);
11431143
}
11441144

1145-
func = zend_hash_find_ptr(EG(function_table), lcname);
1145+
exists = zend_hash_exists(EG(function_table), lcname);
11461146
zend_string_release_ex(lcname, 0);
11471147

1148-
/*
1149-
* A bit of a hack, but not a bad one: we see if the handler of the function
1150-
* is actually one that displays "function is disabled" message.
1151-
*/
1152-
RETURN_BOOL(func && (func->type != ZEND_INTERNAL_FUNCTION ||
1153-
func->internal_function.handler != zif_display_disabled_function));
1148+
RETURN_BOOL(exists);
11541149
}
11551150
/* }}} */
11561151

@@ -1420,30 +1415,25 @@ ZEND_FUNCTION(get_defined_functions)
14201415
zval internal, user;
14211416
zend_string *key;
14221417
zend_function *func;
1423-
zend_bool exclude_disabled = 0;
1424-
char *disable_functions = NULL;
1418+
zend_bool exclude_disabled = 1;
14251419

14261420
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &exclude_disabled) == FAILURE) {
14271421
RETURN_THROWS();
14281422
}
14291423

1424+
if (exclude_disabled == 0) {
1425+
zend_error(E_DEPRECATED,
1426+
"get_defined_functions(): Setting $exclude_disabled to false has no effect");
1427+
}
1428+
14301429
array_init(&internal);
14311430
array_init(&user);
14321431
array_init(return_value);
14331432

1434-
if (exclude_disabled) {
1435-
disable_functions = INI_STR("disable_functions");
1436-
}
14371433
ZEND_HASH_FOREACH_STR_KEY_PTR(EG(function_table), key, func) {
14381434
if (key && ZSTR_VAL(key)[0] != 0) {
14391435
if (func->type == ZEND_INTERNAL_FUNCTION) {
1440-
if (disable_functions != NULL) {
1441-
if (strstr(disable_functions, func->common.function_name->val) == NULL) {
1442-
add_next_index_str(&internal, zend_string_copy(key));
1443-
}
1444-
} else {
1445-
add_next_index_str(&internal, zend_string_copy(key));
1446-
}
1436+
add_next_index_str(&internal, zend_string_copy(key));
14471437
} else if (func->type == ZEND_USER_FUNCTION) {
14481438
add_next_index_str(&user, zend_string_copy(key));
14491439
}

Zend/zend_builtin_functions.stub.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function get_declared_traits(): array {}
8585

8686
function get_declared_interfaces(): array {}
8787

88-
function get_defined_functions(bool $exclude_disabled = false): array {}
88+
function get_defined_functions(bool $exclude_disabled = true): array {}
8989

9090
function get_defined_vars(): array {}
9191

Zend/zend_builtin_functions_arginfo.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ ZEND_END_ARG_INFO()
146146
#define arginfo_get_declared_interfaces arginfo_func_get_args
147147

148148
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_get_defined_functions, 0, 0, IS_ARRAY, 0)
149-
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, exclude_disabled, _IS_BOOL, 0, "false")
149+
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, exclude_disabled, _IS_BOOL, 0, "true")
150150
ZEND_END_ARG_INFO()
151151

152152
#define arginfo_get_defined_vars arginfo_func_get_args

Zend/zend_compile.c

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3934,10 +3934,6 @@ int zend_compile_func_array_slice(znode *result, zend_ast_list *args) /* {{{ */
39343934

39353935
int zend_try_compile_special_func(znode *result, zend_string *lcname, zend_ast_list *args, zend_function *fbc, uint32_t type) /* {{{ */
39363936
{
3937-
if (fbc->internal_function.handler == ZEND_FN(display_disabled_function)) {
3938-
return FAILURE;
3939-
}
3940-
39413937
if (CG(compiler_options) & ZEND_COMPILE_NO_BUILTINS) {
39423938
return FAILURE;
39433939
}

ext/opcache/Optimizer/pass1.c

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -382,12 +382,10 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
382382
Z_TYPE(ZEND_OP1_LITERAL(send1_opline)) == IS_STRING) {
383383
if ((Z_STRLEN(ZEND_OP2_LITERAL(init_opline)) == sizeof("function_exists")-1 &&
384384
!memcmp(Z_STRVAL(ZEND_OP2_LITERAL(init_opline)),
385-
"function_exists", sizeof("function_exists")-1) &&
386-
!zend_optimizer_is_disabled_func("function_exists", sizeof("function_exists") - 1)) ||
385+
"function_exists", sizeof("function_exists")-1)) ||
387386
(Z_STRLEN(ZEND_OP2_LITERAL(init_opline)) == sizeof("is_callable")-1 &&
388387
!memcmp(Z_STRVAL(ZEND_OP2_LITERAL(init_opline)),
389-
"is_callable", sizeof("is_callable")) &&
390-
!zend_optimizer_is_disabled_func("is_callable", sizeof("is_callable") - 1))) {
388+
"is_callable", sizeof("is_callable")))) {
391389
zend_internal_function *func;
392390
zend_string *lc_name = zend_string_tolower(
393391
Z_STR(ZEND_OP1_LITERAL(send1_opline)));
@@ -400,12 +398,7 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
400398
#endif
401399
) {
402400
zval t;
403-
if (Z_STRLEN(ZEND_OP2_LITERAL(init_opline)) == sizeof("is_callable") - 1 ||
404-
func->handler != ZEND_FN(display_disabled_function)) {
405-
ZVAL_TRUE(&t);
406-
} else {
407-
ZVAL_FALSE(&t);
408-
}
401+
ZVAL_TRUE(&t);
409402
literal_dtor(&ZEND_OP2_LITERAL(init_opline));
410403
MAKE_NOP(init_opline);
411404
literal_dtor(&ZEND_OP1_LITERAL(send1_opline));
@@ -423,8 +416,7 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
423416
break;
424417
} else if (Z_STRLEN(ZEND_OP2_LITERAL(init_opline)) == sizeof("extension_loaded")-1 &&
425418
!memcmp(Z_STRVAL(ZEND_OP2_LITERAL(init_opline)),
426-
"extension_loaded", sizeof("extension_loaded")-1) &&
427-
!zend_optimizer_is_disabled_func("extension_loaded", sizeof("extension_loaded") - 1)) {
419+
"extension_loaded", sizeof("extension_loaded")-1)) {
428420
zval t;
429421
zend_string *lc_name = zend_string_tolower(
430422
Z_STR(ZEND_OP1_LITERAL(send1_opline)));
@@ -465,8 +457,7 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
465457
break;
466458
} else if (Z_STRLEN(ZEND_OP2_LITERAL(init_opline)) == sizeof("constant")-1 &&
467459
!memcmp(Z_STRVAL(ZEND_OP2_LITERAL(init_opline)),
468-
"constant", sizeof("constant")-1) &&
469-
!zend_optimizer_is_disabled_func("constant", sizeof("constant") - 1)) {
460+
"constant", sizeof("constant")-1)) {
470461
zval t;
471462

472463
if (zend_optimizer_get_persistent_constant(Z_STR(ZEND_OP1_LITERAL(send1_opline)), &t, 1)) {
@@ -488,7 +479,6 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
488479
} else if (Z_STRLEN(ZEND_OP2_LITERAL(init_opline)) == sizeof("dirname")-1 &&
489480
!memcmp(Z_STRVAL(ZEND_OP2_LITERAL(init_opline)),
490481
"dirname", sizeof("dirname") - 1) &&
491-
!zend_optimizer_is_disabled_func("dirname", sizeof("dirname") - 1) &&
492482
IS_ABSOLUTE_PATH(Z_STRVAL(ZEND_OP1_LITERAL(send1_opline)), Z_STRLEN(ZEND_OP1_LITERAL(send1_opline)))) {
493483
zend_string *dirname = zend_string_init(Z_STRVAL(ZEND_OP1_LITERAL(send1_opline)), Z_STRLEN(ZEND_OP1_LITERAL(send1_opline)), 0);
494484
ZSTR_LEN(dirname) = zend_dirname(ZSTR_VAL(dirname), ZSTR_LEN(dirname));

ext/opcache/Optimizer/sccp.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,8 +1019,7 @@ static inline int ct_eval_func_call(
10191019
}
10201020

10211021
func = zend_hash_find_ptr(CG(function_table), name);
1022-
if (!func || func->type != ZEND_INTERNAL_FUNCTION
1023-
|| func->internal_function.handler == ZEND_FN(display_disabled_function)) {
1022+
if (!func || func->type != ZEND_INTERNAL_FUNCTION) {
10241023
return FAILURE;
10251024
}
10261025

ext/opcache/Optimizer/zend_func_info.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -899,9 +899,7 @@ static uint32_t get_internal_func_info(
899899
}
900900

901901
func_info_t *info = Z_PTR_P(zv);
902-
if (UNEXPECTED(zend_optimizer_is_disabled_func(info->name, info->name_len))) {
903-
return MAY_BE_NULL;
904-
} else if (info->info_func) {
902+
if (info->info_func) {
905903
return info->info_func(call_info, ssa);
906904
} else {
907905
return info->info;

ext/opcache/Optimizer/zend_optimizer.c

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,6 @@ static inline int zend_optimizer_add_literal_string(zend_op_array *op_array, zen
157157
return zend_optimizer_add_literal(op_array, &zv);
158158
}
159159

160-
int zend_optimizer_is_disabled_func(const char *name, size_t len) {
161-
zend_function *fbc = (zend_function *)zend_hash_str_find_ptr(EG(function_table), name, len);
162-
163-
return (fbc && fbc->type == ZEND_INTERNAL_FUNCTION &&
164-
fbc->internal_function.handler == ZEND_FN(display_disabled_function));
165-
}
166-
167160
static inline void drop_leading_backslash(zval *val) {
168161
if (Z_STRVAL_P(val)[0] == '\\') {
169162
zend_string *str = zend_string_init(Z_STRVAL_P(val) + 1, Z_STRLEN_P(val) - 1, 0);

ext/opcache/Optimizer/zend_optimizer_internal.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_c
108108
void zend_optimizer_nop_removal(zend_op_array *op_array, zend_optimizer_ctx *ctx);
109109
void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx *ctx);
110110
void zend_optimizer_compact_vars(zend_op_array *op_array);
111-
int zend_optimizer_is_disabled_func(const char *name, size_t len);
112111
zend_function *zend_optimizer_get_called_func(
113112
zend_script *script, zend_op_array *op_array, zend_op *opline, zend_bool *is_prototype);
114113
uint32_t zend_optimizer_classify_function(zend_string *name, uint32_t num_args);

ext/opcache/tests/bug68104.phpt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ disable_functions=dl
1111
--FILE--
1212
<?php
1313
var_dump(is_callable("dl"));
14-
dl("a.so");
14+
try {
15+
dl("a.so");
16+
} catch (Error $e) {
17+
echo $e->getMessage(), "\n";
18+
}
1519
?>
16-
--EXPECTF--
17-
bool(true)
18-
19-
Warning: dl() has been disabled for security reasons in %sbug68104.php on line %d
20+
--EXPECT--
21+
bool(false)
22+
Call to undefined function dl()

0 commit comments

Comments
 (0)