From be4864f8d348465b0031a51752900e6abb1b1cd2 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Tue, 11 Aug 2026 21:51:04 +0100 Subject: [PATCH] ext/pcre: preg_replace() use-after-free when __toString() destroys an array argument. Fix #23204 Follow-up on GH-23207. preg_replace() has a frameless handler, so the pattern, replacement and subject arrays reached _preg_replace_common() with a refcount of one. Stringifying an entry runs __toString(), which freed arData under the iterator or rehashed it in place. Taking a reference on each table for the duration of the read keeps it alive and turns a mutation into a separation. --- ext/pcre/php_pcre.c | 20 ++++++++++ ext/pcre/tests/gh23204.phpt | 78 +++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 ext/pcre/tests/gh23204.phpt diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c index 6a24691a170e..dfff4640be2d 100644 --- a/ext/pcre/php_pcre.c +++ b/ext/pcre/php_pcre.c @@ -2316,6 +2316,16 @@ static void _preg_replace_common( RETURN_THROWS(); } + if (regex_ht) { + GC_TRY_ADDREF(regex_ht); + } + if (replace_ht) { + GC_TRY_ADDREF(replace_ht); + } + if (subject_ht) { + GC_TRY_ADDREF(subject_ht); + } + if (subject_str) { old_replace_count = replace_count; result = php_replace_in_subject(regex_str, regex_ht, replace_str, replace_ht, @@ -2370,6 +2380,16 @@ static void _preg_replace_common( if (zcount) { ZEND_TRY_ASSIGN_REF_LONG(zcount, replace_count); } + + if (regex_ht) { + GC_TRY_DTOR_NO_REF(regex_ht); + } + if (replace_ht) { + GC_TRY_DTOR_NO_REF(replace_ht); + } + if (subject_ht) { + GC_TRY_DTOR_NO_REF(subject_ht); + } } /* {{{ preg_replace_common */ diff --git a/ext/pcre/tests/gh23204.phpt b/ext/pcre/tests/gh23204.phpt new file mode 100644 index 000000000000..9f56d3bd6f17 --- /dev/null +++ b/ext/pcre/tests/gh23204.phpt @@ -0,0 +1,78 @@ +--TEST-- +GH-23204 (Use-after-free when __toString() destroys the array being read) +--FILE-- +getMessage(), PHP_EOL; +} +?> +--EXPECT-- +string(4) "zzzz" +NULL +string(4) "zyyy" +array(2) { + [0]=> + string(4) "zbcd" + [1]=> + string(4) "zbcd" +} +string(3) "XXz" +int(3) +Exception: boom