Skip to content

Commit

Permalink
feat: move backrefexistscondition into conditional
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoDog896 committed Mar 5, 2024
1 parent 1610b24 commit 59433a2
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 24 deletions.
8 changes: 8 additions & 0 deletions crates/redos/src/ilq.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use crate::ir::Expr;

/// Returns true iif an ilq is present anywhere in the regex
pub fn scan_ilq(expr: &Expr) -> bool {
match expr {
_ => false,
}
}
34 changes: 19 additions & 15 deletions crates/redos/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ use fancy_regex::{parse::ExprTree, Assertion, Expr as RegexExpr, LookAround};

use crate::vulnerability::VulnerabilityConfig;

#[derive(Debug, PartialEq, Eq)]
pub enum ExprConditional {
Condition(Box<Expr>),
BackrefExistsCondition(usize),
}

#[derive(Debug, PartialEq, Eq)]
pub enum Expr {
/// An empty expression, e.g. the last branch in `(a|b|)`
Empty,
/// Some token, whether its a character class, any character, etc.
Token,
/// An assertion
Expand Down Expand Up @@ -38,14 +42,10 @@ pub enum Expr {
/// Atomic non-capturing group, e.g. `(?>ab|a)` in text that contains `ab` will match `ab` and
/// never backtrack and try `a`, even if matching fails after the atomic group.
AtomicGroup(Box<Expr>),
/// Anchor to match at the position where the previous match ended
ContinueFromPreviousMatchEnd,
/// Conditional expression based on whether the numbered capture group matched or not
BackrefExistsCondition(usize),
/// If/Then/Else Condition. If there is no Then/Else, these will just be empty expressions.
Conditional {
/// The conditional expression to evaluate
condition: Box<Expr>,
condition: ExprConditional,
/// What to execute if the condition is true
true_branch: Box<Expr>,
/// What to execute if the condition is false
Expand All @@ -56,7 +56,7 @@ pub enum Expr {
/// Converts a fancy-regex AST to an IR AST
pub fn to_expr(tree: &ExprTree, expr: &RegexExpr, config: &VulnerabilityConfig) -> Option<Expr> {
match expr {
RegexExpr::Empty => Some(Expr::Empty),
RegexExpr::Empty => None,
RegexExpr::Any { .. } => Some(Expr::Token),
RegexExpr::Assertion(a) => Some(Expr::Assertion(*a)),
RegexExpr::Literal { .. } => Some(Expr::Token),
Expand Down Expand Up @@ -109,21 +109,25 @@ pub fn to_expr(tree: &ExprTree, expr: &RegexExpr, config: &VulnerabilityConfig)
to_expr(tree, e, config).map(|e| Expr::AtomicGroup(Box::new(e)))
}
RegexExpr::KeepOut => None,
RegexExpr::ContinueFromPreviousMatchEnd => Some(Expr::ContinueFromPreviousMatchEnd),
RegexExpr::BackrefExistsCondition(i) => Some(Expr::BackrefExistsCondition(*i)),
RegexExpr::ContinueFromPreviousMatchEnd => None,
RegexExpr::BackrefExistsCondition(_) => None,
RegexExpr::Conditional {
condition,
true_branch,
false_branch,
} => {
let condition = to_expr(tree, condition, config);
let true_branch = to_expr(tree, true_branch, config);
let false_branch = to_expr(tree, false_branch, config);
if let (Some(condition), Some(true_branch), Some(false_branch)) =
(condition, true_branch, false_branch)
if let (Some(true_branch), Some(false_branch)) =
(true_branch, false_branch)
{
Some(Expr::Conditional {
condition: Box::new(condition),
let condition: Option<ExprConditional> = match condition.as_ref() {
&RegexExpr::BackrefExistsCondition(number) => Some(ExprConditional::BackrefExistsCondition(number)),
expr => to_expr(tree, expr, config).map(|x| ExprConditional::Condition(Box::new(x)))
};

condition.map(|condition| Expr::Conditional {
condition,
true_branch: Box::new(true_branch),
false_branch: Box::new(false_branch),
})
Expand Down
27 changes: 18 additions & 9 deletions crates/redos/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
pub mod ir;
pub mod vulnerability;

mod ilq;

use fancy_regex::parse::Parser;
use fancy_regex::Result;
use ir::{to_expr, Expr};
use fancy_regex::{Result, Expr as RegexExpr};
use ir::{to_expr, Expr, ExprConditional};
use vulnerability::{Vulnerability, VulnerabilityConfig};

/// Returns true iif repeats are present anywhere in the regex
Expand All @@ -17,11 +19,8 @@ fn repeats_anywhere(expr: &Expr, config: &VulnerabilityConfig) -> bool {
Expr::Repeat { .. } => true,

// no nested expressions
Expr::Empty => false,
Expr::Token => false,
Expr::Assertion(_) => false,
Expr::ContinueFromPreviousMatchEnd => false,
Expr::BackrefExistsCondition(_) => false,

// propagate
Expr::Concat(list) => list.iter().any(|e| repeats_anywhere(e, config)),
Expand All @@ -35,9 +34,12 @@ fn repeats_anywhere(expr: &Expr, config: &VulnerabilityConfig) -> bool {
true_branch,
false_branch,
} => {
repeats_anywhere(condition.as_ref(), config)
|| repeats_anywhere(true_branch.as_ref(), config)
|| repeats_anywhere(false_branch.as_ref(), config)
match condition {
ExprConditional::BackrefExistsCondition(_) => false,
ExprConditional::Condition(condition) => repeats_anywhere(condition.as_ref(), config)
|| repeats_anywhere(true_branch.as_ref(), config)
|| repeats_anywhere(false_branch.as_ref(), config)
}
}
}
}
Expand All @@ -60,6 +62,13 @@ pub fn vulnerabilities(regex: &str, config: &VulnerabilityConfig) -> Result<Vuln
// first pass: parse the regex
let tree = Parser::parse(regex)?;

if tree.expr == RegexExpr::Empty {
return Ok(VulnerabilityResult {
vulnerabilities: vec![],
dfa: can_be_dfa,
});
}

// second pass: turn AST into IR
let expr =
to_expr(&tree, &tree.expr, config).expect("Failed to convert AST to IR; this is a bug");
Expand All @@ -74,7 +83,7 @@ pub fn vulnerabilities(regex: &str, config: &VulnerabilityConfig) -> Result<Vuln

// TODO: this is a fake placeholder
Ok(VulnerabilityResult {
vulnerabilities: vec![Vulnerability::ExponentialOverlappingDisjunction],
vulnerabilities: vec![Vulnerability::InitialQuantifier],
dfa: can_be_dfa,
})
}

0 comments on commit 59433a2

Please sign in to comment.