Skip to content

Commit a3110a9

Browse files
committed
Rust: Implement query.
1 parent 5c64d4e commit a3110a9

File tree

5 files changed

+196
-8
lines changed

5 files changed

+196
-8
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/rust-all
4+
extensible: sinkModel
5+
data:
6+
- ["sqlx_core::query::query", "Argument[0]", "database-store", "manual"]
7+
- ["sqlx_core::query_as::query_as", "Argument[0]", "database-store", "manual"]
8+
- ["sqlx_core::query_with::query_with", "Argument[0]", "database-store", "manual"]
9+
- ["sqlx_core::query_as_with::query_as_with", "Argument[0]", "database-store", "manual"]
10+
- ["sqlx_core::query_scalar::query_scalar", "Argument[0]", "database-store", "manual"]
11+
- ["sqlx_core::query_scalar_with::query_scalar_with", "Argument[0]", "database-store", "manual"]
12+
- ["sqlx_core::raw_sql::raw_sql", "Argument[0]", "database-store", "manual"]
13+
- ["<_ as sqlx_core::executor::Executor>::execute", "Argument[0]", "database-store", "manual"]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Provides classes and predicates for reasoning about cleartext storage
3+
* of sensitive information in a database.
4+
*/
5+
6+
import rust
7+
private import codeql.rust.dataflow.DataFlow
8+
private import codeql.rust.dataflow.internal.DataFlowImpl
9+
private import codeql.rust.security.SensitiveData
10+
private import codeql.rust.Concepts
11+
12+
/**
13+
* Provides default sources, sinks and barriers for detecting cleartext storage
14+
* of sensitive information in a database, as well as extension points for
15+
* adding your own.
16+
*/
17+
module CleartextStorageDatabase {
18+
/**
19+
* A data flow source for cleartext storage vulnerabilities.
20+
*/
21+
abstract class Source extends DataFlow::Node { }
22+
23+
/**
24+
* A data flow sink for cleartext storage vulnerabilities.
25+
*/
26+
abstract class Sink extends QuerySink::Range {
27+
override string getSinkType() { result = "CleartextStorageDatabase" }
28+
}
29+
30+
/**
31+
* A barrier for cleartext storage vulnerabilities.
32+
*/
33+
abstract class Barrier extends DataFlow::Node { }
34+
35+
/**
36+
* Sensitive data, considered as a flow source.
37+
*/
38+
private class SensitiveDataAsSource extends Source instanceof SensitiveData { }
39+
40+
/**
41+
* A sink for cleartext storage vulnerabilities from model data.
42+
*/
43+
private class ModelsAsDataSink extends Sink {
44+
ModelsAsDataSink() { exists(string s | sinkNode(this, s) and s.matches("database-store")) }
45+
}
46+
}

rust/ql/src/queries/security/CWE-312/CleartextStorageDatabase.ql

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,44 @@
1212
*/
1313

1414
import rust
15+
import codeql.rust.dataflow.DataFlow
16+
import codeql.rust.dataflow.TaintTracking
17+
import codeql.rust.security.CleartextStorageDatabaseExtensions
1518

