Skip to content

Commit 60027bb

Browse files
authored
HIVE-26539: Prevent unsafe deserialization in PartitionExpressionForMetastore (#3605)
1 parent 88fb576 commit 60027bb

File tree

4 files changed

+64
-25
lines changed

4 files changed

+64
-25
lines changed

ql/src/java/org/apache/hadoop/hive/ql/exec/SerializationUtilities.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ private static class KryoWithHooks extends Kryo implements Configurable {
124124
private Hook globalHook;
125125
// this should be set on-the-fly after borrowing this instance and needs to be reset on release
126126
private Configuration configuration;
127+
// default false, should be reset on release
128+
private boolean isExprNodeFirst = false;
129+
// total classes we have met during (de)serialization, should be reset on release
130+
private long classCounter = 0;
127131

128132
@SuppressWarnings({"unchecked", "rawtypes"})
129133
private static final class SerializerWithHook extends com.esotericsoftware.kryo.Serializer {
@@ -228,6 +232,32 @@ public void setConf(Configuration conf) {
228232
public Configuration getConf() {
229233
return configuration;
230234
}
235+
236+
@Override
237+
public com.esotericsoftware.kryo.Registration getRegistration(Class type) {
238+
// If PartitionExpressionForMetastore performs deserialization at remote HMS,
239+
// the first class encountered during deserialization must be an ExprNodeDesc,
240+
// throw exception to avoid potential security problem if it is not.
241+
if (isExprNodeFirst && classCounter == 0) {
242+
if (!ExprNodeDesc.class.isAssignableFrom(type)) {
243+
throw new UnsupportedOperationException(
244+
"The object to be deserialized must be an ExprNodeDesc, but encountered: " + type);
245+
}
246+
}
247+
classCounter++;
248+
return super.getRegistration(type);
249+
}
250+
251+
public void setExprNodeFirst(boolean isPartFilter) {
252+
this.isExprNodeFirst = isPartFilter;
253+
}
254+
255+
// reset the fields on release
256+
public void restore() {
257+
setConf(null);
258+
isExprNodeFirst = false;
259+
classCounter = 0;
260+
}
231261
}
232262

233263
private static final Object FAKE_REFERENCE = new Object();
@@ -294,7 +324,7 @@ public static Kryo borrowKryo(Configuration configuration) {
294324
*/
295325
public static void releaseKryo(Kryo kryo) {
296326
if (kryo != null){
297-
((KryoWithHooks) kryo).setConf(null);
327+
((KryoWithHooks) kryo).restore();
298328
}
299329
kryoPool.free(kryo);
300330
}
@@ -830,10 +860,13 @@ public static byte[] serializeObjectWithTypeInformation(Serializable object) {
830860
/**
831861
* Deserializes expression from Kryo.
832862
* @param bytes Bytes containing the expression.
863+
* @param isPartFilter ture if it is a partition filter
833864
* @return Expression; null if deserialization succeeded, but the result type is incorrect.
834865
*/
835-
public static <T> T deserializeObjectWithTypeInformation(byte[] bytes) {
836-
Kryo kryo = borrowKryo();
866+
public static <T> T deserializeObjectWithTypeInformation(byte[] bytes,
867+
boolean isPartFilter) {
868+
KryoWithHooks kryo = (KryoWithHooks) borrowKryo();
869+
kryo.setExprNodeFirst(isPartFilter);
837870
try (Input inp = new Input(new ByteArrayInputStream(bytes))) {
838871
return (T) kryo.readClassAndObject(inp);
839872
} finally {

ql/src/java/org/apache/hadoop/hive/ql/optimizer/ppr/PartitionExpressionForMetastore.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import org.apache.hadoop.hive.ql.parse.SemanticException;
3838
import org.apache.hadoop.hive.ql.plan.ExprNodeDesc;
3939
import org.apache.hadoop.hive.ql.plan.ExprNodeDescUtils;
40-
import org.apache.hadoop.hive.ql.plan.ExprNodeGenericFuncDesc;
4140
import org.apache.hadoop.hive.serde2.typeinfo.PrimitiveTypeInfo;
4241
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory;
4342
import org.slf4j.Logger;
@@ -108,7 +107,7 @@ public boolean filterPartitionsByExpr(List<FieldSchema> partColumns,
108107
private ExprNodeDesc deserializeExpr(byte[] exprBytes) throws MetaException {
109108
ExprNodeDesc expr = null;
110109
try {
111-
expr = SerializationUtilities.deserializeObjectWithTypeInformation(exprBytes);
110+
expr = SerializationUtilities.deserializeObjectWithTypeInformation(exprBytes, true);
112111
} catch (Exception ex) {
113112
LOG.error("Failed to deserialize the expression", ex);
114113
throw new MetaException(ex.getMessage());

ql/src/test/org/apache/hadoop/hive/ql/exec/TestSerializationUtilities.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.io.IOException;
2323
import java.io.InputStream;
2424
import java.util.ArrayList;
25+
import java.util.Arrays;
2526
import java.util.LinkedHashMap;
2627
import java.util.List;
2728
import java.util.Map;
@@ -32,10 +33,16 @@
3233
import org.apache.hadoop.hive.conf.HiveConf;
3334
import org.apache.hadoop.hive.ql.io.orc.OrcInputFormat;
3435
import org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat;
36+
import org.apache.hadoop.hive.ql.plan.ExprNodeColumnDesc;
37+
import org.apache.hadoop.hive.ql.plan.ExprNodeDesc;
38+
import org.apache.hadoop.hive.ql.plan.ExprNodeDescUtils;
39+
import org.apache.hadoop.hive.ql.plan.ExprNodeGenericFuncDesc;
3540
import org.apache.hadoop.hive.ql.plan.MapWork;
3641
import org.apache.hadoop.hive.ql.plan.PartitionDesc;
3742
import org.apache.hadoop.hive.ql.plan.TableDesc;
3843
import org.apache.hadoop.hive.ql.plan.VectorPartitionDesc;
44+
import org.apache.hadoop.hive.ql.udf.generic.GenericUDFOPNull;
45+
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory;
3946
import org.junit.Assert;
4047
import org.junit.Test;
4148

@@ -127,6 +134,25 @@ public void testSkippingAppliesToAllPartitions() throws Exception {
127134
assertPartitionDescPropertyPresence(mapWork, "/warehouse/test_table/p=1", "serialization.ddl", false);
128135
}
129136

137+
@Test
138+
public void testUnsupportedDeserialization() throws Exception {
139+
ArrayList<Long> invalidExpr = new ArrayList<>();
140+
invalidExpr.add(1L);
141+
byte[] buf = SerializationUtilities.serializeObjectWithTypeInformation(invalidExpr);
142+
try {
143+
SerializationUtilities.deserializeObjectWithTypeInformation(buf, true);
144+
Assert.fail("Should throw exception as the input is not a valid filter");
145+
} catch (UnsupportedOperationException e) {
146+
// ignore
147+
}
148+
149+
ExprNodeDesc validExpr = ExprNodeGenericFuncDesc.newInstance(new GenericUDFOPNull(),
150+
Arrays.asList(new ExprNodeColumnDesc(new ColumnInfo("_c0", TypeInfoFactory.stringTypeInfo, "a", false))));
151+
buf = SerializationUtilities.serializeObjectWithTypeInformation(validExpr);
152+
ExprNodeDesc desc = SerializationUtilities.deserializeObjectWithTypeInformation(buf, true);
153+
Assert.assertTrue(ExprNodeDescUtils.isSame(validExpr, desc));
154+
}
155+
130156
private MapWork doSerDeser(Configuration configuration) throws Exception, IOException {
131157
MapWork mapWork = mockMapWorkWithSomePartitionDescProperties();
132158
ByteArrayOutputStream baos = new ByteArrayOutputStream();

standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/ObjectStore.java

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ private void initialize() {
422422
isInitialized = pm != null;
423423
if (isInitialized) {
424424
dbType = determineDatabaseProduct();
425-
expressionProxy = createExpressionProxy(conf);
425+
expressionProxy = PartFilterExprUtil.createExpressionProxy(conf);
426426
if (MetastoreConf.getBoolVar(getConf(), ConfVars.TRY_DIRECT_SQL)) {
427427
String schema = PersistenceManagerProvider.getProperty("javax.jdo.mapping.Schema");
428428
schema = org.apache.commons.lang3.StringUtils.defaultIfBlank(schema, null);
@@ -447,25 +447,6 @@ private static String getProductName(PersistenceManager pm) {
447447
}
448448
}
449449

450-
/**
451-
* Creates the proxy used to evaluate expressions. This is here to prevent circular
452-
* dependency - ql -&gt; metastore client &lt;-&gt metastore server -&gt ql. If server and
453-
* client are split, this can be removed.
454-
* @param conf Configuration.
455-
* @return The partition expression proxy.
456-
*/
457-
private static PartitionExpressionProxy createExpressionProxy(Configuration conf) {
458-
String className = MetastoreConf.getVar(conf, ConfVars.EXPRESSION_PROXY_CLASS);
459-
try {
460-
Class<? extends PartitionExpressionProxy> clazz =
461-
JavaUtils.getClass(className, PartitionExpressionProxy.class);
462-
return JavaUtils.newInstance(clazz, new Class<?>[0], new Object[0]);
463-
} catch (MetaException e) {
464-
LOG.error("Error loading PartitionExpressionProxy", e);
465-
throw new RuntimeException("Error loading PartitionExpressionProxy: " + e.getMessage());
466-
}
467-
}
468-
469450
/**
470451
* Configure SSL encryption to the database store.
471452
*

0 commit comments

Comments
 (0)