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]