16-
select 0
19+
/**
20+
* A taint configuration from sensitive information to expressions that are
21+
* stored in a database.
22+
*/
23+
module CleartextStorageDatabaseConfig implements DataFlow::ConfigSig {
24+
import CleartextStorageDatabase
25+
26+
predicate isSource(DataFlow::Node node) { node instanceof Source }
27+
28+
predicate isSink(DataFlow::Node node) { node instanceof Sink }
29+
30+
predicate isBarrier(DataFlow::Node barrier) { barrier instanceof Barrier }
31+
32+
predicate isBarrierIn(DataFlow::Node node) {
33+
// make sources barriers so that we only report the closest instance
34+
isSource(node)
35+
}
36+
37+
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
38+
// flow from `a` to `&a`
39+
node2.asExpr().getExpr().(RefExpr).getExpr() = node1.asExpr().getExpr()
40+
}
41+
42+
predicate observeDiffInformedIncrementalMode() { any() }
43+
}
44+
45+
module CleartextStorageDatabaseFlow = TaintTracking::Global<CleartextStorageDatabaseConfig>;
46+
47+
import CleartextStorageDatabaseFlow::PathGraph
48+
49+
from
50+
CleartextStorageDatabaseFlow::PathNode sourceNode, CleartextStorageDatabaseFlow::PathNode sinkNode
51+
where CleartextStorageDatabaseFlow::flowPath(sourceNode, sinkNode)
52+
select sinkNode, sourceNode, sinkNode,
53+
"This operation stores '" + sinkNode.toString() +
54+
"' in a database. It may contain unencrypted sensitive data from $@.", sourceNode,
55+
sourceNode.getNode().toString()
Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,91 @@
1-
| 0 |
1+
#select
2+
| test_storage.rs:62:13:62:23 | ...::query | test_storage.rs:33:97:33:114 | get_phone_number(...) | test_storage.rs:62:13:62:23 | ...::query | This operation stores '...::query' in a database. It may contain unencrypted sensitive data from $@. | test_storage.rs:33:97:33:114 | get_phone_number(...) | get_phone_number(...) |
3+
| test_storage.rs:77:13:77:25 | ...::raw_sql | test_storage.rs:33:97:33:114 | get_phone_number(...) | test_storage.rs:77:13:77:25 | ...::raw_sql | This operation stores '...::raw_sql' in a database. It may contain unencrypted sensitive data from $@. | test_storage.rs:33:97:33:114 | get_phone_number(...) | get_phone_number(...) |
4+
| test_storage.rs:81:13:81:23 | ...::query | test_storage.rs:33:97:33:114 | get_phone_number(...) | test_storage.rs:81:13:81:23 | ...::query | This operation stores '...::query' in a database. It may contain unencrypted sensitive data from $@. | test_storage.rs:33:97:33:114 | get_phone_number(...) | get_phone_number(...) |
5+
| test_storage.rs:87:13:87:23 | ...::query | test_storage.rs:33:97:33:114 | get_phone_number(...) | test_storage.rs:87:13:87:23 | ...::query | This operation stores '...::query' in a database. It may contain unencrypted sensitive data from $@. | test_storage.rs:33:97:33:114 | get_phone_number(...) | get_phone_number(...) |
6+
| test_storage.rs:101:13:101:23 | ...::query | test_storage.rs:33:97:33:114 | get_phone_number(...) | test_storage.rs:101:13:101:23 | ...::query | This operation stores '...::query' in a database. It may contain unencrypted sensitive data from $@. | test_storage.rs:33:97:33:114 | get_phone_number(...) | get_phone_number(...) |
7+
edges
8+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:62:25:62:37 | insert_query2 | provenance | |
9+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:62:25:62:46 | insert_query2.as_str() | provenance | MaD:5 |
10+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:62:25:62:46 | insert_query2.as_str() | provenance | MaD:4 |
11+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:62:25:62:46 | insert_query2.as_str() | provenance | MaD:5 |
12+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:77:27:77:39 | insert_query2 | provenance | |
13+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:77:27:77:48 | insert_query2.as_str() | provenance | MaD:5 |
14+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:77:27:77:48 | insert_query2.as_str() | provenance | MaD:4 |
15+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:77:27:77:48 | insert_query2.as_str() | provenance | MaD:5 |
16+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:81:25:81:37 | insert_query2 | provenance | |
17+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:81:25:81:46 | insert_query2.as_str() | provenance | MaD:5 |
18+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:81:25:81:46 | insert_query2.as_str() | provenance | MaD:4 |
19+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:81:25:81:46 | insert_query2.as_str() | provenance | MaD:5 |
20+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:87:25:87:37 | insert_query2 | provenance | |
21+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:87:25:87:46 | insert_query2.as_str() | provenance | MaD:5 |
22+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:87:25:87:46 | insert_query2.as_str() | provenance | MaD:4 |
23+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:87:25:87:46 | insert_query2.as_str() | provenance | MaD:5 |
24+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:101:25:101:37 | insert_query2 | provenance | |
25+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:101:25:101:46 | insert_query2.as_str() | provenance | MaD:5 |
26+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:101:25:101:46 | insert_query2.as_str() | provenance | MaD:4 |
27+
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:101:25:101:46 | insert_query2.as_str() | provenance | MaD:5 |
28+
| test_storage.rs:33:25:33:114 | ... + ... | test_storage.rs:33:9:33:21 | insert_query2 | provenance | |
29+
| test_storage.rs:33:25:33:114 | ... + ... | test_storage.rs:33:25:33:121 | ... + ... | provenance | MaD:3 |
30+
| test_storage.rs:33:25:33:121 | ... + ... | test_storage.rs:33:9:33:21 | insert_query2 | provenance | |
31+
| test_storage.rs:33:96:33:114 | &... | test_storage.rs:33:9:33:21 | insert_query2 | provenance | |
32+
| test_storage.rs:33:96:33:114 | &... | test_storage.rs:33:25:33:114 | ... + ... | provenance | |
33+
| test_storage.rs:33:97:33:114 | get_phone_number(...) | test_storage.rs:33:96:33:114 | &... | provenance | Config |
34+
| test_storage.rs:62:25:62:37 | insert_query2 | test_storage.rs:62:25:62:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
35+
| test_storage.rs:62:25:62:37 | insert_query2 | test_storage.rs:62:25:62:46 | insert_query2.as_str() [&ref] | provenance | MaD:4 |
36+
| test_storage.rs:62:25:62:37 | insert_query2 | test_storage.rs:62:25:62:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
37+
| test_storage.rs:62:25:62:46 | insert_query2.as_str() | test_storage.rs:62:13:62:23 | ...::query | provenance | MaD:1 Sink:MaD:1 |
38+
| test_storage.rs:62:25:62:46 | insert_query2.as_str() [&ref] | test_storage.rs:62:13:62:23 | ...::query | provenance | MaD:1 Sink:MaD:1 |
39+
| test_storage.rs:77:27:77:39 | insert_query2 | test_storage.rs:77:27:77:48 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
40+
| test_storage.rs:77:27:77:39 | insert_query2 | test_storage.rs:77:27:77:48 | insert_query2.as_str() [&ref] | provenance | MaD:4 |
41+
| test_storage.rs:77:27:77:39 | insert_query2 | test_storage.rs:77:27:77:48 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
42+
| test_storage.rs:77:27:77:48 | insert_query2.as_str() | test_storage.rs:77:13:77:25 | ...::raw_sql | provenance | MaD:2 Sink:MaD:2 |
43+
| test_storage.rs:77:27:77:48 | insert_query2.as_str() [&ref] | test_storage.rs:77:13:77:25 | ...::raw_sql | provenance | MaD:2 Sink:MaD:2 |
44+
| test_storage.rs:81:25:81:37 | insert_query2 | test_storage.rs:81:25:81:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
45+
| test_storage.rs:81:25:81:37 | insert_query2 | test_storage.rs:81:25:81:46 | insert_query2.as_str() [&ref] | provenance | MaD:4 |
46+
| test_storage.rs:81:25:81:37 | insert_query2 | test_storage.rs:81:25:81:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
47+
| test_storage.rs:81:25:81:46 | insert_query2.as_str() | test_storage.rs:81:13:81:23 | ...::query | provenance | MaD:1 Sink:MaD:1 |
48+
| test_storage.rs:81:25:81:46 | insert_query2.as_str() [&ref] | test_storage.rs:81:13:81:23 | ...::query | provenance | MaD:1 Sink:MaD:1 |
49+
| test_storage.rs:87:25:87:37 | insert_query2 | test_storage.rs:87:25:87:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
50+
| test_storage.rs:87:25:87:37 | insert_query2 | test_storage.rs:87:25:87:46 | insert_query2.as_str() [&ref] | provenance | MaD:4 |
51+
| test_storage.rs:87:25:87:37 | insert_query2 | test_storage.rs:87:25:87:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
52+
| test_storage.rs:87:25:87:46 | insert_query2.as_str() | test_storage.rs:87:13:87:23 | ...::query | provenance | MaD:1 Sink:MaD:1 |
53+
| test_storage.rs:87:25:87:46 | insert_query2.as_str() [&ref] | test_storage.rs:87:13:87:23 | ...::query | provenance | MaD:1 Sink:MaD:1 |
54+
| test_storage.rs:101:25:101:37 | insert_query2 | test_storage.rs:101:25:101:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
55+
| test_storage.rs:101:25:101:37 | insert_query2 | test_storage.rs:101:25:101:46 | insert_query2.as_str() [&ref] | provenance | MaD:4 |
56+
| test_storage.rs:101:25:101:37 | insert_query2 | test_storage.rs:101:25:101:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
57+
| test_storage.rs:101:25:101:46 | insert_query2.as_str() | test_storage.rs:101:13:101:23 | ...::query | provenance | MaD:1 Sink:MaD:1 |
58+
| test_storage.rs:101:25:101:46 | insert_query2.as_str() [&ref] | test_storage.rs:101:13:101:23 | ...::query | provenance | MaD:1 Sink:MaD:1 |
59+
models
60+
| 1 | Sink: sqlx_core::query::query; Argument[0]; database-store |
61+
| 2 | Sink: sqlx_core::raw_sql::raw_sql; Argument[0]; database-store |
62+
| 3 | Summary: <alloc::string::String as core::ops::arith::Add>::add; Argument[self]; ReturnValue; value |
63+
| 4 | Summary: <alloc::string::String>::as_str; Argument[self]; ReturnValue; value |
64+
| 5 | Summary: <core::str>::as_str; Argument[self]; ReturnValue; value |
65+
nodes
66+
| test_storage.rs:33:9:33:21 | insert_query2 | semmle.label | insert_query2 |
67+
| test_storage.rs:33:25:33:114 | ... + ... | semmle.label | ... + ... |
68+
| test_storage.rs:33:25:33:121 | ... + ... | semmle.label | ... + ... |
69+
| test_storage.rs:33:96:33:114 | &... | semmle.label | &... |
70+
| test_storage.rs:33:97:33:114 | get_phone_number(...) | semmle.label | get_phone_number(...) |
71+
| test_storage.rs:62:13:62:23 | ...::query | semmle.label | ...::query |
72+
| test_storage.rs:62:25:62:37 | insert_query2 | semmle.label | insert_query2 |
73+
| test_storage.rs:62:25:62:46 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
74+
| test_storage.rs:62:25:62:46 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
75+
| test_storage.rs:77:13:77:25 | ...::raw_sql | semmle.label | ...::raw_sql |
76+
| test_storage.rs:77:27:77:39 | insert_query2 | semmle.label | insert_query2 |
77+
| test_storage.rs:77:27:77:48 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
78+
| test_storage.rs:77:27:77:48 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
79+
| test_storage.rs:81:13:81:23 | ...::query | semmle.label | ...::query |
80+
| test_storage.rs:81:25:81:37 | insert_query2 | semmle.label | insert_query2 |
81+
| test_storage.rs:81:25:81:46 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
82+
| test_storage.rs:81:25:81:46 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
83+
| test_storage.rs:87:13:87:23 | ...::query | semmle.label | ...::query |
84+
| test_storage.rs:87:25:87:37 | insert_query2 | semmle.label | insert_query2 |
85+
| test_storage.rs:87:25:87:46 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
86+
| test_storage.rs:87:25:87:46 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
87+
| test_storage.rs:101:13:101:23 | ...::query | semmle.label | ...::query |
88+
| test_storage.rs:101:25:101:37 | insert_query2 | semmle.label | insert_query2 |
89+
| test_storage.rs:101:25:101:46 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
90+
| test_storage.rs:101:25:101:46 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
91+
subpaths

