Skip to content

Commit fbf7e05

Browse files
authored
Merge pull request #18129 from geoffw0/sinkmodels
Rust: Sink models for rust/sql-injection
2 parents f7aab2e + 5b50a82 commit fbf7e05

File tree

6 files changed

+121
-59
lines changed

6 files changed

+121
-59
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ module RemoteSource {
105105
}
106106

107107
/**
108-
* A data-flow node that constructs a SQL statement.
108+
* A data-flow node that constructs a SQL statement (for later execution).
109109
*
110110
* Often, it is worthy of an alert if a SQL statement is constructed such that
111111
* executing it would be a security risk.
@@ -133,10 +133,10 @@ module SqlConstruction {
133133
}
134134

135135
/**
136-
* A data-flow node that executes SQL statements.
136+
* A data-flow node that constructs and executes SQL statements.
137137
*
138138
* If the context of interest is such that merely constructing a SQL statement
139-
* would be valuable to report, consider using `SqlConstruction`.
139+
* would be valuable to report, consider also using `SqlConstruction`.
140140
*
141141
* Extend this class to refine existing API models. If you want to model new APIs,
142142
* extend `SqlExecution::Range` instead.

rust/ql/lib/codeql/rust/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44

55
private import codeql.rust.frameworks.Reqwest
66
private import codeql.rust.frameworks.stdlib.Env
7+
private import codeql.rust.frameworks.Sqlx
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Provides modeling for the `SQLx` library.
3+
*/
4+
5+
private import rust
6+
private import codeql.rust.Concepts
7+
private import codeql.rust.dataflow.DataFlow
8+
9+
/**
10+
* A call to `sqlx::query` and variations.
11+
*/
12+
private class SqlxQuery extends SqlConstruction::Range {
13+
CallExpr call;
14+
15+
SqlxQuery() {
16+
this.asExpr().getExpr() = call and
17+
call.getFunction().(PathExpr).getPath().getResolvedPath() =
18+
[
19+
"crate::query::query", "crate::query_as::query_as", "crate::query_with::query_with",
20+
"crate::query_as_with::query_as_with", "crate::query_scalar::query_scalar",
21+
"crate::query_scalar_with::query_scalar_with", "crate::raw_sql::raw_sql"
22+
]
23+
}
24+
25+
override DataFlow::Node getSql() { result.asExpr().getExpr() = call.getArgList().getArg(0) }
26+
}
27+
28+
/**
29+
* A call to `sqlx::Executor::execute`.
30+
*/
31+
private class SqlxExecute extends SqlExecution::Range {
32+
MethodCallExpr call;
33+
34+
SqlxExecute() {
35+
this.asExpr().getExpr() = call and
36+
call.(Resolvable).getResolvedPath() = "crate::executor::Executor::execute"
37+
}
38+
39+
override DataFlow::Node getSql() { result.asExpr().getExpr() = call.getArgList().getArg(0) }
40+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
testFailures
2+
failures
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import rust
2+
import codeql.rust.security.SqlInjectionExtensions
3+
import utils.InlineExpectationsTest
4+
5+
module SqlSinksTest implements TestSig {
6+
string getARelevantTag() { result = "sql-sink" }
7+
8+
predicate hasActualResult(Location location, string element, string tag, string value) {
9+
exists(SqlInjection::Sink sink |
10+
location = sink.getLocation() and
11+
location.getFile().getBaseName() != "" and
12+
element = sink.toString() and
13+
tag = "sql-sink" and
14+
value = ""
15+
)
16+
}
17+
}
18+
19+
import MakeTest<SqlSinksTest>

rust/ql/test/query-tests/security/CWE-089/sqlx.rs

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ async fn test_sqlx_mysql(url: &str, enable_remote: bool) -> Result<(), sqlx::Err
4444

4545
// construct queries (with extra variants)
4646
let const_string = String::from("Alice");
47-
let arg_string = std::env::args().nth(1).unwrap_or(String::from("Alice")); // $ MISSING Source=args1
48-
let remote_string = reqwest::blocking::get("http://example.com/").unwrap().text().unwrap_or(String::from("Alice")); // $ MISSING Source=remote1
47+
let arg_string = std::env::args().nth(1).unwrap_or(String::from("Alice")); // $ MISSING: Source=args1
48+
let remote_string = reqwest::blocking::get("http://example.com/").unwrap().text().unwrap_or(String::from("Alice")); // $ MISSING: Source=remote1
4949
let remote_number = remote_string.parse::<i32>().unwrap_or(0);
5050
let safe_query_1 = String::from("SELECT * FROM people WHERE firstname='Alice'");
5151
let safe_query_2 = String::from("SELECT * FROM people WHERE firstname='") + &const_string + "'";
@@ -57,31 +57,31 @@ async fn test_sqlx_mysql(url: &str, enable_remote: bool) -> Result<(), sqlx::Err
5757
let prepared_query_1 = String::from("SELECT * FROM people WHERE firstname=?"); // (prepared arguments are safe)
5858

5959
// direct execution
60-
let _ = conn.execute(safe_query_1.as_str()).await?;
61-
let _ = conn.execute(safe_query_2.as_str()).await?;
62-
let _ = conn.execute(safe_query_3.as_str()).await?;
63-
let _ = conn.execute(unsafe_query_1.as_str()).await?; // $ MISSING Alert[sql-injection]=args1
60+
let _ = conn.execute(safe_query_1.as_str()).await?; // $ sql-sink
61+
let _ = conn.execute(safe_query_2.as_str()).await?; // $ sql-sink
62+
let _ = conn.execute(safe_query_3.as_str()).await?; // $ sql-sink
63+
let _ = conn.execute(unsafe_query_1.as_str()).await?; // $ sql-sink MISSING: Alert[sql-injection]=args1
6464
if enable_remote {
65-
let _ = conn.execute(unsafe_query_2.as_str()).await?; // $ MISSING Alert[sql-injection]=remote1
66-
let _ = conn.execute(unsafe_query_3.as_str()).await?; // $ MISSING Alert[sql-injection]=remote1
67-
let _ = conn.execute(unsafe_query_4.as_str()).await?; // $ MISSING Alert[sql-injection]=remote1
65+
let _ = conn.execute(unsafe_query_2.as_str()).await?; // $ sql-sink MISSING: Alert[sql-injection]=remote1
66+
let _ = conn.execute(unsafe_query_3.as_str()).await?; // $ sql-sink MISSING: Alert[sql-injection]=remote1
67+
let _ = conn.execute(unsafe_query_4.as_str()).await?; // $ sql-sink MISSING: Alert[sql-injection]=remote1
6868
}
6969

7070
// prepared queries
71-
let _ = sqlx::query(safe_query_1.as_str()).execute(&pool).await?;
72-
let _ = sqlx::query(safe_query_2.as_str()).execute(&pool).await?;
73-
let _ = sqlx::query(safe_query_3.as_str()).execute(&pool).await?;
74-
let _ = sqlx::query(unsafe_query_1.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=args1
71+
let _ = sqlx::query(safe_query_1.as_str()).execute(&pool).await?; // $ sql-sink
72+
let _ = sqlx::query(safe_query_2.as_str()).execute(&pool).await?; // $ sql-sink
73+
let _ = sqlx::query(safe_query_3.as_str()).execute(&pool).await?; // $ sql-sink
74+
let _ = sqlx::query(unsafe_query_1.as_str()).execute(&pool).await?; // $ sql-sink MISSING: Alert[sql-injection]=args1
7575
if enable_remote {
76-
let _ = sqlx::query(unsafe_query_2.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=remote1
77-
let _ = sqlx::query(unsafe_query_3.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=remote1
78-
let _ = sqlx::query(unsafe_query_4.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=remote1
76+
let _ = sqlx::query(unsafe_query_2.as_str()).execute(&pool).await?; // $ sql-sink MISSING: Alert[sql-injection]=remote1
77+
let _ = sqlx::query(unsafe_query_3.as_str()).execute(&pool).await?; // $ sql-sink MISSING: Alert[sql-injection]=remote1
78+
let _ = sqlx::query(unsafe_query_4.as_str()).execute(&pool).await?; // $ sql-sink MISSING: Alert[sql-injection]=remote1
7979
}
80-
let _ = sqlx::query(prepared_query_1.as_str()).bind(const_string).execute(&pool).await?;
81-
let _ = sqlx::query(prepared_query_1.as_str()).bind(arg_string).execute(&pool).await?;
80+
let _ = sqlx::query(prepared_query_1.as_str()).bind(const_string).execute(&pool).await?; // $ sql-sink
81+
let _ = sqlx::query(prepared_query_1.as_str()).bind(arg_string).execute(&pool).await?; // $ sql-sink
8282
if enable_remote {
83-
let _ = sqlx::query(prepared_query_1.as_str()).bind(remote_string).execute(&pool).await?;
84-
let _ = sqlx::query(prepared_query_1.as_str()).bind(remote_number).execute(&pool).await?;
83+
let _ = sqlx::query(prepared_query_1.as_str()).bind(remote_string).execute(&pool).await?; // $ sql-sink
84+
let _ = sqlx::query(prepared_query_1.as_str()).bind(remote_number).execute(&pool).await?; // $ sql-sink
8585
}
8686

8787
Ok(())
@@ -93,67 +93,67 @@ async fn test_sqlx_sqlite(url: &str, enable_remote: bool) -> Result<(), sqlx::Er
9393

9494
// construct queries
9595
let const_string = String::from("Alice");
96-
let remote_string = reqwest::blocking::get("http://example.com/").unwrap().text().unwrap_or(String::from("Alice")); // $ MISSING Source=remote2
96+
let remote_string = reqwest::blocking::get("http://example.com/").unwrap().text().unwrap_or(String::from("Alice")); // $ MISSING: Source=remote2
9797
let safe_query_1 = String::from("SELECT * FROM people WHERE firstname='") + &const_string + "'";
9898
let unsafe_query_1 = String::from("SELECT * FROM people WHERE firstname='") + &remote_string + "'";
9999
let prepared_query_1 = String::from("SELECT * FROM people WHERE firstname=?"); // (prepared arguments are safe)
100100

101101
// direct execution (with extra variants)
102-
let _ = conn.execute(safe_query_1.as_str()).await?;
102+
let _ = conn.execute(safe_query_1.as_str()).await?; // $ sql-sink
103103
if enable_remote {
104-
let _ = conn.execute(unsafe_query_1.as_str()).await?; // $ MISSING Alert[sql-injection]=remote2
104+
let _ = conn.execute(unsafe_query_1.as_str()).await?; // $ sql-sink MISSING: Alert[sql-injection]=remote2
105105
}
106106
// ...
107-
let _ = sqlx::raw_sql(safe_query_1.as_str()).execute(&mut conn).await?;
107+
let _ = sqlx::raw_sql(safe_query_1.as_str()).execute(&mut conn).await?; // $ sql-sink
108108
if enable_remote {
109-
let _ = sqlx::raw_sql(unsafe_query_1.as_str()).execute(&mut conn).await?; // $ MISSING Alert[sql-injection]=remote2
109+
let _ = sqlx::raw_sql(unsafe_query_1.as_str()).execute(&mut conn).await?; // $ sql-sink MISSING: Alert[sql-injection]=remote2
110110
}
111111

112112
// prepared queries (with extra variants)
113-
let _ = sqlx::query(safe_query_1.as_str()).execute(&mut conn).await?;
114-
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).execute(&mut conn).await?;
113+
let _ = sqlx::query(safe_query_1.as_str()).execute(&mut conn).await?; // $ sql-sink
114+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).execute(&mut conn).await?; // $ sql-sink
115115
if enable_remote {
116-
let _ = sqlx::query(unsafe_query_1.as_str()).execute(&mut conn).await?; // $ MISSING Alert[sql-injection]=remote2
117-
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).execute(&mut conn).await?;
116+
let _ = sqlx::query(unsafe_query_1.as_str()).execute(&mut conn).await?; // $ sql-sink MISSING: Alert[sql-injection]=remote2
117+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).execute(&mut conn).await?; // $ sql-sink
118118
}
119119
// ...
120-
let _ = sqlx::query(safe_query_1.as_str()).fetch(&mut conn);
121-
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).fetch(&mut conn);
120+
let _ = sqlx::query(safe_query_1.as_str()).fetch(&mut conn); // $ sql-sink
121+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).fetch(&mut conn); // $ sql-sink
122122
if enable_remote {
123-
let _ = sqlx::query(unsafe_query_1.as_str()).fetch(&mut conn); // $ MISSING Alert[sql-injection]=remote2
124-
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).fetch(&mut conn);
123+
let _ = sqlx::query(unsafe_query_1.as_str()).fetch(&mut conn); // $ sql-sink MISSING: Alert[sql-injection]=remote2
124+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).fetch(&mut conn); // $ sql-sink
125125
}
126126
// ...
127-
let row1: (i64, String, String) = sqlx::query_as(safe_query_1.as_str()).fetch_one(&mut conn).await?;
127+
let row1: (i64, String, String) = sqlx::query_as(safe_query_1.as_str()).fetch_one(&mut conn).await?; // $ sql-sink
128128
println!(" row1 = {:?}", row1);
129-
let row2: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&const_string).fetch_one(&mut conn).await?;
129+
let row2: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&const_string).fetch_one(&mut conn).await?; // $ sql-sink
130130
println!(" row2 = {:?}", row2);
131131
if enable_remote {
132-
let _: (i64, String, String) = sqlx::query_as(unsafe_query_1.as_str()).fetch_one(&mut conn).await?; // $ MISSING Alert[sql-injection]=remote2
133-
let _: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&remote_string).fetch_one(&mut conn).await?;
132+
let _: (i64, String, String) = sqlx::query_as(unsafe_query_1.as_str()).fetch_one(&mut conn).await?; // $ sql-sink MISSING: Alert[sql-injection]=remote2
133+
let _: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&remote_string).fetch_one(&mut conn).await?; // $ sql-sink
134134
}
135135
// ...
136-
let row3: (i64, String, String) = sqlx::query_as(safe_query_1.as_str()).fetch_optional(&mut conn).await?.expect("no data");
136+
let row3: (i64, String, String) = sqlx::query_as(safe_query_1.as_str()).fetch_optional(&mut conn).await?.expect("no data"); // $ sql-sink
137137
println!(" row3 = {:?}", row3);
138-
let row4: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&const_string).fetch_optional(&mut conn).await?.expect("no data");
138+
let row4: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&const_string).fetch_optional(&mut conn).await?.expect("no data"); // $ sql-sink
139139
println!(" row4 = {:?}", row4);
140140
if enable_remote {
141-
let _: (i64, String, String) = sqlx::query_as(unsafe_query_1.as_str()).fetch_optional(&mut conn).await?.expect("no data"); // $ MISSING Alert[sql-injection]=remote2
142-
let _: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&remote_string).fetch_optional(&mut conn).await?.expect("no data");
141+
let _: (i64, String, String) = sqlx::query_as(unsafe_query_1.as_str()).fetch_optional(&mut conn).await?.expect("no data"); // $ sql-sink $ MISSING: Alert[sql-injection]=remote2
142+
let _: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&remote_string).fetch_optional(&mut conn).await?.expect("no data"); // $ sql-sink
143143
}
144144
// ...
145-
let _ = sqlx::query(safe_query_1.as_str()).fetch_all(&mut conn).await?;
146-
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).fetch_all(&mut conn).await?;
147-
let _ = sqlx::query("SELECT * FROM people WHERE firstname=?").bind(&const_string).fetch_all(&mut conn).await?;
145+
let _ = sqlx::query(safe_query_1.as_str()).fetch_all(&mut conn).await?; // $ sql-sink
146+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).fetch_all(&mut conn).await?; // $ sql-sink
147+
let _ = sqlx::query("SELECT * FROM people WHERE firstname=?").bind(&const_string).fetch_all(&mut conn).await?; // $ sql-sink
148148
if enable_remote {
149-
let _ = sqlx::query(unsafe_query_1.as_str()).fetch_all(&mut conn).await?; // $ MISSING Alert[sql-injection]=remote2
150-
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).fetch_all(&mut conn).await?;
151-
let _ = sqlx::query("SELECT * FROM people WHERE firstname=?").bind(&remote_string).fetch_all(&mut conn).await?;
149+
let _ = sqlx::query(unsafe_query_1.as_str()).fetch_all(&mut conn).await?; // $ sql-sink MISSING: Alert[sql-injection]=remote2
150+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).fetch_all(&mut conn).await?; // $ sql-sink
151+
let _ = sqlx::query("SELECT * FROM people WHERE firstname=?").bind(&remote_string).fetch_all(&mut conn).await?; // $ sql-sink
152152
}
153153
// ...
154-
let _ = sqlx::query!("SELECT * FROM people WHERE firstname=$1", const_string).fetch_all(&mut conn).await?; // (only takes string literals, so can't be vulnerable)
154+
let _ = sqlx::query!("SELECT * FROM people WHERE firstname=$1", const_string).fetch_all(&mut conn).await?; // $ MISSING: sql-sink (only takes string literals, so can't be vulnerable)
155155
if enable_remote {
156-
let _ = sqlx::query!("SELECT * FROM people WHERE firstname=$1", remote_string).fetch_all(&mut conn).await?;
156+
let _ = sqlx::query!("SELECT * FROM people WHERE firstname=$1", remote_string).fetch_all(&mut conn).await?; // $ MISSING: sql-sink
157157
}
158158

