From 8a585856d1a21507c1bee0f4a1fad0e2154c08db Mon Sep 17 00:00:00 2001 From: David Carlier Date: Fri, 2 May 2025 07:11:57 +0100 Subject: [PATCH] Fix GH-18480: array_splice overflow on array length with offset. close GH-18483 --- NEWS | 4 +++ ext/standard/array.c | 12 ++++---- ext/standard/tests/array/gh18480.phpt | 40 +++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 ext/standard/tests/array/gh18480.phpt diff --git a/NEWS b/NEWS index c2e0418487b..ed6cd4e2e3a 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.3.22 +- Core: + . Fixed GH-18480 (array_splice with large values for offset/length arguments). + (nielsdos/David Carlier) + - Curl: . Fixed GH-18460 (curl_easy_setopt with CURLOPT_USERPWD/CURLOPT_USERNAME/ CURLOPT_PASSWORD set the Authorization header when set to NULL). diff --git a/ext/standard/array.c b/ext/standard/array.c index b89deb241f0..4d3c03bce52 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -3252,7 +3252,7 @@ static void php_splice(HashTable *in_hash, zend_long offset, zend_long length, H /* If hash for removed entries exists, go until offset+length and copy the entries to it */ if (removed != NULL) { - for ( ; pos < offset + length && idx < in_hash->nNumUsed; idx++, entry++) { + for ( ; pos - offset < length && idx < in_hash->nNumUsed; idx++, entry++) { if (Z_TYPE_P(entry) == IS_UNDEF) continue; pos++; Z_TRY_ADDREF_P(entry); @@ -3260,9 +3260,9 @@ static void php_splice(HashTable *in_hash, zend_long offset, zend_long length, H zend_hash_packed_del_val(in_hash, entry); } } else { /* otherwise just skip those entries */ - int pos2 = pos; + zend_long pos2 = pos; - for ( ; pos2 < offset + length && idx < in_hash->nNumUsed; idx++, entry++) { + for ( ; pos2 - offset < length && idx < in_hash->nNumUsed; idx++, entry++) { if (Z_TYPE_P(entry) == IS_UNDEF) continue; pos2++; zend_hash_packed_del_val(in_hash, entry); @@ -3317,7 +3317,7 @@ static void php_splice(HashTable *in_hash, zend_long offset, zend_long length, H /* If hash for removed entries exists, go until offset+length and copy the entries to it */ if (removed != NULL) { - for ( ; pos < offset + length && idx < in_hash->nNumUsed; idx++, p++) { + for ( ; pos - offset < length && idx < in_hash->nNumUsed; idx++, p++) { if (Z_TYPE(p->val) == IS_UNDEF) continue; pos++; entry = &p->val; @@ -3330,9 +3330,9 @@ static void php_splice(HashTable *in_hash, zend_long offset, zend_long length, H zend_hash_del_bucket(in_hash, p); } } else { /* otherwise just skip those entries */ - int pos2 = pos; + zend_long pos2 = pos; - for ( ; pos2 < offset + length && idx < in_hash->nNumUsed; idx++, p++) { + for ( ; pos2 - offset < length && idx < in_hash->nNumUsed; idx++, p++) { if (Z_TYPE(p->val) == IS_UNDEF) continue; pos2++; zend_hash_del_bucket(in_hash, p); diff --git a/ext/standard/tests/array/gh18480.phpt b/ext/standard/tests/array/gh18480.phpt new file mode 100644 index 00000000000..b9466029d4e --- /dev/null +++ b/ext/standard/tests/array/gh18480.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-18480 (array_splice overflow with large offset / length values) +--FILE-- + PHP_INT_MAX]; + $offset = PHP_INT_MAX; + var_dump(array_splice($a,$offset, $length)); + $a = ["a" => PHP_INT_MAX]; + $offset = PHP_INT_MIN; + var_dump(array_splice($a,$offset, $length)); +} +--EXPECTF-- +array(0) { +} +array(0) { +} +array(0) { +} +array(0) { +} +array(0) { +} +array(1) { + [0]=> + int(%d) +} +array(0) { +} +array(1) { + ["a"]=> + int(%d) +}