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

asf-gitbox-commits pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/spark.git

commit 3bd8d1191abdcdab7a1e111cd5032be80b95cf3a
Author: Peter Toth <[email protected]>
AuthorDate: Thu Jul 9 13:42:43 2026 -0700

    [SPARK-58042][CONNECT] Validate the UDT jvm_class is a UserDefinedType 
before instantiating it
    
    Backport of SPARK-58042 to branch-4.0. DataTypeProtoConverter rejects a 
Spark
    Connect UDT whose jvm_class is not a UserDefinedType before running its
    constructor, loading the class without initializing it to avoid triggering a
    crafted class's static initializer. branch-4.0's InvalidPlanInput carries a
    plain message (it is not yet a SparkThrowable with error classes), so the 
check
    throws a plain-message InvalidPlanInput with SPARK-58042's wording instead 
of the
    CONNECT_INVALID_PLAN.UDT_JVM_CLASS_NOT_UDT error condition.
    
    Authored-by: Peter Toth <[email protected]>
    Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
    Co-Authored-By: Holden Karau <[email protected]>
---
 .../sql/connect/common/DataTypeProtoConverter.scala      | 15 +++++++++++----
 .../sql/connect/messages/ConnectProtoMessagesSuite.scala | 16 +++++++++++++++-
 2 files changed, 26 insertions(+), 5 deletions(-)

diff --git 
a/sql/connect/common/src/main/scala/org/apache/spark/sql/connect/common/DataTypeProtoConverter.scala
 
b/sql/connect/common/src/main/scala/org/apache/spark/sql/connect/common/DataTypeProtoConverter.scala
index 8c83ad3d1f55..5930a4cbb5fc 100644
--- 
a/sql/connect/common/src/main/scala/org/apache/spark/sql/connect/common/DataTypeProtoConverter.scala
+++ 
b/sql/connect/common/src/main/scala/org/apache/spark/sql/connect/common/DataTypeProtoConverter.scala
@@ -130,10 +130,17 @@ object DataTypeProtoConverter {
     }
 
     if (t.hasJvmClass) {
-      SparkClassUtils
-        .classForName[UserDefinedType[_]](t.getJvmClass)
-        .getConstructor()
-        .newInstance()
+      // Verify the class is a UserDefinedType before constructing it. 
newInstance() runs the
+      // class's no-arg constructor, so load it without initializing and check 
the type first,
+      // rather than instantiating an arbitrary client-provided class name and 
relying on a
+      // later cast (which happens only after the constructor has already run).
+      val clazz =
+        SparkClassUtils.classForName[UserDefinedType[_]](t.getJvmClass, 
initialize = false)
+      if (!classOf[UserDefinedType[_]].isAssignableFrom(clazz)) {
+        throw InvalidPlanInput(
+          s"The 'jvm_class' ${t.getJvmClass} in the UDT is not a subclass of 
UserDefinedType.")
+      }
+      clazz.getConstructor().newInstance()
     } else {
       if (!t.hasPythonClass || !t.hasSerializedPythonClass || !t.hasSqlType) {
         throw InvalidPlanInput(
diff --git 
a/sql/connect/server/src/test/scala/org/apache/spark/sql/connect/messages/ConnectProtoMessagesSuite.scala
 
b/sql/connect/server/src/test/scala/org/apache/spark/sql/connect/messages/ConnectProtoMessagesSuite.scala
index 65c03a3c2e29..ccaf18bf9954 100644
--- 
a/sql/connect/server/src/test/scala/org/apache/spark/sql/connect/messages/ConnectProtoMessagesSuite.scala
+++ 
b/sql/connect/server/src/test/scala/org/apache/spark/sql/connect/messages/ConnectProtoMessagesSuite.scala
@@ -20,7 +20,7 @@ import com.google.protobuf.ByteString
 
 import org.apache.spark.SparkFunSuite
 import org.apache.spark.connect.proto
-import org.apache.spark.sql.connect.common.DataTypeProtoConverter
+import org.apache.spark.sql.connect.common.{DataTypeProtoConverter, 
InvalidPlanInput}
 import org.apache.spark.sql.types.IntegerType
 
 class ConnectProtoMessagesSuite extends SparkFunSuite {
@@ -86,4 +86,18 @@ class ConnectProtoMessagesSuite extends SparkFunSuite {
     assert(fun.hasPythonUdf == true)
     assert(pythonUdf.getPythonVer == "3.10")
   }
+
+  test("SPARK-58042: UDT jvm_class must be a UserDefinedType") {
+    // A jvm_class that is not a UserDefinedType must be rejected before it is 
instantiated.
+    val udt = proto.DataType.UDT
+      .newBuilder()
+      .setType("udt")
+      .setJvmClass("java.lang.String")
+      .build()
+    val dataType = proto.DataType.newBuilder().setUdt(udt).build()
+    val exception = intercept[InvalidPlanInput] {
+      DataTypeProtoConverter.toCatalystType(dataType)
+    }
+    assert(exception.getMessage.contains("is not a subclass of 
UserDefinedType"))
+  }
 }


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

Reply via email to