Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ PHP NEWS
registrations are freed while still reachable from the cycle collector.
(Ilia Alshanetsky)

- MBString:
. Fixed bug GH-23106 (mb_strpos() reads past the end of a haystack ending in
a truncated UTF-8 sequence). (Lazizbek Ergashev)

- SPL:
. Fixed bug GH-23385 (SplDoublyLinkedList::serialize() use-after-free when
__serialize() removes an element). (David Carlier)


10 Sep 2026, PHP 8.6.0beta3

Expand Down
1 change: 1 addition & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,7 @@ PHP 8.6 UPGRADE NOTES
. Improved performance of array_walk().
. Improved performance of intval('+0b...', 2) and intval('0b...', 2).
. Improved performance of str_split().
. Improved performance of str_pad().

- URI:
. Improved performance of Uri\WhatWg\Url::parse() when collecting
Expand Down
3 changes: 3 additions & 0 deletions ext/mbstring/mbstring.c
Original file line number Diff line number Diff line change
Expand Up @@ -1902,6 +1902,9 @@ static unsigned char* offset_to_pointer_utf8(unsigned char *str, unsigned char *
}
pos += u8_tbl[*pos];
}
if (pos > end) {
pos = end;
}
return pos;
}
}
Expand Down
22 changes: 22 additions & 0 deletions ext/mbstring/tests/gh23106.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
GH-23106 (mb_strpos() reads past the end of a haystack ending in a truncated UTF-8 sequence)
--EXTENSIONS--
mbstring
--FILE--
<?php
var_dump(mb_strpos("AA\xf0\x90", "xyz", 3));
var_dump(mb_strpos("AA\xf0\x90", "x", 3));
var_dump(mb_strrpos("AA\xf0\x90", "A", 3));
var_dump(mb_strrpos("AA\xf0\x90", "A", -1));
try {
mb_strpos("AA\xf0\x90", "xyz", 4);
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
bool(false)
bool(false)
bool(false)
int(1)
mb_strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack)
6 changes: 5 additions & 1 deletion ext/spl/spl_dllist.c
Original file line number Diff line number Diff line change
Expand Up @@ -962,12 +962,16 @@ PHP_METHOD(SplDoublyLinkedList, serialize)

/* elements */
while (current) {
zval data;

smart_str_appendc(&buf, ':');
next = current->next;

SPL_LLIST_CHECK_ADDREF(next);

php_var_serialize(&buf, &current->data, &var_hash);
ZVAL_COPY(&data, &current->data);
php_var_serialize(&buf, &data, &var_hash);
zval_ptr_dtor(&data);

SPL_LLIST_CHECK_DELREF_EX(next, break;);

Expand Down
87 changes: 87 additions & 0 deletions ext/spl/tests/gh23385.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
--TEST--
GH-23385 (Use-after-free in SplDoublyLinkedList::serialize())
--CREDITS--
f9j2n6nd8k-eng
--FILE--
<?php

class RemoveSelf {
public function __serialize(): array {
global $list;
unset($list[0]);
return [];
}
}

$list = new SplDoublyLinkedList();
$list->push([new RemoveSelf(), [1, 2, 3]]);
$list->push("tail");
var_dump($list->serialize());
var_dump($list->count());

class RemoveNext {
public function __serialize(): array {
global $list2;
unset($list2[1]);
return [];
}
}

$list2 = new SplDoublyLinkedList();
$list2->push(new RemoveNext());
$list2->push("removed");
$list2->push("after");
var_dump($list2->serialize());
var_dump($list2->count());

class RemoveAll {
public function __serialize(): array {
global $list3;
while (!$list3->isEmpty()) {
$list3->pop();
}
return [];
}
}

$list3 = new SplDoublyLinkedList();
$list3->push([new RemoveAll(), [1, 2]]);
$list3->push("x");
$list3->push("y");
var_dump($list3->serialize());
var_dump($list3->count());

class RemoveHolder {
public function __serialize(): array {
global $list4;
unset($list4[0]);
return [];
}
}

class Holder {
public $first;
public $second = "second";
public $third = "third";
}

$holder = new Holder();
$holder->first = new RemoveHolder();

$list4 = new SplDoublyLinkedList();
$list4->push($holder);
unset($holder);
$list4->push("tail");
var_dump($list4->serialize());
var_dump($list4->count());

?>
--EXPECT--
string(83) "i:0;:a:2:{i:0;O:10:"RemoveSelf":0:{}i:1;a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}}:s:4:"tail";"
int(1)
string(27) "i:0;:O:10:"RemoveNext":0:{}"
int(2)
string(61) "i:0;:a:2:{i:0;O:9:"RemoveAll":0:{}i:1;a:2:{i:0;i:1;i:1;i:2;}}"
int(0)
string(120) "i:0;:O:6:"Holder":3:{s:5:"first";O:12:"RemoveHolder":0:{}s:6:"second";s:6:"second";s:5:"third";s:5:"third";}:s:4:"tail";"
int(1)
12 changes: 7 additions & 5 deletions ext/standard/string.c
Original file line number Diff line number Diff line change
Expand Up @@ -5891,13 +5891,15 @@ static void php_str_pad_fill(zend_string *result, size_t pad_chars, const char *
return;
}

const char *start = p;
const char *end = p + pad_chars;
while (p + pad_str_len <= end) {
p = zend_mempcpy(p, pad_str, pad_str_len);
}
size_t len = MIN(pad_str_len, pad_chars);
p = zend_mempcpy(p, pad_str, len);

if (p < end) {
memcpy(p, pad_str, end - p);
/* Double the filled area on each iteration. */
while (p < end) {
len = MIN(p - start, end - p);
p = zend_mempcpy(p, start, len);
}

ZSTR_LEN(result) += pad_chars;
Expand Down