Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions src/main/java/graphql/validation/ValidationErrorCollector.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,46 @@
import java.util.ArrayList;
import java.util.List;

import static graphql.validation.ValidationErrorType.MaxValidationErrorsReached;

@Internal
public class ValidationErrorCollector {

private final List<ValidationError> errors = new ArrayList<>();
private final int maxErrors;

public ValidationErrorCollector() {
this(Validator.MAX_VALIDATION_ERRORS);
}

public ValidationErrorCollector(int maxErrors) {
this.maxErrors = maxErrors;
}

private boolean atMaxErrors() {
return errors.size() >= maxErrors - 1;
}

public void addError(ValidationError validationError) {
this.errors.add(validationError);
/**
* This will throw {@link MaxValidationErrorsReached} if too many validation errors are added
*
* @param validationError the error to add
*
* @throws MaxValidationErrorsReached if too many errors have been generated
*/
public void addError(ValidationError validationError) throws MaxValidationErrorsReached {
if (!atMaxErrors()) {
this.errors.add(validationError);
} else {
this.errors.add(ValidationError.newValidationError()
.validationErrorType(MaxValidationErrorsReached)
.description(
String.format("The maximum number of validation errors has been reached. (%d)", maxErrors)
)
.build());

throw new MaxValidationErrorsReached();
}
}

public List<ValidationError> getErrors() {
Expand All @@ -38,4 +71,17 @@ public String toString() {
"errors=" + errors +
'}';
}

/**
* Indicates that that maximum number of validation errors has been reached
*/
@Internal
static class MaxValidationErrorsReached extends RuntimeException {

@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}

}
1 change: 1 addition & 0 deletions src/main/java/graphql/validation/ValidationErrorType.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

public enum ValidationErrorType {

MaxValidationErrorsReached,
DefaultForNonNullArgument,
WrongType,
UnknownType,
Expand Down
28 changes: 25 additions & 3 deletions src/main/java/graphql/validation/Validator.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,36 @@
@Internal
public class Validator {

static int MAX_VALIDATION_ERRORS = 100;

/**
* `graphql-java` will stop validation after a maximum number of validation messages has been reached. Attackers
* can send pathologically invalid queries to induce a Denial of Service attack and fill memory with 10000s of errors
* and burn CPU in process.
*
* By default, this is set to 100 errors. You can set a new JVM wide value as the maximum allowed validation errors.
*
* @param maxValidationErrors the maximum validation errors allow JVM wide
*/
public static void setMaxValidationErrors(int maxValidationErrors) {
MAX_VALIDATION_ERRORS = maxValidationErrors;
}

public static int getMaxValidationErrors() {
return MAX_VALIDATION_ERRORS;
}

public List<ValidationError> validateDocument(GraphQLSchema schema, Document document) {
ValidationContext validationContext = new ValidationContext(schema, document);


ValidationErrorCollector validationErrorCollector = new ValidationErrorCollector();
ValidationErrorCollector validationErrorCollector = new ValidationErrorCollector(MAX_VALIDATION_ERRORS);
List<AbstractRule> rules = createRules(validationContext, validationErrorCollector);
LanguageTraversal languageTraversal = new LanguageTraversal();
languageTraversal.traverse(document, new RulesVisitor(validationContext, rules));
try {
languageTraversal.traverse(document, new RulesVisitor(validationContext, rules));
} catch (ValidationErrorCollector.MaxValidationErrorsReached ignored) {
// if we have generated enough errors, then we can shortcut out
}

return validationErrorCollector.getErrors();
}
Expand Down
25 changes: 25 additions & 0 deletions src/test/groovy/graphql/validation/MaxValidationErrorsTest.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package graphql.validation

class MaxValidationErrorsTest extends SpecValidationBase {

def "The maximum number of validation messages is respected"() {
def directives = "@lol" * 10000
def query = """
query lotsOfErrors {
f $directives
}
"""
when:
def validationErrors = validate(query)

then:
validationErrors.size() == 100

when: "we can set a new maximum"
Validator.setMaxValidationErrors(10)
validationErrors = validate(query)

then:
validationErrors.size() == 10
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import org.jetbrains.annotations.NotNull;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Measurement;
Expand Down Expand Up @@ -62,7 +61,6 @@ static TypeDefinitionRegistry serialise() {
});
}

@NotNull
private static ByteArrayOutputStream serialisedRegistryStream(TypeDefinitionRegistry registryOut) {
return asRTE(() -> {
ByteArrayOutputStream baOS = new ByteArrayOutputStream();
Expand Down