Skip to content

Commit 9a1b8a7

Browse files
authored
Fix phpGH-20194: null offset deprecation not emitted for writes (php#20238)
Based on a patch from @ndossche
1 parent 0960689 commit 9a1b8a7

File tree

83 files changed

+982
-2110
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+982
-2110
lines changed

Zend/Optimizer/sccp.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,11 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
11061106

11071107
if (op2) {
11081108
SKIP_IF_TOP(op2);
1109+
if (Z_TYPE_P(op2) == IS_NULL) {
1110+
/* Emits deprecation at run-time. */
1111+
SET_RESULT_BOT(result);
1112+
return;
1113+
}
11091114
}
11101115

11111116
/* We want to avoid keeping around intermediate arrays for each SSA variable in the

Zend/Optimizer/zend_inference.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5252,7 +5252,7 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
52525252
case ZEND_INIT_ARRAY:
52535253
return (opline->op2_type != IS_UNUSED) && (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
52545254
case ZEND_ADD_ARRAY_ELEMENT:
5255-
return (opline->op2_type == IS_UNUSED) || (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
5255+
return (opline->op2_type == IS_UNUSED) || (t2 & (MAY_BE_NULL|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
52565256
case ZEND_STRLEN:
52575257
return (t1 & MAY_BE_ANY) != MAY_BE_STRING;
52585258
case ZEND_COUNT:

Zend/tests/constexpr/constant_expressions_dynamic.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ var_dump(
4848
Warning: A non-numeric value encountered in %s on line %d
4949

5050
Deprecated: Implicit conversion from float 3.14 to int loses precision in %s on line %d
51+
52+
Deprecated: Using null as an array offset is deprecated, use an empty string instead in %s on line %d
5153
int(3)
5254
string(4) "1foo"
5355
bool(false)

Zend/tests/offsets/gh20194.phpt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
GH-20194: Using null as an array offset does not emit deprecation when resolved at compile time
3+
--FILE--
4+
<?php
5+
6+
$a = [null => 1];
7+
8+
echo $a[null];
9+
10+
?>
11+
--EXPECTF--
12+
Deprecated: Using null as an array offset is deprecated, use an empty string instead in %s on line %d
13+
14+
Deprecated: Using null as an array offset is deprecated, use an empty string instead in %s on line %d
15+
1
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Do not leak when promoting null offset deprecation
3+
--FILE--
4+
<?php
5+
6+
set_error_handler(function ($errno, $errstr) {
7+
throw new Exception($errstr);
8+
});
9+
10+
try {
11+
$a = ['foo' => 'bar', null => new stdClass];
12+
} catch (Throwable $e) {
13+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
14+
}
15+
?>
16+
--EXPECT--
17+
Exception: Using null as an array offset is deprecated, use an empty string instead
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
No UAF on null offset deprecation with unset value
3+
--FILE--
4+
<?php
5+
set_error_handler(function ($errno, $errstr) {
6+
var_dump($errstr);
7+
global $a;
8+
unset($a);
9+
});
10+
11+
$a = new stdClass;
12+
$b = [0, null => $a];
13+
14+
echo "\nSuccess\n";
15+
?>
16+
--EXPECTF--
17+
string(72) "Using null as an array offset is deprecated, use an empty string instead"
18+
19+
Success

Zend/zend_API.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2255,9 +2255,6 @@ ZEND_API zend_result array_set_zval_key(HashTable *ht, zval *key, zval *value) /
22552255
case IS_STRING:
22562256
result = zend_symtable_update(ht, Z_STR_P(key), value);
22572257
break;
2258-
case IS_NULL:
2259-
result = zend_hash_update(ht, ZSTR_EMPTY_ALLOC(), value);
2260-
break;
22612258
case IS_RESOURCE:
22622259
zend_use_resource_as_offset(key);
22632260
result = zend_hash_index_update(ht, Z_RES_HANDLE_P(key), value);
@@ -2274,6 +2271,13 @@ ZEND_API zend_result array_set_zval_key(HashTable *ht, zval *key, zval *value) /
22742271
case IS_DOUBLE:
22752272
result = zend_hash_index_update(ht, zend_dval_to_lval_safe(Z_DVAL_P(key)), value);
22762273
break;
2274+
case IS_NULL:
2275+
zend_error(E_DEPRECATED, "Using null as an array offset is deprecated, use an empty string instead");
2276+
if (UNEXPECTED(EG(exception))) {
2277+
return FAILURE;
2278+
}
2279+
result = zend_hash_update(ht, ZSTR_EMPTY_ALLOC(), value);
2280+
break;
22772281
default:
22782282
zend_illegal_container_offset(ZSTR_KNOWN(ZEND_STR_ARRAY), key, BP_VAR_W);
22792283
result = NULL;

Zend/zend_compile.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10175,9 +10175,7 @@ static bool zend_try_ct_eval_array(zval *result, zend_ast *ast) /* {{{ */
1017510175
zend_long lval = zend_dval_to_lval_silent(Z_DVAL_P(key));
1017610176
/* Incompatible float will generate an error, leave this to run-time */
1017710177
if (!zend_is_long_compatible(Z_DVAL_P(key), lval)) {
10178-
zval_ptr_dtor_nogc(value);
10179-
zval_ptr_dtor(result);
10180-
return 0;
10178+
goto fail;
1018110179
}
1018210180
zend_hash_index_update(Z_ARRVAL_P(result), lval, value);
1018310181
break;
@@ -10189,13 +10187,14 @@ static bool zend_try_ct_eval_array(zval *result, zend_ast *ast) /* {{{ */
1018910187
zend_hash_index_update(Z_ARRVAL_P(result), 1, value);
1019010188
break;
1019110189
case IS_NULL:
10192-
zend_hash_update(Z_ARRVAL_P(result), ZSTR_EMPTY_ALLOC(), value);
10193-
break;
10190+
/* Null key will generate a warning at run-time. */
10191+
goto fail;
1019410192
default:
1019510193
zend_error_noreturn(E_COMPILE_ERROR, "Illegal offset type");
1019610194
break;
1019710195
}
1019810196
} else if (!zend_hash_next_index_insert(Z_ARRVAL_P(result), value)) {
10197+
fail:
1019910198
zval_ptr_dtor_nogc(value);
1020010199
zval_ptr_dtor(result);
1020110200
return 0;

Zend/zend_vm_def.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6278,7 +6278,21 @@ ZEND_VM_C_LABEL(num_index):
62786278
} else if ((OP2_TYPE & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) {
62796279
offset = Z_REFVAL_P(offset);
62806280
ZEND_VM_C_GOTO(add_again);
6281-
} else if (Z_TYPE_P(offset) == IS_NULL) {
6281+
} else if (UNEXPECTED(Z_TYPE_P(offset) == IS_NULL)) {
6282+
zval tmp;
6283+
if (OP1_TYPE == IS_CV || OP1_TYPE == IS_VAR) {
6284+
ZVAL_COPY(&tmp, expr_ptr);
6285+
}
6286+
zend_error(E_DEPRECATED, "Using null as an array offset is deprecated, use an empty string instead");
6287+
if (OP1_TYPE == IS_CV || OP1_TYPE == IS_VAR) {
6288+
/* A userland error handler can do funky things to the expression, so reset it */
6289+
zval_ptr_dtor(expr_ptr);
6290+
ZVAL_COPY_VALUE(expr_ptr, &tmp);
6291+
}
6292+
if (UNEXPECTED(EG(exception))) {
6293+
zval_ptr_dtor_nogc(expr_ptr);
6294+
HANDLE_EXCEPTION();
6295+
}
62826296
str = ZSTR_EMPTY_ALLOC();
62836297
ZEND_VM_C_GOTO(str_index);
62846298
} else if (Z_TYPE_P(offset) == IS_DOUBLE) {

0 commit comments

Comments
 (0)