Skip to content

Commit cb0ac61

Browse files
authored
Merge pull request #18069 from geoffw0/sourcemodels
Rust: Add some flow source models
2 parents d73dcd6 + 1090164 commit cb0ac61

File tree

14 files changed

+293
-0
lines changed

14 files changed

+293
-0
lines changed

rust/ql/lib/codeql/rust/Concepts.qll

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
private import codeql.rust.dataflow.DataFlow
88
private import codeql.threatmodels.ThreatModels
9+
private import codeql.rust.Frameworks
910

1011
/**
1112
* A data flow source for a specific threat-model.
@@ -46,6 +47,63 @@ class ActiveThreatModelSource extends ThreatModelSource {
4647
ActiveThreatModelSource() { currentThreatModel(this.getThreatModel()) }
4748
}
4849

50+
/**
51+
* A data flow source corresponding to the program's command line arguments or path.
52+
*/
53+
final class CommandLineArgsSource = CommandLineArgsSource::Range;
54+
55+
/**
56+
* Provides a class for modeling new sources for the program's command line arguments or path.
57+
*/
58+
module CommandLineArgsSource {
59+
/**
60+
* A data flow source corresponding to the program's command line arguments or path.
61+
*/
62+
abstract class Range extends ThreatModelSource::Range {
63+
override string getThreatModel() { result = "commandargs" }
64+
65+
override string getSourceType() { result = "CommandLineArgs" }
66+
}
67+
}
68+
69+
/**
70+
* A data flow source corresponding to the program's environment.
71+
*/
72+
final class EnvironmentSource = EnvironmentSource::Range;
73+
74+
/**
75+
* Provides a class for modeling new sources for the program's environment.
76+
*/
77+
module EnvironmentSource {
78+
/**
79+
* A data flow source corresponding to the program's environment.
80+
*/
81+
abstract class Range extends ThreatModelSource::Range {
82+
override string getThreatModel() { result = "environment" }
83+
84+
override string getSourceType() { result = "EnvironmentSource" }
85+
}
86+
}
87+
88+
/**
89+
* A data flow source for remote (network) data.
90+
*/
91+
final class RemoteSource = RemoteSource::Range;
92+
93+
/**
94+
* Provides a class for modeling new sources of remote (network) data.
95+
*/
96+
module RemoteSource {
97+
/**
98+
* A data flow source for remote (network) data.
99+
*/
100+
abstract class Range extends ThreatModelSource::Range {
101+
override string getThreatModel() { result = "remote" }
102+
103+
override string getSourceType() { result = "RemoteSource" }
104+
}
105+
}
106+
49107
/**
50108
* A data-flow node that constructs a SQL statement.
51109
*
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* This file imports all models of frameworks and libraries.
3+
*/
4+
5+
private import codeql.rust.frameworks.Reqwest
6+
private import codeql.rust.frameworks.stdlib.Env
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Provides modeling for the `reqwest` library.
3+
*/
4+
5+
private import rust
6+
private import codeql.rust.Concepts
7+
8+
/**
9+
* A call to `reqwest::get` or `reqwest::blocking::get`.
10+
*/
11+
private class ReqwestGet extends RemoteSource::Range {
12+
ReqwestGet() {
13+
exists(CallExpr ce |
14+
this.asExpr().getExpr() = ce and
15+
ce.getExpr().(PathExpr).getPath().getResolvedCrateOrigin().matches("%reqwest") and
16+
ce.getExpr().(PathExpr).getPath().getResolvedPath() = ["crate::get", "crate::blocking::get"]
17+
)
18+
}
19+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Provides modeling for the `std::env` library.
3+
*/
4+
5+
private import rust
6+
private import codeql.rust.Concepts
7+
8+
/**
9+
* A call to `std::env::args` or `std::env::args_os`.
10+
*/
11+
private class StdEnvArgs extends CommandLineArgsSource::Range {
12+
StdEnvArgs() {
13+
this.asExpr().getExpr().(CallExpr).getExpr().(PathExpr).getPath().getResolvedPath() =
14+
["crate::env::args", "crate::env::args_os"]
15+
}
16+
}
17+
18+
/**
19+
* A call to `std::env::current_dir`, `std::env::current_exe` or `std::env::home_dir`.
20+
*/
21+
private class StdEnvDir extends CommandLineArgsSource::Range {
22+
StdEnvDir() {
23+
this.asExpr().getExpr().(CallExpr).getExpr().(PathExpr).getPath().getResolvedPath() =
24+
["crate::env::current_dir", "crate::env::current_exe", "crate::env::home_dir"]
25+
}
26+
}
27+
28+
/**
29+
* A call to `std::env::var`, `std::env::var_os`, `std::env::vars` or `std::env::vars_os`.
30+
*/
31+
private class StdEnvVar extends EnvironmentSource::Range {
32+
StdEnvVar() {
33+
this.asExpr().getExpr().(CallExpr).getExpr().(PathExpr).getPath().getResolvedPath() =
34+
["crate::env::var", "crate::env::var_os", "crate::env::vars", "crate::env::vars_os"]
35+
}
36+
}

rust/ql/src/queries/summary/SummaryStats.ql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import rust
10+
import codeql.rust.Concepts
1011
import codeql.rust.Diagnostics
1112
import Stats
1213

@@ -43,4 +44,8 @@ where
4344
key = "Macro calls - resolved" and value = count(MacroCall mc | mc.hasExpanded())
4445
or
4546
key = "Macro calls - unresolved" and value = count(MacroCall mc | not mc.hasExpanded())
47+
or
48+
key = "Taint sources - total" and value = count(ThreatModelSource s)
49+
or
50+
key = "Taint sources - active" and value = count(ActiveThreatModelSource s)
4651
select key, value order by key
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @name Taint Sources
3+
* @description List all sources of untrusted input that have been idenfitied
4+
* in the database.
5+
* @kind problem
6+
* @problem.severity info
7+
* @id rust/summary/taint-sources
8+
* @tags summary
9+
*/
10+
11+
import rust
12+
import codeql.rust.Concepts
13+
14+
from ThreatModelSource s, string defaultString
15+
where
16+
if s instanceof ActiveThreatModelSource then defaultString = " (DEFAULT)" else defaultString = ""
17+
select s,
18+
"Flow source '" + s.getSourceType() + "' of type " + s.getThreatModel() + defaultString + "."

rust/ql/test/library-tests/dataflow/sources/InlineFlow.expected

Whitespace-only changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import rust
2+
import codeql.rust.dataflow.DataFlow
3+
import codeql.rust.Concepts
4+
import utils.InlineFlowTest
5+
6+
/**
7+
* Configuration for flow from any threat model source to an argument of the function `sink`.
8+
*/
9+
module MyFlowConfig implements DataFlow::ConfigSig {
10+
predicate isSource(DataFlow::Node source) { source instanceof ThreatModelSource }
11+
12+
predicate isSink(DataFlow::Node sink) {
13+
any(CallExpr call | call.getExpr().(PathExpr).getPath().getResolvedPath() = "crate::test::sink")
14+
.getArgList()
15+
.getAnArg() = sink.asExpr().getExpr()
16+
}
17+
}
18+
19+
module MyFlowTest = TaintFlowTest<MyFlowConfig>;
20+
21+
import MyFlowTest
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
| test.rs:8:10:8:30 | ...::var(...) | Flow source 'EnvironmentSource' of type environment. |
2+
| test.rs:9:10:9:33 | ...::var_os(...) | Flow source 'EnvironmentSource' of type environment. |
3+
| test.rs:11:16:11:36 | ...::var(...) | Flow source 'EnvironmentSource' of type environment. |
4+
| test.rs:12:16:12:39 | ...::var_os(...) | Flow source 'EnvironmentSource' of type environment. |
5+
| test.rs:17:25:17:40 | ...::vars(...) | Flow source 'EnvironmentSource' of type environment. |
6+
| test.rs:22:25:22:43 | ...::vars_os(...) | Flow source 'EnvironmentSource' of type environment. |
7+
| test.rs:29:29:29:44 | ...::args(...) | Flow source 'CommandLineArgs' of type commandargs. |
8+
| test.rs:32:16:32:31 | ...::args(...) | Flow source 'CommandLineArgs' of type commandargs. |
9+
| test.rs:33:16:33:34 | ...::args_os(...) | Flow source 'CommandLineArgs' of type commandargs. |
10+
| test.rs:40:16:40:31 | ...::args(...) | Flow source 'CommandLineArgs' of type commandargs. |
11+
| test.rs:44:16:44:34 | ...::args_os(...) | Flow source 'CommandLineArgs' of type commandargs. |
12+
| test.rs:50:15:50:37 | ...::current_dir(...) | Flow source 'CommandLineArgs' of type commandargs. |
13+
| test.rs:51:15:51:37 | ...::current_exe(...) | Flow source 'CommandLineArgs' of type commandargs. |
14+
| test.rs:52:16:52:35 | ...::home_dir(...) | Flow source 'CommandLineArgs' of type commandargs. |
15+
| test.rs:60:26:60:70 | ...::get(...) | Flow source 'RemoteSource' of type remote (DEFAULT). |
16+
| test.rs:63:26:63:70 | ...::get(...) | Flow source 'RemoteSource' of type remote (DEFAULT). |
17+
| test.rs:66:26:66:60 | ...::get(...) | Flow source 'RemoteSource' of type remote (DEFAULT). |
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
query: queries/summary/TaintSources.ql
2+
postprocess: utils/InlineExpectationsTestQuery.ql
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
qltest_cargo_check: true
2+
qltest_dependencies:
3+
- reqwest = { version = "0.12.9", features = ["blocking"] }
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
// --- stubs for the "reqwest" library ---
3+
4+
/*
5+
--- we don't seem to have a way to use this, hence we currently test against the real reqwest library
6+
#[derive(Debug)]
7+
pub struct Error { }
8+
9+
pub mod blocking {
10+
pub struct Response { }
11+
impl Response {
12+
pub fn text(self) -> Result<String, super::Error> {
13+
Ok("".to_string())
14+
}
15+
}
16+
17+
pub fn get<T>(url: T) -> Result<Response, super::Error> {
18+
let _url = url;
19+
20+
Ok(Response {})
21+
}
22+
}
23+
24+
pub struct Response { }
25+
impl Response {
26+
pub async fn text(self) -> Result<String, Error> {
27+
Ok("".to_string())
28+
}
29+
}
30+
31+
pub async fn get<T>(url: T) -> Result<Response, Error> {
32+
let _url = url;
33+
34+
Ok(Response {})
35+
}
36+
*/
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#![allow(deprecated)]
2+
3+
fn sink<T>(_: T) { }
4+
5+
// --- tests ---
6+
7+
fn test_env_vars() {
8+
sink(std::env::var("HOME")); // $ Alert[rust/summary/taint-sources] hasTaintFlow
9+
sink(std::env::var_os("PATH")); // $ Alert[rust/summary/taint-sources] hasTaintFlow
10+
11+
let var1 = std::env::var("HOME").expect("HOME not set"); // $ Alert[rust/summary/taint-sources]
12+
let var2 = std::env::var_os("PATH").unwrap(); // $ Alert[rust/summary/taint-sources]
13+
14+
sink(var1); // $ MISSING: hasTaintFlow
15+
sink(var2); // $ MISSING: hasTaintFlow
16+
17+
for (key, value) in std::env::vars() { // $ Alert[rust/summary/taint-sources]
18+
sink(key); // $ MISSING: hasTaintFlow
19+
sink(value); // $ MISSING: hasTaintFlow
20+
}
21+
22+
for (key, value) in std::env::vars_os() { // $ Alert[rust/summary/taint-sources]
23+
sink(key); // $ MISSING: hasTaintFlow
24+
sink(value); // $ MISSING: hasTaintFlow
25+
}
26+
}
27+
28+
fn test_env_args() {
29+
let args: Vec<String> = std::env::args().collect(); // $ Alert[rust/summary/taint-sources]
30+
let my_path = &args[0];
31+
let arg1 = &args[1];
32+
let arg2 = std::env::args().nth(2).unwrap(); // $ Alert[rust/summary/taint-sources]
33+
let arg3 = std::env::args_os().nth(3).unwrap(); // $ Alert[rust/summary/taint-sources]
34+
35+
sink(my_path); // $ MISSING: hasTaintFlow
36+
sink(arg1); // $ MISSING: hasTaintFlow
37+
sink(arg2); // $ MISSING: hasTaintFlow
38+
sink(arg3); // $ MISSING: hasTaintFlow
39+
40+
for arg in std::env::args() { // $ Alert[rust/summary/taint-sources]
41+
sink(arg); // $ MISSING: hasTaintFlow
42+
}
43+
44+
for arg in std::env::args_os() { // $ Alert[rust/summary/taint-sources]
45+
sink(arg); // $ MISSING: hasTaintFlow
46+
}
47+
}
48+
49+
fn test_env_dirs() {
50+
let dir = std::env::current_dir().expect("FAILED"); // $ Alert[rust/summary/taint-sources]
51+
let exe = std::env::current_exe().expect("FAILED"); // $ Alert[rust/summary/taint-sources]
52+
let home = std::env::home_dir().expect("FAILED"); // $ Alert[rust/summary/taint-sources]
53+
54+
sink(dir); // $ MISSING: hasTaintFlow
55+
sink(exe); // $ MISSING: hasTaintFlow
56+
sink(home); // $ MISSING: hasTaintFlow
57+
}
58+
59+
async fn test_reqwest() -> Result<(), reqwest::Error> {
60+
let remote_string1 = reqwest::blocking::get("http://example.com/")?.text()?; // $ Alert[rust/summary/taint-sources]
61+
sink(remote_string1); // $ MISSING: hasTaintFlow
62+
63+
let remote_string2 = reqwest::blocking::get("http://example.com/").unwrap().text().unwrap(); // $ Alert[rust/summary/taint-sources]
64+
sink(remote_string2); // $ MISSING: hasTaintFlow
65+
66+
let remote_string3 = reqwest::get("http://example.com/").await?.text().await?; // $ Alert[rust/summary/taint-sources]
67+
sink(remote_string3); // $ MISSING: hasTaintFlow
68+
69+
Ok(())
70+
}

rust/ql/test/query-tests/diagnostics/SummaryStats.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@
1313
| Macro calls - resolved | 8 |
1414
| Macro calls - total | 9 |
1515
| Macro calls - unresolved | 1 |
16+
| Taint sources - active | 0 |
17+
| Taint sources - total | 0 |

0 commit comments

Comments
 (0)