Protect against stack overflow if the user derives from Message. (#8248)
* Protect against stack overflow if the user derives from Message.
* For pure-PHP, change error into an exception.
diff --git a/php/ext/google/protobuf/message.c b/php/ext/google/protobuf/message.c
index 0e61770..c8ea964 100644
--- a/php/ext/google/protobuf/message.c
+++ b/php/ext/google/protobuf/message.c
@@ -554,10 +554,32 @@
*/
PHP_METHOD(Message, __construct) {
Message* intern = (Message*)Z_OBJ_P(getThis());
- const Descriptor* desc = Descriptor_GetFromClassEntry(Z_OBJCE_P(getThis()));
+ const Descriptor* desc;
+ zend_class_entry *ce = Z_OBJCE_P(getThis());
upb_arena *arena = Arena_Get(&intern->arena);
zval *init_arr = NULL;
+ // This descriptor should always be available, as the generated __construct
+ // method calls initOnce() to load the descriptor prior to calling us.
+ //
+ // However, if the user created their own class derived from Message, this
+ // will trigger an infinite construction loop and blow the stack. We
+ // temporarily clear create_object to break this loop (see check in
+ // NameMap_GetMessage()).
+ PBPHP_ASSERT(ce->create_object == Message_create);
+ ce->create_object = NULL;
+ desc = Descriptor_GetFromClassEntry(ce);
+ ce->create_object = Message_create;
+
+ if (!desc) {
+ zend_throw_exception_ex(
+ NULL, 0,
+ "Couldn't find descriptor. Note only generated code may derive from "
+ "\\Google\\Protobuf\\Internal\\Message");
+ return;
+ }
+
+
Message_Initialize(intern, desc);
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|a!", &init_arr) == FAILURE) {
diff --git a/php/src/Google/Protobuf/Internal/Message.php b/php/src/Google/Protobuf/Internal/Message.php
index c02d2b4..64aadf9 100644
--- a/php/src/Google/Protobuf/Internal/Message.php
+++ b/php/src/Google/Protobuf/Internal/Message.php
@@ -93,8 +93,9 @@
$pool = DescriptorPool::getGeneratedPool();
$this->desc = $pool->getDescriptorByClassName(get_class($this));
if (is_null($this->desc)) {
- user_error(get_class($this) . " is not found in descriptor pool.");
- return;
+ throw new \InvalidArgumentException(
+ get_class($this) ." is not found in descriptor pool. " .
+ 'Only generated classes may derive from Message.');
}
foreach ($this->desc->getField() as $field) {
$setter = $field->getSetter();
diff --git a/php/tests/GeneratedClassTest.php b/php/tests/GeneratedClassTest.php
index c0de0ba..d1836a9 100644
--- a/php/tests/GeneratedClassTest.php
+++ b/php/tests/GeneratedClassTest.php
@@ -24,6 +24,13 @@
use PBEmpty\PBEcho\TestEmptyPackage;
use Php\Test\TestNamespace;
+# This is not allowed, but we at least shouldn't crash.
+class C extends \Google\Protobuf\Internal\Message {
+ public function __construct($data = null) {
+ parent::__construct($data);
+ }
+}
+
class GeneratedClassTest extends TestBase
{
@@ -1724,6 +1731,16 @@
}
#########################################################
+ # Test that we don't crash if users create their own messages.
+ #########################################################
+
+ public function testUserDefinedClass() {
+ # This is not allowed, but at least we shouldn't crash.
+ $this->expectException(Exception::class);
+ $p = new C();
+ }
+
+ #########################################################
# Test no segfault when error happens
#########################################################