Rust: Add integral type barrier for Regex injection.

This commit is contained in:
Geoffrey White
2025-10-31 15:54:23 +00:00
parent 2d4369ac6c
commit 33efed92b8
4 changed files with 32 additions and 34 deletions

View File

@@ -24,3 +24,19 @@ class NumericTypeBarrier extends DataFlow::Node {
)
}
}
/**
* A node whose type is an integral (integer) or boolean type, which may be an
* appropriate taint flow barrier for some queries.
*/
class IntegralOrBooleanTypeBarrier extends DataFlow::Node {
IntegralOrBooleanTypeBarrier() {
exists(TypeInference::Type t |
t = TypeInference::inferType(this.asExpr().getExpr()) and
(
t.(StructType).getStruct() instanceof IntegralType or
t.(StructType).getStruct() instanceof Bool
)
)
}
}

View File

@@ -9,6 +9,7 @@ private import codeql.rust.dataflow.DataFlow
private import codeql.rust.controlflow.CfgNodes
private import codeql.rust.dataflow.FlowSink
private import codeql.rust.Concepts
private import codeql.rust.security.Barriers as Barriers
/**
* Provides default sources, sinks and barriers for detecting regular expression
@@ -87,4 +88,13 @@ module RegexInjection {
.getText() = "escape"
}
}
/**
* A barrier for regular expression injection vulnerabilities for nodes whose
* type is an integral or boolean type, which is unlikely to expose any vulnerability.
*
* We don't include floating point types in this barrier, as `.` is a special character
* in regular expressions.
*/
private class IntegralOrBooleanTypeBarrier extends Barrier instanceof Barriers::IntegralOrBooleanTypeBarrier { }
}

View File

