Skip to content

Commit bbf986d

Browse files
authored
ESQL: Limit size of query (#117898)
Queries bigger than a mb tend to take a lot of memory. In the worse case it's an astounding amount of memory.
1 parent ec27e20 commit bbf986d

File tree

4 files changed

+50
-1
lines changed

4 files changed

+50
-1
lines changed

docs/changelog/117898.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 117898
2+
summary: Limit size of query
3+
area: ES|QL
4+
type: bug
5+
issues: []

test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/HeapAttackIT.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,13 @@ private void assertCircuitBreaks(ThrowingRunnable r) throws IOException {
194194
);
195195
}
196196

197+
private void assertParseFailure(ThrowingRunnable r) throws IOException {
198+
ResponseException e = expectThrows(ResponseException.class, r);
199+
Map<?, ?> map = responseAsMap(e.getResponse());
200+
logger.info("expected parse failure {}", map);
201+
assertMap(map, matchesMap().entry("status", 400).entry("error", matchesMap().extraOk().entry("type", "parsing_exception")));
202+
}
203+
197204
private Response sortByManyLongs(int count) throws IOException {
198205
logger.info("sorting by {} longs", count);
199206
return query(makeSortByManyLongs(count).toString(), null);
@@ -318,6 +325,13 @@ public void testManyConcatFromRow() throws IOException {
318325
assertManyStrings(resp, strings);
319326
}
320327

328+
/**
329+
* Fails to parse a huge huge query.
330+
*/
331+
public void testHugeHugeManyConcatFromRow() throws IOException {
332+
assertParseFailure(() -> manyConcat("ROW a=9999, b=9999, c=9999, d=9999, e=9999", 50000));
333+
}
334+
321335
/**
322336
* Tests that generate many moderately long strings.
323337
*/
@@ -378,6 +392,13 @@ public void testManyRepeatFromRow() throws IOException {
378392
assertManyStrings(resp, strings);
379393
}
380394

395+
/**
396+
* Fails to parse a huge huge query.
397+
*/
398+
public void testHugeHugeManyRepeatFromRow() throws IOException {
399+
assertParseFailure(() -> manyRepeat("ROW a = 99", 100000));
400+
}
401+
381402
/**
382403
* Tests that generate many moderately long strings.
383404
*/

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlParser.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ public class EsqlParser {
3333

3434
private static final Logger log = LogManager.getLogger(EsqlParser.class);
3535

36+
/**
37+
* Maximum number of characters in an ESQL query. Antlr may parse the entire
38+
* query into tokens to make the choices, buffering the world. There's a lot we
39+
* can do in the grammar to prevent that, but let's be paranoid and assume we'll
40+
* fail at preventing antlr from slurping in the world. Instead, let's make sure
41+
* that the world just isn't that big.
42+
*/
43+
public static final int MAX_LENGTH = 1_000_000;
44+
3645
private EsqlConfig config = new EsqlConfig();
3746

3847
public EsqlConfig config() {
@@ -60,8 +69,14 @@ private <T> T invokeParser(
6069
Function<EsqlBaseParser, ParserRuleContext> parseFunction,
6170
BiFunction<AstBuilder, ParserRuleContext, T> result
6271
) {
72+
if (query.length() > MAX_LENGTH) {
73+
throw new org.elasticsearch.xpack.esql.core.ParsingException(
74+
"ESQL statement is too large [{} characters > {}]",
75+
query.length(),
76+
MAX_LENGTH
77+
);
78+
}
6379
try {
64-
// new CaseChangingCharStream()
6580
EsqlBaseLexer lexer = new EsqlBaseLexer(CharStreams.fromString(query));
6681

6782
lexer.removeErrorListeners();

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/ParsingTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ public void testInlineCast() throws IOException {
103103
logger.info("Wrote to file: {}", file);
104104
}
105105

106+
public void testTooBigQuery() {
107+
StringBuilder query = new StringBuilder("FROM foo | EVAL a = a");
108+
while (query.length() < EsqlParser.MAX_LENGTH) {
109+
query.append(", a = CONCAT(a, a)");
110+
}
111+
assertEquals("-1:0: ESQL statement is too large [1000011 characters > 1000000]", error(query.toString()));
112+
}
113+
106114
private String functionName(EsqlFunctionRegistry registry, Expression functionCall) {
107115
for (FunctionDefinition def : registry.listFunctions()) {
108116
if (functionCall.getClass().equals(def.clazz())) {

0 commit comments

Comments
 (0)