Skip to content

Member access within null pointer in ext/spl/spl_observer.c #14639

Closed

Description

@YuanchengJiang

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

  1. Girgias commented on Jun 23, 2024

    @Girgias
    Member

    Can you try and reduce the reproducible?

  2. YuanchengJiang commented on Jun 23, 2024

    @YuanchengJiang
    Author

    @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.

  3. Girgias commented on Jun 23, 2024

    @Girgias
    Member

    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?

  4. YuanchengJiang commented on Jun 23, 2024

    @YuanchengJiang
    Author

    Not sure. I do not have 8.2 in my hands now. It reproduced in PHP 8.4.0-dev.

    Hope valgrind output helps:

    ==788749== Process terminating with default action of signal 11 (SIGSEGV)
    ==788749==  Access not within mapped region at address 0x0
    ==788749==    at 0x625058: spl_object_storage_dtor (spl_observer.c:122)
    ==788749==    by 0x83A3E6: zend_hash_destroy (zend_hash.c:1741)
    ==788749==    by 0x624E9C: spl_SplObjectStorage_free_storage (spl_observer.c:82)
    ==788749==    by 0x91FCF1: zend_objects_store_free_object_storage (zend_objects_API.c:122)
    ==788749==    by 0x80136F: zend_shutdown_executor_values (zend_execute_API.c:401)
    ==788749==    by 0x801404: shutdown_executor (zend_execute_API.c:418)
    ==788749==    by 0x81CB14: zend_deactivate (zend.c:1308)
    ==788749==    by 0x76B9D9: php_request_shutdown (main.c:1886)
    ==788749==    by 0x9AA0E3: do_cli (php_cli.c:1136)
    ==788749==    by 0x9AA5F0: main (php_cli.c:1340)
    
  5. ndossche commented on Jul 6, 2024

    @ndossche
    Member

    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.

  6. ndossche commented on Jul 6, 2024

    @ndossche
    Member

    With a bit of fiddling, reduced to

    <?php
    $b = new SplObjectStorage();
    for ($i = 10000; $i > 0; $i--) {
        $object = new StdClass();
        $object->a = str_repeat("a", 2);
        $b->attach($object);
    }
    

    So yeah, indeed what I thought: spl_object_storage_attach_handle creates an entry already, but only fills it in at the end with spl_object_storage_create_element which 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...

  7. added a commit that references this issue on Jul 6, 2024
    f04517a
  8. added 3 commits that reference this issue on Jul 6, 2024
    8ea3f15
    e2189be
    0d4e0c0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions