Skip to content

Commit 5084b2c

Browse files
Add regression tests for libFuzzer findings (v0.5.1 fixes)
1 parent 583e623 commit 5084b2c

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
--TEST--
2+
Regressions for bugs surfaced by the libFuzzer harnesses (v0.5.1 fixes)
3+
--EXTENSIONS--
4+
deepclone
5+
--FILE--
6+
<?php
7+
8+
// #1 — DoS via oversized IS_LONG objectMeta (commit 4a0f594).
9+
// A tiny payload with `objectMeta` = a huge integer used to drive
10+
// multi-GB allocations. The reader caps the IS_LONG form at 1M.
11+
try {
12+
deepclone_from_array([
13+
'classes' => 'stdClass',
14+
'objectMeta' => 0x40000000, // 1G — well over the 1M cap
15+
'prepared' => 0,
16+
]);
17+
var_dump(false);
18+
} catch (\ValueError $e) {
19+
var_dump(str_contains($e->getMessage(), 'out of range'));
20+
}
21+
22+
// #2 — UAF on ref tree_pos across packed→hash conversion (commit 3e0bf4f).
23+
// Build an array that triggers packed-to-hash mid-copy while holding saved
24+
// tree_pos pointers from an earlier (ref) iteration. Under ASAN this
25+
// would fire a heap-use-after-free; without ASAN it could segfault or
26+
// silently corrupt. The fix forces mixed mode up front.
27+
$ref = 42;
28+
$mixed = [
29+
0 => &$ref, // integer key with reference (saves tree_pos)
30+
'trigger' => 'str', // string key triggers packed→hash rehash
31+
];
32+
$d = deepclone_from_array(deepclone_to_array($mixed));
33+
var_dump(is_array($d));
34+
var_dump($d[0] === 42);
35+
var_dump($d['trigger'] === 'str');
36+
37+
// #3 — Unsound refcount==1 pool-skip on shared parent (commit 0bc6dc3).
38+
// An object with refcount==1 can be reached twice when the containing
39+
// array has refcount > 1 (shared via multiple slots). Old code would
40+
// skip the pool lookup on the second visit and trip add_new()'s
41+
// "slot already occupied" assertion in debug builds; in release, it
42+
// would silently emit a duplicate objectMeta entry.
43+
// The contained object must have refcount=1 (only reachable via the shared
44+
// HT slot), so bypass the local variable and put an anonymous object in.
45+
$mk = static fn() => (function() { $o = new stdClass(); $o->val = 42; return $o; })();
46+
$shared = [$mk()]; // inner stdClass refcount 1, $shared refcount 1
47+
$graph = [$shared, $shared]; // $shared HT refcount 2; inner obj refcount still 1
48+
49+
$d = deepclone_to_array($graph);
50+
$c = deepclone_from_array($d);
51+
var_dump(is_array($c));
52+
var_dump($c[0][0]->val === 42);
53+
var_dump($c[1][0]->val === 42);
54+
// Both copies should resolve to the same reconstructed object (object identity
55+
// is preserved within a single deepclone).
56+
var_dump($c[0][0] === $c[1][0]);
57+
58+
echo "Done\n";
59+
?>
60+
--EXPECT--
61+
bool(true)
62+
bool(true)
63+
bool(true)
64+
bool(true)
65+
bool(true)
66+
bool(true)
67+
bool(true)
68+
bool(true)
69+
Done

0 commit comments

Comments
 (0)