@@ -1,6 +1,5 @@
#select
| main.rs:6:25:6:30 | &regex | main.rs:4:20:4:32 | ...::var | main.rs:6:25:6:30 | &regex | This regular expression is constructed from a $@. | main.rs:4:20:4:32 | ...::var | user-provided value |
| main.rs:21:25:21:30 | &regex | main.rs:19:23:19:35 | ...::var | main.rs:21:25:21:30 | &regex | This regular expression is constructed from a $@. | main.rs:19:23:19:35 | ...::var | user-provided value |
edges
| main.rs:4:9:4:16 | username | main.rs:5:25:5:44 | MacroExpr | provenance | |
| main.rs:4:20:4:32 | ...::var | main.rs:4:20:4:40 | ...::var(...) [Ok] | provenance | Src:MaD:1 |
@@ -9,28 +8,14 @@ edges
| main.rs:5:9:5:13 | regex | main.rs:6:26:6:30 | regex | provenance | |
| main.rs:5:25:5:44 | ...::format(...) | main.rs:5:25:5:44 | { ... } | provenance | |
| main.rs:5:25:5:44 | ...::must_use(...) | main.rs:5:9:5:13 | regex | provenance | |
| main.rs:5:25:5:44 | MacroExpr | main.rs:5:25:5:44 | ...::format(...) | provenance | MaD:4 |
| main.rs:5:25:5:44 | { ... } | main.rs:5:25:5:44 | ...::must_use(...) | provenance | MaD:5 |
| main.rs:5:25:5:44 | MacroExpr | main.rs:5:25:5:44 | ...::format(...) | provenance | MaD:3 |
| main.rs:5:25:5:44 | { ... } | main.rs:5:25:5:44 | ...::must_use(...) | provenance | MaD:4 |
| main.rs:6:26:6:30 | regex | main.rs:6:25:6:30 | &regex | provenance | |
| main.rs:19:9:19:19 | user_number | main.rs:20:25:20:47 | MacroExpr | provenance | |
| main.rs:19:23:19:35 | ...::var | main.rs:19:23:19:43 | ...::var(...) [Ok] | provenance | Src:MaD:1 |
| main.rs:19:23:19:43 | ...::var(...) [Ok] | main.rs:19:23:19:70 | ... .unwrap_or(...) | provenance | MaD:2 |
| main.rs:19:23:19:70 | ... .unwrap_or(...) | main.rs:19:23:19:85 | ... .parse() [Ok] | provenance | MaD:3 |
| main.rs:19:23:19:70 | ... .unwrap_or(...) | main.rs:19:23:19:85 | ... .parse() [Ok] | provenance | MaD:3 |
| main.rs:19:23:19:85 | ... .parse() [Ok] | main.rs:19:23:19:98 | ... .unwrap_or(...) | provenance | MaD:2 |
| main.rs:19:23:19:98 | ... .unwrap_or(...) | main.rs:19:9:19:19 | user_number | provenance | |
| main.rs:20:9:20:13 | regex | main.rs:21:26:21:30 | regex | provenance | |
| main.rs:20:25:20:47 | ...::format(...) | main.rs:20:25:20:47 | { ... } | provenance | |
| main.rs:20:25:20:47 | ...::must_use(...) | main.rs:20:9:20:13 | regex | provenance | |
| main.rs:20:25:20:47 | MacroExpr | main.rs:20:25:20:47 | ...::format(...) | provenance | MaD:4 |
| main.rs:20:25:20:47 | { ... } | main.rs:20:25:20:47 | ...::must_use(...) | provenance | MaD:5 |
| main.rs:21:26:21:30 | regex | main.rs:21:25:21:30 | &regex | provenance | |
models
| 1 | Source: std::env::var; ReturnValue.Field[core::result::Result::Ok(0)]; environment |
| 2 | Summary: <core::result::Result>::unwrap_or; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value |
| 3 | Summary: <core::str>::parse; Argument[self]; ReturnValue.Field[core::result::Result::Ok(0)]; taint |
| 4 | Summary: alloc::fmt::format; Argument[0]; ReturnValue; taint |
| 5 | Summary: core::hint::must_use; Argument[0]; ReturnValue; value |
| 3 | Summary: alloc::fmt::format; Argument[0]; ReturnValue; taint |
| 4 | Summary: core::hint::must_use; Argument[0]; ReturnValue; value |
nodes
| main.rs:4:9:4:16 | username | semmle.label | username |
| main.rs:4:20:4:32 | ...::var | semmle.label | ...::var |
@@ -43,17 +28,4 @@ nodes
| main.rs:5:25:5:44 | { ... } | semmle.label | { ... } |
| main.rs:6:25:6:30 | &regex | semmle.label | &regex |
| main.rs:6:26:6:30 | regex | semmle.label | regex |
| main.rs:19:9:19:19 | user_number | semmle.label | user_number |
| main.rs:19:23:19:35 | ...::var | semmle.label | ...::var |
| main.rs:19:23:19:43 | ...::var(...) [Ok] | semmle.label | ...::var(...) [Ok] |
| main.rs:19:23:19:70 | ... .unwrap_or(...) | semmle.label | ... .unwrap_or(...) |
| main.rs:19:23:19:85 | ... .parse() [Ok] | semmle.label | ... .parse() [Ok] |
| main.rs:19:23:19:98 | ... .unwrap_or(...) | semmle.label | ... .unwrap_or(...) |
| main.rs:20:9:20:13 | regex | semmle.label | regex |
| main.rs:20:25:20:47 | ...::format(...) | semmle.label | ...::format(...) |
| main.rs:20:25:20:47 | ...::must_use(...) | semmle.label | ...::must_use(...) |
| main.rs:20:25:20:47 | MacroExpr | semmle.label | MacroExpr |
| main.rs:20:25:20:47 | { ... } | semmle.label | { ... } |
| main.rs:21:25:21:30 | &regex | semmle.label | &regex |
| main.rs:21:26:21:30 | regex | semmle.label | regex |
subpaths

View File

@@ -16,9 +16,9 @@ fn simple_good_escaped(hay: &str) -> Option<bool> {
}
fn simple_good_numeric(hay: &str) -> Option<bool> {
let user_number = std::env::var("USER").unwrap_or("0".to_string()).parse::<u64>().unwrap_or(0); // $ Source=env
let user_number = std::env::var("USER").unwrap_or("0".to_string()).parse::<u64>().unwrap_or(0);
let regex = format!("foo{}bar", user_number);
let re = Regex::new(&regex).unwrap(); // $ SPURIOUS: Alert[rust/regex-injection]=env
let re = Regex::new(&regex).unwrap();
Some(re.is_match(hay))
}