okumin commented on code in PR #6726:
URL: https://github.com/apache/hive/pull/6726#discussion_r3975975040


##########
ql/src/java/org/apache/hadoop/hive/ql/exec/SerializationUtilities.java:
##########
@@ -237,31 +242,102 @@ public Configuration getConf() {
 
     @Override
     public com.esotericsoftware.kryo.kryo5.Registration getRegistration(Class 
type) {
-      // If PartitionExpressionForMetastore performs deserialization at remote 
HMS,
-      // the first class encountered during deserialization must be an 
ExprNodeDesc,
-      // throw exception to avoid potential security problem if it is not.
-      if (isExprNodeFirst && classCounter == 0) {
-        if (!ExprNodeDesc.class.isAssignableFrom(type)) {
+      // If this instance deserializes a payload that a remote client controls 
(e.g. PartitionExpressionForMetastore at
+      // a remote HMS) or that a client can persist (e.g. a table property 
copied into the job conf), the first class
+      // encountered during deserialization must be compatible with the 
expected root type, and every class in the
+      // stream must pass the allowlist check. Kryo is otherwise willing to 
instantiate any classpath class named by the
+      // payload (registrationRequired=false plus StdInstantiatorStrategy), 
which turns these payloads into a
+      // deserialization-of-untrusted-data primitive.
+      if (untrustedRootType != null) {
+        if (classCounter == 0 && !untrustedRootType.isAssignableFrom(type)) {
+          throw new UnsupportedOperationException("The object to be 
deserialized must be a "
+              + untrustedRootType.getName() + ", but encountered: " + type);
+        }
+        if (!isAllowedForUntrustedDeserialization(type)) {
           throw new UnsupportedOperationException(
-              "The object to be deserialized must be an ExprNodeDesc, but 
encountered: " + type);
+              "Deserialization of " + type + " is not allowed from an 
untrusted payload");
         }
       }
       classCounter++;
       return super.getRegistration(type);
     }
 
     public void setExprNodeFirst(boolean isPartFilter) {
-      this.isExprNodeFirst = isPartFilter;
+      setUntrustedRootType(isPartFilter ? ExprNodeDesc.class : null);
+    }
+
+    void setUntrustedRootType(Class<?> rootType) {
+      this.untrustedRootType = rootType;
+      this.classCounter = 0;
     }
 
     // reset the fields on release
     public void restore() {
       setConf(null);
-      isExprNodeFirst = false;
+      untrustedRootType = null;
       classCounter = 0;
     }
   }
 
+  /**
+   * Package prefixes that classes read from an untrusted Kryo payload may 
come from. These cover everything a
+   * legitimate serialized expression ({@link ExprNodeDesc} graph) or search 
argument (SearchArgumentImpl graph)
+   * contains: expression descriptors and plan literals, builtin and installed 
UDFs, type infos and object inspectors,
+   * Hive/Hadoop value types, and plain JDK value/collection classes. Known 
gadget carriers (commons-collections,
+   * beanutils, xalan/TemplatesImpl, ...) all live outside these prefixes.
+   */
+  private static final String[] UNTRUSTED_ALLOWED_PACKAGE_PREFIXES = new 
String[] {
+      "java.lang.",
+      "java.util.",
+      "java.sql.",
+      "java.time.",
+      "java.math.",
+      "org.apache.hadoop.hive.ql.plan.",
+      "org.apache.hadoop.hive.ql.udf.",
+      "org.apache.hadoop.hive.ql.io.sarg.",
+      "org.apache.hadoop.hive.serde2.",
+      "org.apache.hadoop.hive.common.type.",
+      "org.apache.hadoop.io."
+  };
+
+  /**
+   * Classes that are never acceptable in an untrusted payload even though 
they pass the package allowlist.
+   * GenericUDFReflect, GenericUDFReflect2, and GenericUDFInFile are typically 
disallowed in a secure environment.
+   * {@link 
org.apache.hadoop.hive.ql.security.authorization.plugin.SettableConfigUpdater}
+   */
+  private static final Set<String> UNTRUSTED_DENIED_CLASS_NAMES = new 
HashSet<>(Arrays.asList(
+      "org.apache.hadoop.hive.ql.udf.generic.GenericUDFReflect",
+      "org.apache.hadoop.hive.ql.udf.generic.GenericUDFReflect2",
+      "org.apache.hadoop.hive.ql.udf.generic.GenericUDFInFile"
+  ));
+
+  @VisibleForTesting
+  static boolean isAllowedForUntrustedDeserialization(Class<?> type) {
+    Class<?> component = type;
+    while (component.isArray()) {
+      component = component.getComponentType();
+    }
+    if (component.isPrimitive()) {
+      return true;
+    }
+    String name = component.getName();
+    if (UNTRUSTED_DENIED_CLASS_NAMES.contains(name)) {

Review Comment:
   IMO, HMS should not resolve HS2's configurations in most cases. Also, 
`hive.server2.builtin.udf.blacklist` is usually configured via 
`SettableConfigUpdater`. It is not easy to ensure that the class is invoked 
from HMS. Since these three classes aren't helpful for filtering out 
partitions, I'd say banning them doesn't introduce a new issue.



##########
ql/src/java/org/apache/hadoop/hive/ql/exec/SerializationUtilities.java:
##########
@@ -237,31 +242,102 @@ public Configuration getConf() {
 
     @Override
     public com.esotericsoftware.kryo.kryo5.Registration getRegistration(Class 
type) {
-      // If PartitionExpressionForMetastore performs deserialization at remote 
HMS,
-      // the first class encountered during deserialization must be an 
ExprNodeDesc,
-      // throw exception to avoid potential security problem if it is not.
-      if (isExprNodeFirst && classCounter == 0) {
-        if (!ExprNodeDesc.class.isAssignableFrom(type)) {
+      // If this instance deserializes a payload that a remote client controls 
(e.g. PartitionExpressionForMetastore at
+      // a remote HMS) or that a client can persist (e.g. a table property 
copied into the job conf), the first class
+      // encountered during deserialization must be compatible with the 
expected root type, and every class in the
+      // stream must pass the allowlist check. Kryo is otherwise willing to 
instantiate any classpath class named by the
+      // payload (registrationRequired=false plus StdInstantiatorStrategy), 
which turns these payloads into a
+      // deserialization-of-untrusted-data primitive.
+      if (untrustedRootType != null) {
+        if (classCounter == 0 && !untrustedRootType.isAssignableFrom(type)) {
+          throw new UnsupportedOperationException("The object to be 
deserialized must be a "
+              + untrustedRootType.getName() + ", but encountered: " + type);
+        }
+        if (!isAllowedForUntrustedDeserialization(type)) {
           throw new UnsupportedOperationException(
-              "The object to be deserialized must be an ExprNodeDesc, but 
encountered: " + type);
+              "Deserialization of " + type + " is not allowed from an 
untrusted payload");
         }
       }
       classCounter++;
       return super.getRegistration(type);
     }
 
     public void setExprNodeFirst(boolean isPartFilter) {
-      this.isExprNodeFirst = isPartFilter;
+      setUntrustedRootType(isPartFilter ? ExprNodeDesc.class : null);
+    }
+
+    void setUntrustedRootType(Class<?> rootType) {
+      this.untrustedRootType = rootType;
+      this.classCounter = 0;
     }
 
     // reset the fields on release
     public void restore() {
       setConf(null);
-      isExprNodeFirst = false;
+      untrustedRootType = null;
       classCounter = 0;
     }
   }
 
+  /**
+   * Package prefixes that classes read from an untrusted Kryo payload may 
come from. These cover everything a
+   * legitimate serialized expression ({@link ExprNodeDesc} graph) or search 
argument (SearchArgumentImpl graph)
+   * contains: expression descriptors and plan literals, builtin and installed 
UDFs, type infos and object inspectors,
+   * Hive/Hadoop value types, and plain JDK value/collection classes. Known 
gadget carriers (commons-collections,
+   * beanutils, xalan/TemplatesImpl, ...) all live outside these prefixes.
+   */
+  private static final String[] UNTRUSTED_ALLOWED_PACKAGE_PREFIXES = new 
String[] {
+      "java.lang.",
+      "java.util.",
+      "java.sql.",
+      "java.time.",
+      "java.math.",
+      "org.apache.hadoop.hive.ql.plan.",
+      "org.apache.hadoop.hive.ql.udf.",
+      "org.apache.hadoop.hive.ql.io.sarg.",
+      "org.apache.hadoop.hive.serde2.",
+      "org.apache.hadoop.hive.common.type.",
+      "org.apache.hadoop.io."
+  };
+
+  /**
+   * Classes that are never acceptable in an untrusted payload even though 
they pass the package allowlist.
+   * GenericUDFReflect, GenericUDFReflect2, and GenericUDFInFile are typically 
disallowed in a secure environment.
+   * {@link 
org.apache.hadoop.hive.ql.security.authorization.plugin.SettableConfigUpdater}
+   */
+  private static final Set<String> UNTRUSTED_DENIED_CLASS_NAMES = new 
HashSet<>(Arrays.asList(
+      "org.apache.hadoop.hive.ql.udf.generic.GenericUDFReflect",
+      "org.apache.hadoop.hive.ql.udf.generic.GenericUDFReflect2",
+      "org.apache.hadoop.hive.ql.udf.generic.GenericUDFInFile"
+  ));
+
+  @VisibleForTesting
+  static boolean isAllowedForUntrustedDeserialization(Class<?> type) {
+    Class<?> component = type;
+    while (component.isArray()) {
+      component = component.getComponentType();
+    }
+    if (component.isPrimitive()) {
+      return true;
+    }
+    String name = component.getName();
+    if (UNTRUSTED_DENIED_CLASS_NAMES.contains(name)) {
+      return false;
+    }
+    // Custom (temporary/permanent) UDFs live in user packages. The classes 
themselves were
+    // installed by an administrator, so allowing kryo to instantiate them is 
no worse than any
+    // query invoking them.
+    if (GenericUDF.class.isAssignableFrom(component) || 
UDF.class.isAssignableFrom(component)) {
+      return true;
+    }
+    for (String prefix : UNTRUSTED_ALLOWED_PACKAGE_PREFIXES) {
+      if (name.startsWith(prefix)) {
+        return true;
+      }
+    }
+    return false;

Review Comment:
   Your point is understandable, but I don't have an effective way to restrict 
only what we really want to block.
   One option is to disallow non-built-in UDFs as well. This could be 
reasonable. Does anyone want to extend the reflective partitioning filtering 
beyond standard UDFs? If not, we may remove L330-332.



##########
ql/src/java/org/apache/hadoop/hive/ql/optimizer/ppr/PartitionExpressionForMetastore.java:
##########
@@ -111,21 +115,64 @@ private ExprNodeDesc deserializeExpr(byte[] exprBytes) 
throws MetaException {
     try {
       expr = 
SerializationUtilities.deserializeObjectWithTypeInformation(exprBytes, true);
     } catch (Exception ex) {
-      LOG.error("Failed to deserialize the expression, fall back to 
deserializeObjectFromKryo", ex);
+      LOG.error("Failed to deserialize the expression, fall back to 
deserializeUntrustedObjectFromKryo", ex);
       try {
-        expr = SerializationUtilities.deserializeObjectFromKryo(exprBytes, 
ExprNodeGenericFuncDesc.class);
+        // The fallback must use the same untrusted-payload restrictions as 
the primary path: these bytes come straight
+        // from a Thrift client.
+        expr = 
SerializationUtilities.deserializeUntrustedObjectFromKryo(exprBytes, 
ExprNodeGenericFuncDesc.class);
       } catch (Exception e) {
         LOG.error("Failed to deserialize the expression", e);
         throw new 
MetaException("SerializationUtilities#deserializeObjectWithTypeInformation: " + 
ex.getMessage() +
-            ", SerializationUtilities#deserializeObjectFromKryo: " + 
e.getMessage());
+            ", SerializationUtilities#deserializeUntrustedObjectFromKryo: " + 
e.getMessage());
       }
     }
     if (expr == null) {
       throw new MetaException("Failed to deserialize expression - ExprNodeDesc 
not present");
     }
+    validateDeserializedExpr(expr);
     return expr;
   }
 
+  /**
+   * Rejects client-supplied expression graphs that would execute arbitrary 
code when the metastore stringifies or
+   * evaluates them. The Kryo-level class allowlist already blocks 
reflect/reflect2/java_method/in_file; a
+   * {@link GenericUDFBridge} instance is legitimate (it wraps builtin 
old-style UDFs like year()), but it instantiates
+   * whatever class name its {@code udfClassName} field carries, so that name 
must resolve to a real {@link UDF}.
+   */
+  private void validateDeserializedExpr(ExprNodeDesc expr) throws 
MetaException {
+    if (expr instanceof ExprNodeGenericFuncDesc exprNodeGenericFuncDesc) {
+      validateDeserializedExprNodeGenericFuncDesc(exprNodeGenericFuncDesc);
+    }
+    if (expr.getChildren() != null) {
+      for (ExprNodeDesc child : expr.getChildren()) {
+        validateDeserializedExpr(child);
+      }
+    }
+  }
+
+  private void 
validateDeserializedExprNodeGenericFuncDesc(ExprNodeGenericFuncDesc expr) 
throws MetaException {
+    GenericUDF genericUDF = expr.getGenericUDF();
+    if (genericUDF instanceof GenericUDFBridge genericUDFBridge) {
+      String udfClassName = genericUDFBridge.getUdfClassName();
+      Class<?> udfClass;

Review Comment:
   It is reasonable. I updated.
   
https://github.com/apache/hive/pull/6726/changes/b1edbc84e488b66ac0b69fee6628675e394e5782



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to