Fix GH-17442: Engine UAF with reference assign and dtor

Closes GH-17443.
This commit is contained in:
Niels Dossche 2025-01-11 11:26:59 +01:00
parent 09791ed1d1
commit b068c2ff94
No known key found for this signature in database
GPG key ID: B8A8AD166DF0E2E5
9 changed files with 108 additions and 22 deletions

1
NEWS
View file

@ -17,6 +17,7 @@ PHP NEWS
. Implement GH-15680 (Enhance zend_dump_op_array to properly represent
non-printable characters in string literals). (nielsdos, WangYihang)
. Add support for backtraces for fatal errors. (enorris)
. Fixed bug GH-17442 (Engine UAF with reference assign and dtor). (nielsdos)
- Curl:
. Added curl_multi_get_handles(). (timwolla)

View file

@ -14,6 +14,12 @@ PHP 8.5 INTERNALS UPGRADE NOTES
1. Internal API changes
========================
- Zend
. Added zend_safe_assign_to_variable_noref() function to safely assign
a value to a non-reference zval.
. Added zval_ptr_safe_dtor() to safely destroy a zval when a destructor
could interfere.
========================
2. Build system changes
========================

View file

@ -0,0 +1,22 @@
--TEST--
GH-17442 (Engine UAF with reference assign and dtor) - untyped
--CREDITS--
YuanchengJiang
--FILE--
<?php
$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = new class {
function __destruct() {
throw new Exception("Test");
}
};
headers_sent($obj,$generator);
?>
--EXPECTF--
Fatal error: Uncaught Exception: Test in %s:%d
Stack trace:
#0 [internal function]: class@anonymous->__destruct()
#1 %s(%d): headers_sent(NULL, 0)
#2 {main}
thrown in %s on line %d

View file

@ -0,0 +1,35 @@
--TEST--
GH-17442 (Engine UAF with reference assign and dtor) - typed
--CREDITS--
YuanchengJiang
nielsdos
--FILE--
<?php
$map = new WeakMap;
class Test {
public stdClass|string $obj;
}
$test = new Test;
$test->obj = new stdClass;
$map[$test->obj] = new class {
function __destruct() {
global $test;
var_dump($test->obj);
throw new Exception("Test");
}
};
headers_sent($test->obj);
?>
--EXPECTF--
string(0) ""
Fatal error: Uncaught Exception: Test in %s:%d
Stack trace:
#0 [internal function]: class@anonymous->__destruct()
#1 %s(%d): headers_sent('')
#2 {main}
thrown in %s on line %d

View file

@ -4666,8 +4666,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_ex(zend_reference *ref, zval *val
zval_ptr_dtor(val);
return FAILURE;
} else {
zval_ptr_dtor(&ref->val);
ZVAL_COPY_VALUE(&ref->val, val);
zend_safe_assign_to_variable_noref(&ref->val, val);
return SUCCESS;
}
}

View file

@ -1107,7 +1107,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_NULL(_zv); \
} while (0)
@ -1129,7 +1129,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_FALSE(_zv); \
} while (0)
@ -1151,7 +1151,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_TRUE(_zv); \
} while (0)
@ -1173,7 +1173,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_BOOL(_zv, bval); \
} while (0)
@ -1195,7 +1195,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_LONG(_zv, lval); \
} while (0)
@ -1217,7 +1217,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_DOUBLE(_zv, dval); \
} while (0)
@ -1239,7 +1239,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_EMPTY_STRING(_zv); \
} while (0)
@ -1261,7 +1261,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_STR(_zv, str); \
} while (0)
@ -1283,7 +1283,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_NEW_STR(_zv, str); \
} while (0)
@ -1305,7 +1305,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_STRING(_zv, string); \
} while (0)
@ -1327,7 +1327,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_STRINGL(_zv, string, len); \
} while (0)
@ -1349,7 +1349,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_ARR(_zv, arr); \
} while (0)
@ -1371,7 +1371,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_RES(_zv, res); \
} while (0)
@ -1393,7 +1393,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_COPY_VALUE(_zv, other_zv); \
} while (0)
@ -1415,7 +1415,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_COPY_VALUE(_zv, other_zv); \
} while (0)
@ -1447,7 +1447,7 @@ ZEND_API zend_result zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval
} \
_zv = &ref->val; \
} \
zval_ptr_dtor(_zv); \
zval_ptr_safe_dtor(_zv); \
ZVAL_COPY_VALUE(_zv, other_zv); \
} while (0)
@ -1485,10 +1485,7 @@ static zend_always_inline zval *zend_try_array_init_size(zval *zv, uint32_t size
}
zv = &ref->val;
}
zval garbage;
ZVAL_COPY_VALUE(&garbage, zv);
ZVAL_NULL(zv);
zval_ptr_dtor(&garbage);
zval_ptr_safe_dtor(zv);
ZVAL_ARR(zv, arr);
return zv;
}

View file

@ -207,6 +207,17 @@ static zend_always_inline zval* zend_assign_to_variable_ex(zval *variable_ptr, z
return variable_ptr;
}
static zend_always_inline void zend_safe_assign_to_variable_noref(zval *variable_ptr, zval *value) {
if (Z_REFCOUNTED_P(variable_ptr)) {
ZEND_ASSERT(Z_TYPE_P(variable_ptr) != IS_REFERENCE);
zend_refcounted *ref = Z_COUNTED_P(variable_ptr);
ZVAL_COPY_VALUE(variable_ptr, value);
GC_DTOR_NO_REF(ref);
} else {
ZVAL_COPY_VALUE(variable_ptr, value);
}
}
ZEND_API zend_result ZEND_FASTCALL zval_update_constant(zval *pp);
ZEND_API zend_result ZEND_FASTCALL zval_update_constant_ex(zval *pp, zend_class_entry *scope);
ZEND_API zend_result ZEND_FASTCALL zval_update_constant_with_ctx(zval *pp, zend_class_entry *scope, zend_ast_evaluate_ctx *ctx);

View file

@ -85,6 +85,20 @@ ZEND_API void zval_ptr_dtor(zval *zval_ptr) /* {{{ */
}
/* }}} */
ZEND_API void zval_ptr_safe_dtor(zval *zval_ptr)
{
if (Z_REFCOUNTED_P(zval_ptr)) {
zend_refcounted *ref = Z_COUNTED_P(zval_ptr);
if (GC_DELREF(ref) == 0) {
ZVAL_NULL(zval_ptr);
rc_dtor_func(ref);
} else {
gc_check_possible_root(ref);
}
}
}
ZEND_API void zval_internal_ptr_dtor(zval *zval_ptr) /* {{{ */
{
if (Z_REFCOUNTED_P(zval_ptr)) {

View file

@ -78,6 +78,7 @@ static zend_always_inline void zval_ptr_dtor_str(zval *zval_ptr)
}
ZEND_API void zval_ptr_dtor(zval *zval_ptr);
ZEND_API void zval_ptr_safe_dtor(zval *zval_ptr);
ZEND_API void zval_internal_ptr_dtor(zval *zvalue);
/* Kept for compatibility */