This is an automated email from the ASF dual-hosted git repository.

pjfanning pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git


The following commit(s) were added to refs/heads/main by this push:
     new 3c5449c137 fix: don't dereference a null superclass in the protobuf 
allow list check (#3509)
3c5449c137 is described below

commit 3c5449c13744bf5b5c85bb38c91b1aacabaa552b
Author: PJ Fanning <[email protected]>
AuthorDate: Mon Sep 7 07:53:33 2026 +0100

    fix: don't dereference a null superclass in the protobuf allow list check 
(#3509)
    
    Motivation:
    ProtobufSerializer.isInAllowListClassName reads clazz.getSuperclass.getName
    unconditionally. getSuperclass is null for an interface, for Object and for
    a primitive, and the class it checks is the manifest from the wire, so a
    manifest naming an interface raised NullPointerException from the allow
    list check rather than the IllegalArgumentException that refusing a class
    is meant to produce.
    
    isInAllowList also evaluated isBoundToProtobufSerializer first, which calls
    serializerFor and raises, filling in a stack trace, for a class that is not
    bound - the common case for a class allowed only by name.
    
    Modification:
    Skip the superclass when there is none, and test the name list before the
    binding, which cannot throw. Both operands are pure predicates so the
    decision is unchanged.
    
    Result:
    An interface or Object manifest is refused with the allow list error
    instead of a NullPointerException.
---
 .../remote/serialization/ProtobufSerializer.scala      |  9 +++++++--
 .../remote/serialization/ProtobufSerializerSpec.scala  | 18 ++++++++++++++++++
 2 files changed, 25 insertions(+), 2 deletions(-)

diff --git 
a/remote/src/main/scala/org/apache/pekko/remote/serialization/ProtobufSerializer.scala
 
b/remote/src/main/scala/org/apache/pekko/remote/serialization/ProtobufSerializer.scala
index e2503d11bc..c45442cbec 100644
--- 
a/remote/src/main/scala/org/apache/pekko/remote/serialization/ProtobufSerializer.scala
+++ 
b/remote/src/main/scala/org/apache/pekko/remote/serialization/ProtobufSerializer.scala
@@ -181,7 +181,10 @@ class ProtobufSerializer(val system: ExtendedActorSystem) 
extends BaseSerializer
    * and still bind with the same class (interface).
    */
   private def isInAllowList(clazz: Class[?]): Boolean = {
-    isBoundToProtobufSerializer(clazz) || isInAllowListClassName(clazz)
+    // The name check comes first because it cannot throw: 
`isBoundToProtobufSerializer` calls
+    // `serializerFor`, which raises (and fills in the stack trace of) a 
NotSerializableException
+    // for a class that is not bound.
+    isInAllowListClassName(clazz) || isBoundToProtobufSerializer(clazz)
   }
 
   private def isBoundToProtobufSerializer(clazz: Class[?]): Boolean = {
@@ -194,8 +197,10 @@ class ProtobufSerializer(val system: ExtendedActorSystem) 
extends BaseSerializer
   }
 
   private def isInAllowListClassName(clazz: Class[?]): Boolean = {
+    // getSuperclass is null for an interface, for Object and for a primitive, 
and the manifest
+    // class comes off the wire, so it can be any of those
     allowedClassNames(clazz.getName) ||
-    allowedClassNames(clazz.getSuperclass.getName) ||
+    ((clazz.getSuperclass ne null) && 
allowedClassNames(clazz.getSuperclass.getName)) ||
     clazz.getInterfaces.exists(c => allowedClassNames(c.getName))
   }
 }
diff --git 
a/remote/src/test/scala/org/apache/pekko/remote/serialization/ProtobufSerializerSpec.scala
 
b/remote/src/test/scala/org/apache/pekko/remote/serialization/ProtobufSerializerSpec.scala
index 3b9885398e..9d79c066bc 100644
--- 
a/remote/src/test/scala/org/apache/pekko/remote/serialization/ProtobufSerializerSpec.scala
+++ 
b/remote/src/test/scala/org/apache/pekko/remote/serialization/ProtobufSerializerSpec.scala
@@ -123,6 +123,24 @@ class ProtobufSerializerSpec extends PekkoSpec(s"""
       }
     }
 
+    "reject an interface manifest rather than failing on its missing 
superclass" in {
+      // getSuperclass is null for an interface, and the manifest class comes 
off the wire.
+      // This used to raise NullPointerException from the allow list check 
instead of the
+      // IllegalArgumentException that refusing the class is supposed to 
produce.
+      val originalSerializer = ser.serializerFor(classOf[MyMessage])
+      intercept[IllegalArgumentException] {
+        ser.deserialize(Array[Byte](), originalSerializer.identifier, 
classOf[Runnable].getName).get
+      }.getMessage should include("allow list")
+    }
+
+    "reject java.lang.Object as a manifest" in {
+      // Object.getSuperclass is null too
+      val originalSerializer = ser.serializerFor(classOf[MyMessage])
+      intercept[IllegalArgumentException] {
+        ser.deserialize(Array[Byte](), originalSerializer.identifier, 
classOf[Object].getName).get
+      }.getMessage should include("allow list")
+    }
+
     "allow deserialization of classes in configured allowed classes" in {
       val originalSerializer = ser.serializerFor(classOf[MyMessage])
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to