Skip to content

Use after free with weakmaps dependent on destruction order #18833

Closed
@danog

Description

@danog

Description

The following code:

<?php

class a {
    public static WeakMap $map;
    public static Generator $storage;
}

a::$map = new WeakMap;

$closure = function () {
    $obj = new a;
    a::$map[$obj] = true;
    yield $obj;
};
a::$storage = $closure();
a::$storage->current();

Causes a use after free, due to the following destruction order in zend_objects_store_free_object_storage:

1. a
2. Generator
3. Closure
4. WeakMap

At 1, obj->handlers->free_obj is actually never invoked for a and only the flag is set, because normally we're in the fast fast_shutdown path, which skips invoking free_obj for standard PHP objects (thus, the weak map is not notified).
At 2, the zend_generator_free_storage procedure causes a to be actually freed with efree, since it is only referenced by the generator, which is being freed; thus, a is actually freed without ever notifying the weak map.
At 4, the weak map accesses the already freed object while iterating over the members.

The issue was particularly nasty to reproduce as it won't reproduce by just disabling the zend allocator and enabling ASAN, as the fast shutdown path is for some reason disabled when using custom allocators (will submit a PR to fix that on ASAN).

phpredis/phpredis#2630 is a direct consequence of this bug.

Submitting a PR with a fix, and another PR with misc related improvements.

PHP Version

PHP 8.3.22+

Operating System

No response

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