Skip to content

Commit 9da95ef

Browse files
committed
ingest: Don't allow circular referencing of named patterns in the grok processor.
Otherwise the grok code throws a stackoverflow error. Closes #29257
1 parent 2c20f7a commit 9da95ef

File tree

2 files changed

+114
-4
lines changed

2 files changed

+114
-4
lines changed

libs/grok/src/main/java/org/elasticsearch/grok/Grok.java

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@
3434
import java.io.InputStreamReader;
3535
import java.io.UncheckedIOException;
3636
import java.nio.charset.StandardCharsets;
37+
import java.util.ArrayList;
3738
import java.util.HashMap;
3839
import java.util.Iterator;
40+
import java.util.List;
3941
import java.util.Locale;
4042
import java.util.Map;
4143
import java.util.Collections;
@@ -74,8 +76,6 @@ public final class Grok {
7476
private final Map<String, String> patternBank;
7577
private final boolean namedCaptures;
7678
private final Regex compiledExpression;
77-
private final String expression;
78-
7979

8080
public Grok(Map<String, String> patternBank, String grokPattern) {
8181
this(patternBank, grokPattern, true);
@@ -86,11 +86,59 @@ public Grok(Map<String, String> patternBank, String grokPattern) {
8686
this.patternBank = patternBank;
8787
this.namedCaptures = namedCaptures;
8888

89-
this.expression = toRegex(grokPattern);
89+
for (Map.Entry<String, String> entry : patternBank.entrySet()) {
90+
String name = entry.getKey();
91+
String pattern = entry.getValue();
92+
forbidCircularReferences(name, new ArrayList<>(), pattern);
93+
}
94+
95+
String expression = toRegex(grokPattern);
9096
byte[] expressionBytes = expression.getBytes(StandardCharsets.UTF_8);
9197
this.compiledExpression = new Regex(expressionBytes, 0, expressionBytes.length, Option.DEFAULT, UTF8Encoding.INSTANCE);
9298
}
9399

100+
/**
101+
* Checks whether patterns reference each other in a circular manner and if so fail with an exception
102+
*
103+
* In a pattern, anything between <code>%{</code> and <code>}</code> or <code>:</code> is considered
104+
* a reference to another named pattern. This method will navigate to all these named patterns and
105+
* check for a circular reference.
106+
*/
107+
private void forbidCircularReferences(String patternName, List<String> path, String pattern) {
108+
if (pattern.contains("%{" + patternName + "}") || pattern.contains("%{" + patternName + ":")) {
109+
String message;
110+
if (path.isEmpty()) {
111+
message = "circular reference in pattern [" + patternName + "][" + pattern + "]";
112+
} else {
113+
message = "circular reference in pattern [" + path.remove(path.size() - 1) + "][" + pattern +
114+
"] back to pattern [" + patternName + "]";
115+
// add rest of the path:
116+
if (path.isEmpty() == false) {
117+
message += " via patterns [" + String.join("=>", path) + "]";
118+
}
119+
}
120+
throw new IllegalArgumentException(message);
121+
}
122+
123+
for (int i = pattern.indexOf("%{"); i != -1; i = pattern.indexOf("%{", i + 1)) {
124+
int begin = i + 2;
125+
int brackedIndex = pattern.indexOf('}', begin);
126+
int columnIndex = pattern.indexOf(':', begin);
127+
int end;
128+
if (brackedIndex != -1 && columnIndex == -1) {
129+
end = brackedIndex;
130+
} else if (columnIndex != -1 && brackedIndex == -1) {
131+
end = columnIndex;
132+
} else if (brackedIndex != -1 && columnIndex != -1) {
133+
end = Math.min(brackedIndex, columnIndex);
134+
} else {
135+
throw new IllegalArgumentException("pattern [" + pattern + "] has circular references to other pattern definitions");
136+
}
137+
String otherPatternName = pattern.substring(begin, end);
138+
path.add(otherPatternName);
139+
forbidCircularReferences(patternName, path, patternBank.get(otherPatternName));
140+
}
141+
}
94142

95143
public String groupMatch(String name, Region region, String pattern) {
96144
try {
@@ -125,10 +173,12 @@ public String toRegex(String grokPattern) {
125173
String patternName = groupMatch(PATTERN_GROUP, region, grokPattern);
126174

127175
String pattern = patternBank.get(patternName);
128-
129176
if (pattern == null) {
130177
throw new IllegalArgumentException("Unable to find pattern [" + patternName + "] in Grok's pattern dictionary");
131178
}
179+
if (pattern.contains("%{" + patternName + "}") || pattern.contains("%{" + patternName + ":")) {
180+
throw new IllegalArgumentException("circular reference in pattern back [" + patternName + "]");
181+
}
132182

133183
String grokPart;
134184
if (namedCaptures && subName != null) {

libs/grok/src/test/java/org/elasticsearch/grok/GrokTests.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.HashMap;
2929
import java.util.List;
3030
import java.util.Map;
31+
import java.util.TreeMap;
3132

3233
import static org.hamcrest.Matchers.equalTo;
3334
import static org.hamcrest.Matchers.is;
@@ -205,6 +206,65 @@ public void testNoNamedCaptures() {
205206
assertEquals(expected, actual);
206207
}
207208

209+
public void testCircularReference() {
210+
Exception e = expectThrows(IllegalArgumentException.class, () -> {
211+
Map<String, String> bank = new HashMap<>();
212+
bank.put("NAME", "!!!%{NAME}!!!");
213+
String pattern = "%{NAME}";
214+
new Grok(bank, pattern, false);
215+
});
216+
assertEquals("circular reference in pattern [NAME][!!!%{NAME}!!!]", e.getMessage());
217+
218+
e = expectThrows(IllegalArgumentException.class, () -> {
219+
Map<String, String> bank = new HashMap<>();
220+
bank.put("NAME", "!!!%{NAME:name}!!!");
221+
String pattern = "%{NAME}";
222+
new Grok(bank, pattern, false);
223+
});
224+
assertEquals("circular reference in pattern [NAME][!!!%{NAME:name}!!!]", e.getMessage());
225+
226+
e = expectThrows(IllegalArgumentException.class, () -> {
227+
Map<String, String> bank = new HashMap<>();
228+
bank.put("NAME", "!!!%{NAME:name:int}!!!");
229+
String pattern = "%{NAME}";
230+
new Grok(bank, pattern, false);
231+
});
232+
assertEquals("circular reference in pattern [NAME][!!!%{NAME:name:int}!!!]", e.getMessage());
233+
234+
e = expectThrows(IllegalArgumentException.class, () -> {
235+
Map<String, String> bank = new TreeMap<>();
236+
bank.put("NAME1", "!!!%{NAME2}!!!");
237+
bank.put("NAME2", "!!!%{NAME1}!!!");
238+
String pattern = "%{NAME1}";
239+
new Grok(bank, pattern, false);
240+
});
241+
assertEquals("circular reference in pattern [NAME2][!!!%{NAME1}!!!] back to pattern [NAME1]", e.getMessage());
242+
243+
e = expectThrows(IllegalArgumentException.class, () -> {
244+
Map<String, String> bank = new TreeMap<>();
245+
bank.put("NAME1", "!!!%{NAME2}!!!");
246+
bank.put("NAME2", "!!!%{NAME3}!!!");
247+
bank.put("NAME3", "!!!%{NAME1}!!!");
248+
String pattern = "%{NAME1}";
249+
new Grok(bank, pattern, false);
250+
});
251+
assertEquals("circular reference in pattern [NAME3][!!!%{NAME1}!!!] back to pattern [NAME1] via patterns [NAME2]",
252+
e.getMessage());
253+
254+
e = expectThrows(IllegalArgumentException.class, () -> {
255+
Map<String, String> bank = new TreeMap<>();
256+
bank.put("NAME1", "!!!%{NAME2}!!!");
257+
bank.put("NAME2", "!!!%{NAME3}!!!");
258+
bank.put("NAME3", "!!!%{NAME4}!!!");
259+
bank.put("NAME4", "!!!%{NAME5}!!!");
260+
bank.put("NAME5", "!!!%{NAME1}!!!");
261+
String pattern = "%{NAME1}";
262+
new Grok(bank, pattern, false);
263+
});
264+
assertEquals("circular reference in pattern [NAME5][!!!%{NAME1}!!!] back to pattern [NAME1] " +
265+
"via patterns [NAME2=>NAME3=>NAME4]", e.getMessage());
266+
}
267+
208268
public void testBooleanCaptures() {
209269
Map<String, String> bank = new HashMap<>();
210270

0 commit comments

Comments
 (0)