159159
Ok(())
@@ -166,23 +166,23 @@ async fn test_sqlx_postgres(url: &str, enable_remote: bool) -> Result<(), sqlx::
166166

167167
// construct queries
168168
let const_string = String::from("Alice");
169-
let remote_string = reqwest::blocking::get("http://example.com/").unwrap().text().unwrap_or(String::from("Alice")); // $ MISSING Source=remote3
169+
let remote_string = reqwest::blocking::get("http://example.com/").unwrap().text().unwrap_or(String::from("Alice")); // $ MISSING: Source=remote3
170170
let safe_query_1 = String::from("SELECT * FROM people WHERE firstname='") + &const_string + "'";
171171
let unsafe_query_1 = String::from("SELECT * FROM people WHERE firstname='") + &remote_string + "'";
172172
let prepared_query_1 = String::from("SELECT * FROM people WHERE firstname=$1"); // (prepared arguments are safe)
173173

174174
// direct execution
175-
let _ = conn.execute(safe_query_1.as_str()).await?;
175+
let _ = conn.execute(safe_query_1.as_str()).await?; // $ sql-sink
176176
if enable_remote {
177-
let _ = conn.execute(unsafe_query_1.as_str()).await?; // $ MISSING Alert[sql-injection]=remote3
177+
let _ = conn.execute(unsafe_query_1.as_str()).await?; // $ sql-sink MISSING: Alert[sql-injection]=remote3
178178
}
179179

180180
// prepared queries
181-
let _ = sqlx::query(safe_query_1.as_str()).execute(&pool).await?;
182-
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).execute(&pool).await?;
181+
let _ = sqlx::query(safe_query_1.as_str()).execute(&pool).await?; // $ sql-sink
182+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).execute(&pool).await?; // $ sql-sink
183183
if enable_remote {
184-
let _ = sqlx::query(unsafe_query_1.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=remote3
185-
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).execute(&pool).await?;
184+
let _ = sqlx::query(unsafe_query_1.as_str()).execute(&pool).await?; // $ sql-sink MISSING: Alert[sql-injection]=remote3
185+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).execute(&pool).await?; // $ sql-sink
186186
}
187187

188188
Ok(())

0 commit comments

Comments
 (0)