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.
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\Messageconstructor explicitly calling the constructor of the calling class. So something like this will loop infinitely until we smash the stack:The backtrace of the loop looks like this:
As in, the internal constructor for
\Google\Protobuf\Internal\MessagecallsDescriptor_GetFromClassEntry()atmessage.c:557which then bounces throughdef.cwhere there is a check to see if the class entryceis in the object cache, which it wouldn't be since we are still in the constructor trying to create the class. SoNameMap_GetMessage(ce)is called ondef.c:516to get the message definition and here we hit our loop because inNameMap_GetMessage()there is an explicit call tozend_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 tomessage.c:557and 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:
The bt is:
The crash is obvious from that.
descis null and there is no null check before trying to dereference it on line 545.