rust/ql/test/query-tests/security/CWE-312/test_storage.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ async fn test_storage_sql_command(url: &str) -> Result<(), sqlx::Error> {
3030
let select_query1 = String::from("SELECT * FROM CONTACTS WHERE ID = ") + id;
3131
let select_query2 = String::from("SELECT * FROM CONTACTS WHERE SSN = '") + &get_social_security_number() + "'";
3232
let insert_query1 = String::from("INSERT INTO CONTACTS(ID, HARMLESS) VALUES(") + id + ", '" + &get_harmless() + "')";
33-
let insert_query2 = String::from("INSERT INTO CONTACTS(ID, PHONE) VALUES(") + id + ", '" + &get_phone_number() + "')";
33+
let insert_query2 = String::from("INSERT INTO CONTACTS(ID, PHONE) VALUES(") + id + ", '" + &get_phone_number() + "')"; // $ Source[rust/cleartext-storage-database]
3434
let update_query1 = String::from("UPDATE CONTACTS SET HARMLESS='") + &get_harmless() + "' WHERE ID=" + id;
3535
let update_query2 = String::from("UPDATE CONTACTS SET EMAIL='") + &get_email() + "' WHERE ID=" + id;
3636
let s1 = &get_social_security_number();
@@ -59,7 +59,7 @@ async fn test_storage_sql_command(url: &str) -> Result<(), sqlx::Error> {
5959

6060
// execute queries - MySQL, prepared query
6161
let _ = sqlx::query(insert_query1.as_str()).execute(&pool1).await?;
62-
let _ = sqlx::query(insert_query2.as_str()).execute(&pool1).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
62+
let _ = sqlx::query(insert_query2.as_str()).execute(&pool1).await?; // $ Alert[rust/cleartext-storage-database]
6363
let _ = sqlx::query(prepared_query.as_str()).bind(get_harmless()).execute(&pool1).await?;
6464
let _ = sqlx::query(prepared_query.as_str()).bind(get_social_security_number()).execute(&pool1).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
6565
let _ = sqlx::query(prepared_query.as_str()).bind(&s1).execute(&pool1).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
@@ -74,17 +74,17 @@ async fn test_storage_sql_command(url: &str) -> Result<(), sqlx::Error> {
7474

7575
// execute queries - SQLite, direct variant
7676
let _ = sqlx::raw_sql(insert_query1.as_str()).execute(&mut conn2).await?;
77-
let _ = sqlx::raw_sql(insert_query2.as_str()).execute(&mut conn2).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
77+
let _ = sqlx::raw_sql(insert_query2.as_str()).execute(&mut conn2).await?; // $ Alert[rust/cleartext-storage-database]
7878

7979
// execute queries - SQLite, prepared query
8080
let _ = sqlx::query(insert_query1.as_str()).execute(&mut conn2).await?;
81-
let _ = sqlx::query(insert_query2.as_str()).execute(&mut conn2).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
81+
let _ = sqlx::query(insert_query2.as_str()).execute(&mut conn2).await?; // $ Alert[rust/cleartext-storage-database]
8282
let _ = sqlx::query(prepared_query.as_str()).bind(get_harmless()).execute(&mut conn2).await?;
8383
let _ = sqlx::query(prepared_query.as_str()).bind(get_social_security_number()).execute(&mut conn2).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
8484

8585
// execute queries - SQLite, prepared query variant
8686
let _ = sqlx::query(insert_query1.as_str()).fetch(&mut conn2);
87-
let _ = sqlx::query(insert_query2.as_str()).fetch(&mut conn2); // $ MISSING: Alert[rust/cleartext-storage-database]
87+
let _ = sqlx::query(insert_query2.as_str()).fetch(&mut conn2); // $ Alert[rust/cleartext-storage-database]
8888
let _ = sqlx::query(prepared_query.as_str()).bind(get_harmless()).fetch(&mut conn2);
8989
let _ = sqlx::query(prepared_query.as_str()).bind(get_social_security_number()).fetch(&mut conn2); // $ MISSING: Alert[rust/cleartext-storage-database]
9090

@@ -98,7 +98,7 @@ async fn test_storage_sql_command(url: &str) -> Result<(), sqlx::Error> {
9898

9999
// execute queries - PostgreSQL, prepared query
100100
let _ = sqlx::query(insert_query1.as_str()).execute(&pool3).await?;
101-
let _ = sqlx::query(insert_query2.as_str()).execute(&pool3).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
101+
let _ = sqlx::query(insert_query2.as_str()).execute(&pool3).await?; // $ Alert[rust/cleartext-storage-database]
102102
let _ = sqlx::query(prepared_query.as_str()).bind(get_harmless()).execute(&pool3).await?;
103103
let _ = sqlx::query(prepared_query.as_str()).bind(get_social_security_number()).execute(&pool3).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
104104

0 commit comments

Comments
 (0)