Skip to content

Commit 6cdf3f7

Browse files
committed
XContent: Check for bad parsers (#34561)
Adds checks for misbehaving parsers. The checks aren't perfect at all but they are simple and fast enough that we can do them all the time so they'll catch most badly behaving parsers. Closes #34351
1 parent 061ef69 commit 6cdf3f7

File tree

3 files changed

+70
-5
lines changed

3 files changed

+70
-5
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutRoleMappingResponse.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,10 @@ public int hashCode() {
6464
private static final ConstructingObjectParser<PutRoleMappingResponse, Void> PARSER = new ConstructingObjectParser<>(
6565
"put_role_mapping_response", true, args -> new PutRoleMappingResponse((boolean) args[0]));
6666
static {
67-
PARSER.declareBoolean(constructorArg(), new ParseField("created"));
68-
// To parse the "created" field we declare "role_mapping" field object.
69-
// Once the nested field "created" is found parser constructs the target object and
70-
// ignores the role_mapping object.
71-
PARSER.declareObject((a,b) -> {}, (parser, context) -> null, new ParseField("role_mapping"));
67+
ConstructingObjectParser<Boolean, Void> roleMappingParser = new ConstructingObjectParser<>(
68+
"put_role_mapping_response.role_mapping", true, args -> (Boolean) args[0]);
69+
roleMappingParser.declareBoolean(constructorArg(), new ParseField("created"));
70+
PARSER.declareObject(constructorArg(), roleMappingParser::parse, new ParseField("role_mapping"));
7271
}
7372

7473
public static PutRoleMappingResponse fromXContent(XContentParser parser) throws IOException {

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,9 +324,31 @@ private void parseSub(XContentParser parser, FieldParser fieldParser, String cur
324324
switch (token) {
325325
case START_OBJECT:
326326
parseValue(parser, fieldParser, currentFieldName, value, context);
327+
/*
328+
* Well behaving parsers should consume the entire object but
329+
* asserting that they do that is not something we can do
330+
* efficiently here. Instead we can check that they end on an
331+
* END_OBJECT. They could end on the *wrong* end object and
332+
* this test won't catch them, but that is the price that we pay
333+
* for having a cheap test.
334+
*/
335+
if (parser.currentToken() != XContentParser.Token.END_OBJECT) {
336+
throw new IllegalStateException("parser for [" + currentFieldName + "] did not end on END_OBJECT");
337+
}
327338
break;
328339
case START_ARRAY:
329340
parseArray(parser, fieldParser, currentFieldName, value, context);
341+
/*
342+
* Well behaving parsers should consume the entire array but
343+
* asserting that they do that is not something we can do
344+
* efficiently here. Instead we can check that they end on an
345+
* END_ARRAY. They could end on the *wrong* end array and
346+
* this test won't catch them, but that is the price that we pay
347+
* for having a cheap test.
348+
*/
349+
if (parser.currentToken() != XContentParser.Token.END_ARRAY) {
350+
throw new IllegalStateException("parser for [" + currentFieldName + "] did not end on END_ARRAY");
351+
}
330352
break;
331353
case END_OBJECT:
332354
case END_ARRAY:

libs/x-content/src/test/java/org/elasticsearch/common/xcontent/ObjectParserTests.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.Arrays;
3535
import java.util.Collections;
3636
import java.util.List;
37+
import java.util.concurrent.atomic.AtomicReference;
3738

3839
import static org.hamcrest.Matchers.containsString;
3940
import static org.hamcrest.Matchers.hasSize;
@@ -649,6 +650,49 @@ public void setArray(List<Object> testArray) {
649650
assertThat(ex.getMessage(), containsString("[foo] failed to parse field [int_array]"));
650651
}
651652

653+
public void testNoopDeclareObject() throws IOException {
654+
ObjectParser<AtomicReference<String>, Void> parser = new ObjectParser<>("noopy", AtomicReference::new);
655+
parser.declareString(AtomicReference::set, new ParseField("body"));
656+
parser.declareObject((a,b) -> {}, (p, c) -> null, new ParseField("noop"));
657+
658+
assertEquals("i", parser.parse(createParser(JsonXContent.jsonXContent, "{\"body\": \"i\"}"), null).get());
659+
Exception garbageException = expectThrows(IllegalStateException.class, () -> parser.parse(
660+
createParser(JsonXContent.jsonXContent, "{\"noop\": {\"garbage\": \"shouldn't\"}}"),
661+
null));
662+
assertEquals("parser for [noop] did not end on END_OBJECT", garbageException.getMessage());
663+
Exception sneakyException = expectThrows(IllegalStateException.class, () -> parser.parse(
664+
createParser(JsonXContent.jsonXContent, "{\"noop\": {\"body\": \"shouldn't\"}}"),
665+
null));
666+
assertEquals("parser for [noop] did not end on END_OBJECT", sneakyException.getMessage());
667+
}
668+
669+
public void testNoopDeclareField() throws IOException {
670+
ObjectParser<AtomicReference<String>, Void> parser = new ObjectParser<>("noopy", AtomicReference::new);
671+
parser.declareString(AtomicReference::set, new ParseField("body"));
672+
parser.declareField((a,b) -> {}, (p, c) -> null, new ParseField("noop"), ValueType.STRING_ARRAY);
673+
674+
assertEquals("i", parser.parse(createParser(JsonXContent.jsonXContent, "{\"body\": \"i\"}"), null).get());
675+
Exception e = expectThrows(IllegalStateException.class, () -> parser.parse(
676+
createParser(JsonXContent.jsonXContent, "{\"noop\": [\"ignored\"]}"),
677+
null));
678+
assertEquals("parser for [noop] did not end on END_ARRAY", e.getMessage());
679+
}
680+
681+
public void testNoopDeclareObjectArray() throws IOException {
682+
ObjectParser<AtomicReference<String>, Void> parser = new ObjectParser<>("noopy", AtomicReference::new);
683+
parser.declareString(AtomicReference::set, new ParseField("body"));
684+
parser.declareObjectArray((a,b) -> {}, (p, c) -> null, new ParseField("noop"));
685+
686+
XContentParseException garbageError = expectThrows(XContentParseException.class, () -> parser.parse(
687+
createParser(JsonXContent.jsonXContent, "{\"noop\": [{\"garbage\": \"shouldn't\"}}]"),
688+
null));
689+
assertEquals("expected value but got [FIELD_NAME]", garbageError.getCause().getMessage());
690+
XContentParseException sneakyError = expectThrows(XContentParseException.class, () -> parser.parse(
691+
createParser(JsonXContent.jsonXContent, "{\"noop\": [{\"body\": \"shouldn't\"}}]"),
692+
null));
693+
assertEquals("expected value but got [FIELD_NAME]", sneakyError.getCause().getMessage());
694+
}
695+
652696
static class NamedObjectHolder {
653697
public static final ObjectParser<NamedObjectHolder, Void> PARSER = new ObjectParser<>("named_object_holder",
654698
NamedObjectHolder::new);

0 commit comments

Comments
 (0)