Skip to content

Commit 402a284

Browse files
authored
Use ObjectInputFilter for deserialization (#5469)
* use ObjectInputFilter for deserialization for cursors and script pushdown Signed-off-by: Ritvi Bhatt <ribhatt@amazon.com> * update allowlist Signed-off-by: Ritvi Bhatt <ribhatt@amazon.com> --------- Signed-off-by: Ritvi Bhatt <ribhatt@amazon.com>
1 parent e90692c commit 402a284

7 files changed

Lines changed: 122 additions & 0 deletions

File tree

core/src/main/java/org/opensearch/sql/executor/pagination/PlanSerializer.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.opensearch.sql.planner.SerializablePlan;
2323
import org.opensearch.sql.planner.physical.PhysicalPlan;
2424
import org.opensearch.sql.storage.StorageEngine;
25+
import org.opensearch.sql.utils.DeserializationFilterUtil;
2526

2627
/**
2728
* This class is entry point to paged requests. It is responsible to cursor serialization and
@@ -88,6 +89,14 @@ protected Serializable deserialize(String code) {
8889
new GZIPInputStream(new ByteArrayInputStream(HashCode.fromString(code).asBytes()));
8990
ObjectInputStream objectInput =
9091
new CursorDeserializationStream(new ByteArrayInputStream(gzip.readAllBytes()));
92+
objectInput.setObjectInputFilter(
93+
DeserializationFilterUtil.createFilter(
94+
"org.opensearch.sql.planner.physical.*;"
95+
+ "org.opensearch.sql.opensearch.storage.scan.*;"
96+
+ "org.opensearch.sql.opensearch.data.type.*;"
97+
+ "org.opensearch.sql.executor.pagination.*;"
98+
+ "org.opensearch.sql.executor.QueryType;"
99+
+ "org.opensearch.sql.utils.*;"));
91100
return (Serializable) objectInput.readObject();
92101
} catch (Exception e) {
93102
throw new IllegalStateException("Failed to deserialize object", e);
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.utils;
7+
8+
import java.io.ObjectInputFilter;
9+
import org.apache.logging.log4j.LogManager;
10+
import org.apache.logging.log4j.Logger;
11+
12+
/** Utility class for creating deserialization filters with logging. */
13+
public class DeserializationFilterUtil {
14+
private static final Logger LOG = LogManager.getLogger(DeserializationFilterUtil.class);
15+
16+
/** Base allowlist shared across all serializers. */
17+
private static final String BASE_ALLOWLIST =
18+
"org.opensearch.sql.expression.**;"
19+
+ "org.opensearch.sql.data.**;"
20+
+ "org.opensearch.sql.executor.QueryType;"
21+
+ "org.opensearch.sql.opensearch.data.type.*;"
22+
+ "java.lang.Object;"
23+
+ "java.lang.String;"
24+
+ "java.lang.Number;"
25+
+ "java.lang.Integer;"
26+
+ "java.lang.Long;"
27+
+ "java.lang.Double;"
28+
+ "java.lang.Float;"
29+
+ "java.lang.Short;"
30+
+ "java.lang.Byte;"
31+
+ "java.lang.Boolean;"
32+
+ "java.lang.Character;"
33+
+ "java.lang.Enum;"
34+
+ "java.util.ArrayList;"
35+
+ "java.util.Arrays$ArrayList;"
36+
+ "java.util.LinkedHashMap;"
37+
+ "java.util.HashMap;"
38+
+ "java.util.Collections$*;"
39+
+ "java.util.ImmutableCollections$*;"
40+
+ "java.util.CollSer;"
41+
+ "java.util.Map$Entry;"
42+
+ "java.io.Serializable;"
43+
+ "java.lang.invoke.SerializedLambda;"
44+
+ "java.math.BigDecimal;"
45+
+ "java.math.BigInteger;"
46+
+ "java.time.**;"
47+
+ "com.google.common.collect.**;";
48+
49+
/**
50+
* Creates a logging filter that wraps the provided filter and logs rejected classes.
51+
*
52+
* @param filter The underlying filter to wrap.
53+
* @return A filter that logs rejections.
54+
*/
55+
public static ObjectInputFilter createLoggingFilter(ObjectInputFilter filter) {
56+
return info -> {
57+
ObjectInputFilter.Status status = filter.checkInput(info);
58+
if (status == ObjectInputFilter.Status.REJECTED && info.serialClass() != null) {
59+
LOG.warn("Deserialization filter rejected class: {}", info.serialClass().getName());
60+
}
61+
return status;
62+
};
63+
}
64+
65+
/**
66+
* Creates a filter with the base allowlist plus additional patterns.
67+
*
68+
* @param additionalPatterns Additional patterns to append to the base allowlist.
69+
* @return A logging filter with the combined allowlist.
70+
*/
71+
public static ObjectInputFilter createFilter(String additionalPatterns) {
72+
String fullPattern = BASE_ALLOWLIST + additionalPatterns + "!*";
73+
return createLoggingFilter(ObjectInputFilter.Config.createFilter(fullPattern));
74+
}
75+
}

core/src/test/java/org/opensearch/sql/executor/pagination/PlanSerializerTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,14 @@ void resolveObject() {
168168
assertSame(object, cds.resolveObject(object));
169169
}
170170

171+
@Test
172+
@SneakyThrows
173+
void deserialize_rejects_disallowed_class() {
174+
String serialized = serialize(new java.net.URL("http://example.com"));
175+
var exception = assertThrows(IllegalStateException.class, () -> deserialize(serialized));
176+
assertTrue(exception.getMessage().contains("Failed to deserialize"));
177+
}
178+
171179
// Helpers and auxiliary classes section below
172180

173181
@SneakyThrows

opensearch/src/main/java/org/opensearch/sql/opensearch/storage/serde/DefaultExpressionSerializer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.io.ObjectOutputStream;
1313
import java.util.Base64;
1414
import org.opensearch.sql.expression.Expression;
15+
import org.opensearch.sql.utils.DeserializationFilterUtil;
1516

1617
/** Default serializer that (de-)serialize expressions by JDK serialization. */
1718
public class DefaultExpressionSerializer implements ExpressionSerializer {
@@ -34,6 +35,7 @@ public Expression deserialize(String code) {
3435
try {
3536
ByteArrayInputStream input = new ByteArrayInputStream(Base64.getDecoder().decode(code));
3637
ObjectInputStream objectInput = new ObjectInputStream(input);
38+
objectInput.setObjectInputFilter(DeserializationFilterUtil.createFilter(""));
3739
return (Expression) objectInput.readObject();
3840
} catch (Exception e) {
3941
throw new IllegalStateException("Failed to deserialize expression code: " + code, e);

opensearch/src/main/java/org/opensearch/sql/opensearch/storage/serde/RelJsonSerializer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.opensearch.sql.calcite.CalcitePlanContext;
3030
import org.opensearch.sql.expression.function.PPLBuiltinOperators;
3131
import org.opensearch.sql.opensearch.executor.OpenSearchExecutionEngine.OperatorTable;
32+
import org.opensearch.sql.utils.DeserializationFilterUtil;
3233

3334
/**
3435
* A serializer that (de-)serializes Calcite RexNode, RelDataType and OpenSearch field mapping.
@@ -120,6 +121,7 @@ public RexNode deserialize(String struct) {
120121
try {
121122
ByteArrayInputStream input = new ByteArrayInputStream(Base64.getDecoder().decode(struct));
122123
ObjectInputStream objectInput = new ObjectInputStream(input);
124+
objectInput.setObjectInputFilter(DeserializationFilterUtil.createFilter(""));
123125
exprStr = (String) objectInput.readObject();
124126

125127
// Deserialize RelDataType and RexNode by JSON

opensearch/src/test/java/org/opensearch/sql/opensearch/storage/serde/DefaultExpressionSerializerTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import static org.junit.jupiter.api.Assertions.assertEquals;
99
import static org.junit.jupiter.api.Assertions.assertThrows;
10+
import static org.junit.jupiter.api.Assertions.assertTrue;
1011
import static org.opensearch.sql.data.type.ExprCoreType.STRING;
1112
import static org.opensearch.sql.expression.DSL.literal;
1213
import static org.opensearch.sql.expression.DSL.ref;
@@ -82,4 +83,16 @@ public <T, C> T accept(ExpressionNodeVisitor<T, C> visitor, C context) {
8283
public void cannot_deserialize_illegal_expression_code() {
8384
assertThrows(IllegalStateException.class, () -> serializer.deserialize("hello world"));
8485
}
86+
87+
@Test
88+
public void deserialize_rejects_disallowed_class() throws Exception {
89+
java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream();
90+
java.io.ObjectOutputStream objectOutput = new java.io.ObjectOutputStream(output);
91+
objectOutput.writeObject(new java.net.URL("http://example.com"));
92+
objectOutput.flush();
93+
String encoded = java.util.Base64.getEncoder().encodeToString(output.toByteArray());
94+
var exception =
95+
assertThrows(IllegalStateException.class, () -> serializer.deserialize(encoded));
96+
assertTrue(exception.getMessage().contains("Failed to deserialize"));
97+
}
8598
}

opensearch/src/test/java/org/opensearch/sql/opensearch/storage/serde/RelJsonSerializerTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import static org.junit.jupiter.api.Assertions.assertEquals;
99
import static org.junit.jupiter.api.Assertions.assertThrows;
10+
import static org.junit.jupiter.api.Assertions.assertTrue;
1011
import static org.opensearch.sql.calcite.utils.OpenSearchTypeFactory.TYPE_FACTORY;
1112

1213
import com.google.common.collect.ImmutableRangeSet;
@@ -348,4 +349,16 @@ void testSerializeAndDeserializeSearch() {
348349
assertEquals(List.of(2, 0, 0, 2), helper.sources);
349350
assertEquals(List.of(20, "Number", "Number", 10), helper.digests);
350351
}
352+
353+
@Test
354+
void deserialize_rejects_disallowed_class() throws Exception {
355+
java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream();
356+
java.io.ObjectOutputStream objectOutput = new java.io.ObjectOutputStream(output);
357+
objectOutput.writeObject(new java.net.URL("http://example.com"));
358+
objectOutput.flush();
359+
String encoded = java.util.Base64.getEncoder().encodeToString(output.toByteArray());
360+
var exception =
361+
assertThrows(IllegalStateException.class, () -> serializer.deserialize(encoded));
362+
assertTrue(exception.getMessage().contains("Failed to deserialize"));
363+
}
351364
}

0 commit comments

Comments
 (0)