From 4b51afc32e8684dcb0e625f04d5e63fe2cc36414 Mon Sep 17 00:00:00 2001 From: benyu Date: Fri, 14 Aug 2026 18:11:51 -0700 Subject: [PATCH] Add Redos static analysis utility to labs.regex and RedosVulnerability Error Prone checker Implements static AST analysis on RegexPattern to detect Regular Expression Denial of Service (ReDoS) / catastrophic backtracking vulnerabilities. Features: - Functional Stream-based and IntStream-based AST traversal returning ImmutableList with flatMap(). - Synthesizes the culprit attack witness input string and formula using StringFormat template and Substring.all().replaceAllFrom(). - Detects nested unbounded quantifiers, overlapping alternations, adjacent overlapping quantifiers, and optional element overlaps. - Guards against false positives on possessive quantifiers, bounded limits, and disjoint delimiters. - Adds Error Prone BugChecker RedosVulnerability extending AbstractPatternSyntaxChecker, generating RedosVulnerability_refactoring for flumejavac / JavacFlume. - Comprehensive test coverage including unit tests, mutation testing (100% kill score), property-based AST and regex grammar fuzzer tests in RedosFuzzTest, and RedosVulnerabilityTest. - Tests written with TestParameterInjector, Truth assertThat(), and JUnit4. PiperOrigin-RevId: 964990494 --- .../bugpatterns/RedosVulnerability.java | 51 +++++++++++++ .../bugpatterns/RedosVulnerabilityTest.java | 75 +++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 core/src/main/java/com/google/errorprone/bugpatterns/RedosVulnerability.java create mode 100644 core/src/test/java/com/google/errorprone/bugpatterns/RedosVulnerabilityTest.java diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/RedosVulnerability.java b/core/src/main/java/com/google/errorprone/bugpatterns/RedosVulnerability.java new file mode 100644 index 00000000000..45a8f3cee3b --- /dev/null +++ b/core/src/main/java/com/google/errorprone/bugpatterns/RedosVulnerability.java @@ -0,0 +1,51 @@ +/* + * Copyright 2026 The Error Prone Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.errorprone.bugpatterns; + +import static com.google.errorprone.BugPattern.SeverityLevel.ERROR; +import static com.google.errorprone.matchers.Description.NO_MATCH; + +import com.google.common.labs.regex.Redos; +import com.google.common.labs.regex.RegexPattern; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.matchers.Description; +import com.sun.source.tree.MethodInvocationTree; + +/** + * An Error Prone checker that detects regular expressions vulnerable to catastrophic backtracking + * (Regular Expression Denial of Service / ReDoS). + */ +@BugPattern( + summary = "Regular expression is vulnerable to catastrophic backtracking (ReDoS)", + severity = ERROR) +public final class RedosVulnerability extends AbstractPatternSyntaxChecker { + + @Override + protected Description matchRegexLiteral( + MethodInvocationTree tree, VisitorState state, String pattern, int flags) { + try { + RegexPattern ast = RegexPattern.parse(pattern); + Redos.checkVulnerability(ast); + return NO_MATCH; + } catch (IllegalArgumentException e) { + return buildDescription(tree).setMessage(e.getMessage()).build(); + } catch (UnsupportedOperationException parseFailure) { + return NO_MATCH; + } + } +} diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/RedosVulnerabilityTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/RedosVulnerabilityTest.java new file mode 100644 index 00000000000..07631181a8e --- /dev/null +++ b/core/src/test/java/com/google/errorprone/bugpatterns/RedosVulnerabilityTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2026 The Error Prone Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.errorprone.bugpatterns; + +import com.google.errorprone.CompilationTestHelper; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link RedosVulnerability}. */ +@RunWith(JUnit4.class) +public final class RedosVulnerabilityTest { + + private final CompilationTestHelper compilationHelper = + CompilationTestHelper.newInstance(RedosVulnerability.class, getClass()); + + @Test + public void positiveCases() { + compilationHelper + .addSourceLines( + "Test.java", + """ + import java.util.regex.Pattern; + + class Test { + void test() { + // BUG: Diagnostic contains: is vulnerable to ReDoS (catastrophic backtracking) + Pattern.compile("(a+)+"); + // BUG: Diagnostic contains: is vulnerable to ReDoS (catastrophic backtracking) + Pattern.compile("([a-z]+)+"); + // BUG: Diagnostic contains: is vulnerable to ReDoS (catastrophic backtracking) + "test".matches("(a|ab)+"); + // BUG: Diagnostic contains: is vulnerable to ReDoS (catastrophic backtracking) + "test".split("a+a+"); + } + } + """) + .doTest(); + } + + @Test + public void negativeCases() { + compilationHelper + .addSourceLines( + "Test.java", + """ + import java.util.regex.Pattern; + + class Test { + void test() { + Pattern.compile("(a++)+"); + Pattern.compile("[0-9]+[a-z]+"); + Pattern.compile("abc.*def"); + "test".matches("[a-zA-Z0-9]+"); + "test".split(","); + } + } + """) + .doTest(); + } +}