Repository navigation
Member access within null pointer in ext/spl/spl_observer.c #14639
Copy link
Copy link
Closed
Description
Description
The following code:
<?php
function varToString($var) {
try {
} catch (Exception $e) {
}
$vars = [$var1, $var2, $var3];
foreach ($vars as $i => $v1) {
foreach ($vars as $j => $v2) {
if ($i < $j) {
try {
} catch (Exception $e) {
$result["serialize_{$i}"] = "Error: " . $e->getMessage();
$result["serialize_{$j}"] = "Error: " . $e->getMessage();
}
try {
$result["unserialize_{$i}"] = unserialize(serialize($v1));
$result["unserialize_{$j}"] = unserialize(serialize($v2));
} catch (Exception $e) {
$result["unserialize_{$i}"] = "Error: " . $e->getMessage();
$result["unserialize_{$j}"] = "Error: " . $e->getMessage();
$result["base64_encode_{$i}"] = base64_encode(varToString($v1));
$result["base64_encode_{$j}"] = base64_encode(varToString($v2));
} catch (Exception $e) {
$result["base64_encode_{$i}"] = "Error: " . $e->getMessage();
$result["base64_encode_{$j}"] = "Error: " . $e->getMessage();
}
try {
$result["base64_decode_{$i}"] = base64_decode(base64_encode(varToString($v1)));
$result["base64_decode_{$j}"] = base64_decode(base64_encode(varToString($v2)));
} catch (Exception $e) {
$result["base64_decode_{$i}"] = "Error: " . $e->getMessage();
$result["base64_decode_{$j}"] = "Error: " . $e->getMessage();
$result["md5_{$i}"] = md5(varToString($v1));
$result["md5_{$j}"] = md5(varToString($v2));
} catch (Exception $e) {
$result["md5_{$i}"] = "Error: " . $e->getMessage();
$result["md5_{$j}"] = "Error: " . $e->getMessage();
}
try {
$result["sha1_{$i}"] = sha1(varToString($v1));
$result["sha1_{$j}"] = sha1(varToString($v2));
} catch (Exception $e) {
$result["sha1_{$i}"] = "Error: " . $e->getMessage();
$result["sha1_{$j}"] = "Error: " . $e->getMessage();
$result["url_encode_{$i}"] = urlencode(varToString($v1));
$result["url_encode_{$j}"] = urlencode(varToString($v2));
} catch (Exception $e) {
$result["url_encode_{$i}"] = "Error: " . $e->getMessage();
$result["url_encode_{$j}"] = "Error: " . $e->getMessage();
}
try {
$result["url_decode_{$i}"] = urldecode(urlencode(varToString($v1)));
$result["url_decode_{$j}"] = urldecode(urlencode(varToString($v2)));
} catch (Exception $e) {
$result["url_decode_{$i}"] = "Error: " . $e->getMessage();
$result["url_decode_{$j}"] = "Error: " . $e->getMessage();
$result["html_encode_{$i}"] = htmlspecialchars(varToString($v1));
$result["html_encode_{$j}"] = htmlspecialchars(varToString($v2));
} catch (Exception $e) {
$result["html_encode_{$i}"] = "Error: " . $e->getMessage();
$result["html_encode_{$j}"] = "Error: " . $e->getMessage();
}
try {
$result["html_decode_{$i}"] = htmlspecialchars_decode(htmlspecialchars(varToString($v1)));
$result["html_decode_{$j}"] = htmlspecialchars_decode(htmlspecialchars(varToString($v2)));
} catch (Exception $e) {
$result["html_decode_{$i}"] = "Error: " . $e->getMessage();
$result["html_decode_{$j}"] = "Error: " . $e->getMessage();
$result["strlen_{$i}"] = strlen(varToString($v1));
$result["strlen_{$j}"] = strlen(varToString($v2));
} catch (Exception $e) {
$result["strlen_{$i}"] = "Error: " . $e->getMessage();
$result["strlen_{$j}"] = "Error: " . $e->getMessage();
}
try {
$result["trim_{$i}"] = trim(varToString($v1));
$result["trim_{$j}"] = trim(varToString($v2));
} catch (Exception $e) {
$result["trim_{$i}"] = "Error: " . $e->getMessage();
$result["trim_{$j}"] = "Error: " . $e->getMessage();
$result["strtoupper_{$i}"] = strtoupper(varToString($v1));
$result["strtoupper_{$j}"] = strtoupper(varToString($v2));
} catch (Exception $e) {
$result["strtoupper_{$i}"] = "Error: " . $e->getMessage();
$result["strtoupper_{$j}"] = "Error: " . $e->getMessage();
}
try {
$result["strtolower_{$i}"] = strtolower(varToString($v1));
$result["strtolower_{$j}"] = strtolower(varToString($v2));
} catch (Exception $e) {
$result["strtolower_{$i}"] = "Error: " . $e->getMessage();
$result["strtolower_{$j}"] = "Error: " . $e->getMessage();
$result["strrev_{$i}"] = strrev(varToString($v1));
} catch (Exception $e) {
$result["abs_{$i}"] = "Error: " . $e->getMessage();
$result["abs_{$j}"] = "Error: " . $e->getMessage();
$result["rand_{$i}_{$j}"] = rand(min($i, $j), max($i, $j));
} catch (Exception $e) {
$result["rand_{$i}_{$j}"] = "Error: " . $e->getMessage();
}
try {
if (is_callable($v2)) {
$result["function_call_{$j}"] = $v2();
}
} catch (Exception $e) {
$result["function_call_{$j}"] = "Error: " . $e->getMessage();
$safe_v1 = var_export(varToString($v1), true);
$safe_v2 = var_export(varToString($v2), true);
$code = '$result["eval_' . $i . '_' . $j . '"] = ' . $safe_v1 . ' . " " . ' . $safe_v2 . ';';
eval($code);
} catch (Exception $e) {
$result["eval_error_{$i}_{$j}"] = "Error: " . $e->getMessage();
}
}
}
}
return $result;
}
$b = new SplObjectStorage();
for ($i = 10000; $i > 0; $i--) {
$object = new StdClass();
$a[] = $object;
$b->attach($object);
}
$fiber = new Fiber(function (): void {
while (true) {
}
});Resulted in this output:
/php-src/ext/spl/spl_observer.c:121:26: runtime error: member access within null pointer of type 'spl_SplObjectStorageElement' (aka 'struct _spl_SplObjectStorageElement')
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /php-src/ext/spl/spl_observer.c:121:26 in
To reproduce:
php -d "memory_limit=2M" ./test.php
PHP Version
PHP 8.4.0-dev
Operating System
ubuntu 22.04
Activity
Girgias commentedon Jun 23, 2024
Can you try and reduce the reproducible?
YuanchengJiang commentedon Jun 23, 2024
@Girgias I have reduced the case by delta debugging (might not be the minimal one). It still requires lots of junk code to be reproduced, which also confuses me.
Girgias commentedon Jun 23, 2024
Okay, I will have a look at this at one point, but I am busy with some other stuff. Does this only reproduce on master or also on PHP 8.2?
YuanchengJiang commentedon Jun 23, 2024
Not sure. I do not have 8.2 in my hands now. It reproduced in PHP 8.4.0-dev.
Hope valgrind output helps:
ndossche commentedon Jul 6, 2024
I can't reproduce this yet, but because it's a NULL deref and there's a memory limit involved, I'm gonna take an educated guess and say this is a classic "publish before initialize" bug. Probably we're allocating some memory and already putting an object somewhere, allocating some more during initialization which fails. Then it bails out and we're hitting a semi-initialized object.
ndossche commentedon Jul 6, 2024
With a bit of fiddling, reduced to
So yeah, indeed what I thought:
spl_object_storage_attach_handlecreates an entry already, but only fills it in at the end withspl_object_storage_create_elementwhich allocates memory. In this case the allocation fails and we're left with a NULL slot. Doing the allocation first isn't an option because we want to check whether the slot is occupied before allocating memory...Fix phpGH-14639: Member access within null pointer in ext/spl/spl_obs…
Merge branch 'PHP-8.2' into PHP-8.3
Merge branch 'PHP-8.3'
Fix GH-14639: Member access within null pointer in ext/spl/spl_observ…