Skip to content

Two segfaults in the PHP extension #8229

@rlerdorf

Description

@rlerdorf

protobuf master branch on Linux with gcc-10.2 and tested against PHP 7.3, 7.4 and 8.0

Crash 1 is a stack overflow caused by the \Google\Protobuf\Internal\Message constructor explicitly calling the constructor of the calling class. So something like this will loop infinitely until we smash the stack:

<?php
class C extends \Google\Protobuf\Internal\Message {
    public function __construct($data = null) {
        parent::__construct($data);
    }
}
$p = new C();

The backtrace of the loop looks like this:

#12 0x00007ffff38a21cf in NameMap_GetMessage (ce=0x7ffff3603018) at /home/rasmus/src/protobuf/php/ext/google/protobuf/protobuf.c:247
#13 0x00007ffff3889930 in Descriptor_FromClassEntry (ce=0x7ffff3603018, val=0x7fffff7ffa90) at /home/rasmus/src/protobuf/php/ext/google/protobuf/def.c:516
#14 Descriptor_FromClassEntry (val=0x7fffff7ffa90, ce=0x7ffff3603018) at /home/rasmus/src/protobuf/php/ext/google/protobuf/def.c:509
#15 0x00007ffff3889aff in Descriptor_GetFromClassEntry (ce=<optimized out>) at /home/rasmus/src/protobuf/php/ext/google/protobuf/def.c:537
#16 0x00007ffff389113a in zim_Message___construct (execute_data=0x7ffff2a66010, return_value=<optimized out>) at /home/rasmus/src/protobuf/php/ext/google/protobuf/message.c:557

As in, the internal constructor for \Google\Protobuf\Internal\Message calls Descriptor_GetFromClassEntry() at message.c:557 which then bounces through def.c where there is a check to see if the class entry ce is in the object cache, which it wouldn't be since we are still in the constructor trying to create the class. So NameMap_GetMessage(ce) is called on def.c:516 to get the message definition and here we hit our loop because in NameMap_GetMessage() there is an explicit call to zend_call_method_with_0_params(&tmp, ce, NULL, "__construct", &zv); which calls the userspace constructor for the calling class and we start the loop all over again since the userspace constructor calls the parent constructor and we are back to message.c:557 and none of the cache checks will come back true since we never got to the stage where we cached anything.

The second crash is in the same script with a userspace fix for the above looping problem added:

<?php
class C extends \Google\Protobuf\Internal\Message {
    public function __construct($data = null) {
        static $i = -1;
        $i++;
        if(!$i) parent::__construct($data);
    }
}
$p = new C();

The bt is:

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff389115d in Message_Initialize (desc=0x0, intern=0x7ffff366e180) at /home/rasmus/src/protobuf/php/ext/google/protobuf/message.c:545
warning: Source file is more recent than executable.
545	 intern->msg = upb_msg_new(desc->msgdef, Arena_Get(&intern->arena));
(gdb) bt
#0  0x00007ffff389115d in Message_Initialize (desc=0x0, intern=0x7ffff366e180) at /home/rasmus/src/protobuf/php/ext/google/protobuf/message.c:545
#1  zim_Message___construct (execute_data=0x7ffff3613150, return_value=<optimized out>) at /home/rasmus/src/protobuf/php/ext/google/protobuf/message.c:561
(gdb) l
543	static void Message_Initialize(Message *intern, const Descriptor *desc) {
544	 intern->desc = desc;
545	 intern->msg = upb_msg_new(desc->msgdef, Arena_Get(&intern->arena));
546	 ObjCache_Add(intern->msg, &intern->std);
547	}

The crash is obvious from that. desc is null and there is no null check before trying to dereference it on line 545.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions