Skip to content

Commit

Permalink
SONARJAVA-3733 ReDoS: Don't call cubic and worse runtimes quadratic (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastian-hungerecker-sonarsource authored Mar 19, 2021
1 parent cf76976 commit 3fa749e
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 54 deletions.
93 changes: 56 additions & 37 deletions java-checks-test-sources/src/main/java/checks/regex/RedosCheck.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@

public class RedosCheck {

@Email(regexp = "(.*-)*@.*") // Noncompliant [[sc=4;ec=9]] {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
@Email(regexp = "(.*-)*@.*") // Noncompliant [[sc=4;ec=9]] {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
String email;

void realWorldExamples(String str) {
String cloudflareAttack = "(?:(?:\"|'|\\]|\\}|\\\\|\\d|(?:nan|infinity|true|false|null|undefined|symbol|math)|\\`|\\-|\\+)+[)]*;?((?:\\s|-|~|!|\\{\\}|\\|\\||\\+)*.*(?:.*=.*)))";
String stackOverflowAttack = "^[\\s\\u200c]+|[\\s\\u200c]+$";
str.replaceAll(cloudflareAttack, ""); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.replaceAll(stackOverflowAttack, ""); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches(cloudflareAttack); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.replaceAll(stackOverflowAttack, ""); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
}

void fullAndPartialMatches(String str) {
Pattern p1 = Pattern.compile("(.*,)*"); // Compliant because it's never used for a full match
Pattern p2 = Pattern.compile("(.*,)*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
Pattern p2 = Pattern.compile("(.*,)*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
p1.matcher(str).find();
p2.matcher(str).find();
p2.matcher(str).matches();
Expand All @@ -31,55 +31,70 @@ void alwaysExponential(String str) {
str.matches("(.*,)*\\1"); // Noncompliant {{Make sure the regex used here, which is vulnerable to exponential runtime due to backtracking, cannot lead to denial of service.}}
}

void quadraticInJava9(String str) {
str.matches("(.*,)*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*,)*.*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.split("(.*,)*X"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*,)*X"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*?,)+"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*?,){5,}"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("((.*,)*)*+"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("((.*,)*)?"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(?>(.*,)*)"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("((?>.*,)*)*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*,)* (.*,)*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.split("(.*,)*$"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*,)*$"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*,)*(..)*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*,)*(.{2})*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
void polynomialInJava9(String str) {
str.matches("(.*,)*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*,)*.*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.split("(.*,)*X"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*,)*X"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*?,)+"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*?,){5,}"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("((.*,)*)*+"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("((.*,)*)?"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(?>(.*,)*)"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("((?>.*,)*)*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*,)* (.*,)*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.split("(.*,)*$"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*,)*$"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*,)*(..)*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(.*,)*(.{2})*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
}

void alwaysQuadratic(String str) {
// Always quadratic when two non-possessive quantifiers overlap in a sequence
str.matches("x*\\w*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches(".*.*X"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("x*a*x*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
// Always polynomial when two non-possessive quantifiers overlap in a sequence
str.matches("x*\\w*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches(".*.*X"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("x*a*x*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("x*,a*x*"); // Compliant, can fail between the two quantifiers
str.matches("x*a*b*c*d*e*f*g*h*i*x*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("x*(xy?)*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(ab)*a(ba)*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("x*a*b*c*d*e*f*g*h*i*x*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("x*a*b*c*d*e*f*g*h*i*j*x*"); // FN because we forget about the first x* when the maximum number of tracked repetitions is exceeded
str.matches("x*a*b*c*d*e*f*g*h*i*j*x*x*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
// Non-possessive followed by possessive quantifier is actually quadratic
str.matches(".*\\s*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches(".*\\s*+"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("x*a*b*c*d*e*f*g*h*i*j*x*x*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
// Non-possessive followed by possessive quantifier is actually polynomial
str.matches(".*\\s*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches(".*\\s*+"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches(".*+\\s*"); // Compliant, other way (possessive then non-possessive) is fine
str.matches(".*+\\s*+"); // Compliant, two possessives is fine
str.matches(".*,\\s*+,"); // Compliant, can fail between the two quantifiers
str.matches("\\s*\\s*+,"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("\\s*\\s*+,"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("a*\\s*+,"); // Compliant, no overlap
str.matches("[a\\s]*\\s*+,"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("[a\\s]*b*\\s*+,"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("[a\\s]*\\s*+,"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("[a\\s]*b*\\s*+,"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("\\s*+[a\\s]*b*,"); // Compliant, possessive then non-possessive
str.matches("\\s*+b*[a\\s]*,"); // Compliant, possessive then non-possessive
// Implicit reluctant quantifier in partial match also leads to quadratic runtime
str.split("\\s*,"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.split("\\s*+,"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(?s:.*)\\s*,(?s:.*)"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(?s:.*)\\s*+,(?s:.*)"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
// Implicit reluctant quantifier in partial match also leads to polynomial runtime
str.split("\\s*,"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.split("\\s*+,"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(?s:.*)\\s*,(?s:.*)"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches("(?s:.*)\\s*+,(?s:.*)"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.split(",\\s*+"); // Compliant
str.split(",\\s*+,"); // Compliant
str.split("\\s*+"); // Compliant
}

void differentPolynomials(String str) {
// quadratic (O(n^2))
str.matches("x*x*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
// cubic (O(n^3))
str.matches("x*x*x*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
// O(n^4)
str.matches("x*x*x*x*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
// O(n^5)
str.matches("x*x*x*x*x*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
// cubic
str.matches("[^=]*.*.*=.*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
}

void fixedInJava9(String str) {
str.matches("(.?,)*X");
}
Expand Down Expand Up @@ -109,6 +124,10 @@ void compliant(String str) {
str.matches("(;?,)*");
str.matches("(;*,)*");
str.matches("x*|x*");
str.matches("a*b*");
str.matches("a*a?b*"); // Noncompliant - false positive :-(
str.matches("a*(a?b)*"); // Noncompliant - false positive :-(
str.matches("a*(ab)*");
str.split("x*x*");
str.matches("(?s)x*.*");
str.matches("x*(?s)*"); // Coverage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ void quadraticInJava9(String str) {
}

void alwaysQuadratic(String str) {
str.matches("x*\\w*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches(".*.*X"); // Noncompliant {{Make sure the regex used here, which is vulnerable to quadratic runtime due to backtracking, cannot lead to denial of service.}}
str.matches("x*\\w*"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
str.matches(".*.*X"); // Noncompliant {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}}
}

void fixedInJava9(String str) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/
package org.sonar.java.checks.helpers;

import java.util.Objects;
import org.sonar.java.annotations.VisibleForTesting;
import org.sonar.java.regex.ast.AutomatonState;
import org.sonar.java.regex.ast.BoundaryTree;
Expand Down Expand Up @@ -47,7 +48,7 @@ protected boolean check(SubAutomaton auto1, SubAutomaton auto2, boolean hasConsu
if (hasUnsupportedTransitionType(auto1) || hasUnsupportedTransitionType(auto2)) {
return defaultAnswer;
}
OrderedAutomataPair entry = new OrderedAutomataPair(auto1, auto2);
OrderedAutomataPair entry = new OrderedAutomataPair(auto1, auto2, hasConsumedInput);
Boolean cachedValue = cache.startCalculation(entry, neutralAnswer());
if (cachedValue != null) {
return cachedValue;
Expand Down Expand Up @@ -150,23 +151,25 @@ T save(OrderedAutomataPair statePair, T value) {
static class OrderedAutomataPair {
public final SubAutomaton auto1;
public final SubAutomaton auto2;
public final boolean hasConsumedInput;

public OrderedAutomataPair(SubAutomaton auto1, SubAutomaton auto2) {
public OrderedAutomataPair(SubAutomaton auto1, SubAutomaton auto2, boolean hasConsumedInput) {
this.auto1 = auto1;
this.auto2 = auto2;
this.hasConsumedInput = hasConsumedInput;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
OrderedAutomataPair that = (OrderedAutomataPair) o;
return auto1.equals(that.auto1) && auto2.equals(that.auto2);
return hasConsumedInput == that.hasConsumedInput && auto1.equals(that.auto1) && auto2.equals(that.auto2);
}

@Override
public int hashCode() {
return 31 * auto1.hashCode() + auto2.hashCode();
return Objects.hash(auto1, auto2, hasConsumedInput);
}
}
}
Loading

0 comments on commit 3fa749e

Please sign in to comment.