Rust: Implement query.

This commit is contained in:
Geoffrey White
2025-06-26 10:20:49 +01:00
parent 5c64d4e9b7
commit a3110a9091
5 changed files with 196 additions and 8 deletions

View File

@@ -0,0 +1,13 @@
extensions:
- addsTo:
pack: codeql/rust-all
extensible: sinkModel
data:
- ["sqlx_core::query::query", "Argument[0]", "database-store", "manual"]
- ["sqlx_core::query_as::query_as", "Argument[0]", "database-store", "manual"]
- ["sqlx_core::query_with::query_with", "Argument[0]", "database-store", "manual"]
- ["sqlx_core::query_as_with::query_as_with", "Argument[0]", "database-store", "manual"]
- ["sqlx_core::query_scalar::query_scalar", "Argument[0]", "database-store", "manual"]
- ["sqlx_core::query_scalar_with::query_scalar_with", "Argument[0]", "database-store", "manual"]
- ["sqlx_core::raw_sql::raw_sql", "Argument[0]", "database-store", "manual"]
- ["<_ as sqlx_core::executor::Executor>::execute", "Argument[0]", "database-store", "manual"]

View File

@@ -0,0 +1,46 @@
/**
* Provides classes and predicates for reasoning about cleartext storage
* of sensitive information in a database.
*/
import rust
private import codeql.rust.dataflow.DataFlow
private import codeql.rust.dataflow.internal.DataFlowImpl
private import codeql.rust.security.SensitiveData
private import codeql.rust.Concepts
/**
* Provides default sources, sinks and barriers for detecting cleartext storage
* of sensitive information in a database, as well as extension points for
* adding your own.
*/
module CleartextStorageDatabase {
/**
* A data flow source for cleartext storage vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for cleartext storage vulnerabilities.
*/
abstract class Sink extends QuerySink::Range {
override string getSinkType() { result = "CleartextStorageDatabase" }
}
/**
* A barrier for cleartext storage vulnerabilities.
*/
abstract class Barrier extends DataFlow::Node { }
/**
* Sensitive data, considered as a flow source.
*/
private class SensitiveDataAsSource extends Source instanceof SensitiveData { }
/**
* A sink for cleartext storage vulnerabilities from model data.
*/
private class ModelsAsDataSink extends Sink {
ModelsAsDataSink() { exists(string s | sinkNode(this, s) and s.matches("database-store")) }
}
}

View File

@@ -12,5 +12,44 @@
*/
import rust
import codeql.rust.dataflow.DataFlow
import codeql.rust.dataflow.TaintTracking
import codeql.rust.security.CleartextStorageDatabaseExtensions
select 0
/**
* A taint configuration from sensitive information to expressions that are
* stored in a database.
*/
module CleartextStorageDatabaseConfig implements DataFlow::ConfigSig {
import CleartextStorageDatabase
predicate isSource(DataFlow::Node node) { node instanceof Source }
predicate isSink(DataFlow::Node node) { node instanceof Sink }
predicate isBarrier(DataFlow::Node barrier) { barrier instanceof Barrier }
predicate isBarrierIn(DataFlow::Node node) {
// make sources barriers so that we only report the closest instance
isSource(node)
}
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
// flow from `a` to `&a`
node2.asExpr().getExpr().(RefExpr).getExpr() = node1.asExpr().getExpr()
}
predicate observeDiffInformedIncrementalMode() { any() }
}
module CleartextStorageDatabaseFlow = TaintTracking::Global<CleartextStorageDatabaseConfig>;
import CleartextStorageDatabaseFlow::PathGraph
from
CleartextStorageDatabaseFlow::PathNode sourceNode, CleartextStorageDatabaseFlow::PathNode sinkNode
where CleartextStorageDatabaseFlow::flowPath(sourceNode, sinkNode)
select sinkNode, sourceNode, sinkNode,
"This operation stores '" + sinkNode.toString() +
"' in a database. It may contain unencrypted sensitive data from $@.", sourceNode,
sourceNode.getNode().toString()

View File

@@ -1 +1,91 @@
| 0 |
#select
| 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(...) |
| 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(...) |
| 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(...) |
| 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(...) |
| 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(...) |
edges
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:62:25:62:37 | insert_query2 | provenance | |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:62:25:62:46 | insert_query2.as_str() | provenance | MaD:5 |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:62:25:62:46 | insert_query2.as_str() | provenance | MaD:4 |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:62:25:62:46 | insert_query2.as_str() | provenance | MaD:5 |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:77:27:77:39 | insert_query2 | provenance | |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:77:27:77:48 | insert_query2.as_str() | provenance | MaD:5 |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:77:27:77:48 | insert_query2.as_str() | provenance | MaD:4 |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:77:27:77:48 | insert_query2.as_str() | provenance | MaD:5 |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:81:25:81:37 | insert_query2 | provenance | |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:81:25:81:46 | insert_query2.as_str() | provenance | MaD:5 |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:81:25:81:46 | insert_query2.as_str() | provenance | MaD:4 |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:81:25:81:46 | insert_query2.as_str() | provenance | MaD:5 |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:87:25:87:37 | insert_query2 | provenance | |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:87:25:87:46 | insert_query2.as_str() | provenance | MaD:5 |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:87:25:87:46 | insert_query2.as_str() | provenance | MaD:4 |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:87:25:87:46 | insert_query2.as_str() | provenance | MaD:5 |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:101:25:101:37 | insert_query2 | provenance | |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:101:25:101:46 | insert_query2.as_str() | provenance | MaD:5 |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:101:25:101:46 | insert_query2.as_str() | provenance | MaD:4 |
| test_storage.rs:33:9:33:21 | insert_query2 | test_storage.rs:101:25:101:46 | insert_query2.as_str() | provenance | MaD:5 |
| test_storage.rs:33:25:33:114 | ... + ... | test_storage.rs:33:9:33:21 | insert_query2 | provenance | |
| test_storage.rs:33:25:33:114 | ... + ... | test_storage.rs:33:25:33:121 | ... + ... | provenance | MaD:3 |
| test_storage.rs:33:25:33:121 | ... + ... | test_storage.rs:33:9:33:21 | insert_query2 | provenance | |
| test_storage.rs:33:96:33:114 | &... | test_storage.rs:33:9:33:21 | insert_query2 | provenance | |
| test_storage.rs:33:96:33:114 | &... | test_storage.rs:33:25:33:114 | ... + ... | provenance | |
| test_storage.rs:33:97:33:114 | get_phone_number(...) | test_storage.rs:33:96:33:114 | &... | provenance | Config |
| test_storage.rs:62:25:62:37 | insert_query2 | test_storage.rs:62:25:62:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
| test_storage.rs:62:25:62:37 | insert_query2 | test_storage.rs:62:25:62:46 | insert_query2.as_str() [&ref] | provenance | MaD:4 |
| test_storage.rs:62:25:62:37 | insert_query2 | test_storage.rs:62:25:62:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
| 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 |
| 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 |
| test_storage.rs:77:27:77:39 | insert_query2 | test_storage.rs:77:27:77:48 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
| test_storage.rs:77:27:77:39 | insert_query2 | test_storage.rs:77:27:77:48 | insert_query2.as_str() [&ref] | provenance | MaD:4 |
| test_storage.rs:77:27:77:39 | insert_query2 | test_storage.rs:77:27:77:48 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
| 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 |
| 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 |
| test_storage.rs:81:25:81:37 | insert_query2 | test_storage.rs:81:25:81:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
| test_storage.rs:81:25:81:37 | insert_query2 | test_storage.rs:81:25:81:46 | insert_query2.as_str() [&ref] | provenance | MaD:4 |
| test_storage.rs:81:25:81:37 | insert_query2 | test_storage.rs:81:25:81:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
| 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 |
| 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 |
| test_storage.rs:87:25:87:37 | insert_query2 | test_storage.rs:87:25:87:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
| test_storage.rs:87:25:87:37 | insert_query2 | test_storage.rs:87:25:87:46 | insert_query2.as_str() [&ref] | provenance | MaD:4 |
| test_storage.rs:87:25:87:37 | insert_query2 | test_storage.rs:87:25:87:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
| 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 |
| 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 |
| test_storage.rs:101:25:101:37 | insert_query2 | test_storage.rs:101:25:101:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
| test_storage.rs:101:25:101:37 | insert_query2 | test_storage.rs:101:25:101:46 | insert_query2.as_str() [&ref] | provenance | MaD:4 |
| test_storage.rs:101:25:101:37 | insert_query2 | test_storage.rs:101:25:101:46 | insert_query2.as_str() [&ref] | provenance | MaD:5 |
| 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 |
| 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 |
models
| 1 | Sink: sqlx_core::query::query; Argument[0]; database-store |
| 2 | Sink: sqlx_core::raw_sql::raw_sql; Argument[0]; database-store |
| 3 | Summary: <alloc::string::String as core::ops::arith::Add>::add; Argument[self]; ReturnValue; value |
| 4 | Summary: <alloc::string::String>::as_str; Argument[self]; ReturnValue; value |
| 5 | Summary: <core::str>::as_str; Argument[self]; ReturnValue; value |
nodes
| test_storage.rs:33:9:33:21 | insert_query2 | semmle.label | insert_query2 |
| test_storage.rs:33:25:33:114 | ... + ... | semmle.label | ... + ... |
| test_storage.rs:33:25:33:121 | ... + ... | semmle.label | ... + ... |
| test_storage.rs:33:96:33:114 | &... | semmle.label | &... |
| test_storage.rs:33:97:33:114 | get_phone_number(...) | semmle.label | get_phone_number(...) |
| test_storage.rs:62:13:62:23 | ...::query | semmle.label | ...::query |
| test_storage.rs:62:25:62:37 | insert_query2 | semmle.label | insert_query2 |
| test_storage.rs:62:25:62:46 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
| test_storage.rs:62:25:62:46 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
| test_storage.rs:77:13:77:25 | ...::raw_sql | semmle.label | ...::raw_sql |
| test_storage.rs:77:27:77:39 | insert_query2 | semmle.label | insert_query2 |
| test_storage.rs:77:27:77:48 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
| test_storage.rs:77:27:77:48 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
| test_storage.rs:81:13:81:23 | ...::query | semmle.label | ...::query |
| test_storage.rs:81:25:81:37 | insert_query2 | semmle.label | insert_query2 |
| test_storage.rs:81:25:81:46 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
| test_storage.rs:81:25:81:46 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
| test_storage.rs:87:13:87:23 | ...::query | semmle.label | ...::query |
| test_storage.rs:87:25:87:37 | insert_query2 | semmle.label | insert_query2 |
| test_storage.rs:87:25:87:46 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
| test_storage.rs:87:25:87:46 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
| test_storage.rs:101:13:101:23 | ...::query | semmle.label | ...::query |
| test_storage.rs:101:25:101:37 | insert_query2 | semmle.label | insert_query2 |
| test_storage.rs:101:25:101:46 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
| test_storage.rs:101:25:101:46 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
subpaths

View File

@@ -30,7 +30,7 @@ async fn test_storage_sql_command(url: &str) -> Result<(), sqlx::Error> {
let select_query1 = String::from("SELECT * FROM CONTACTS WHERE ID = ") + id;
let select_query2 = String::from("SELECT * FROM CONTACTS WHERE SSN = '") + &get_social_security_number() + "'";
let insert_query1 = String::from("INSERT INTO CONTACTS(ID, HARMLESS) VALUES(") + id + ", '" + &get_harmless() + "')";
let insert_query2 = String::from("INSERT INTO CONTACTS(ID, PHONE) VALUES(") + id + ", '" + &get_phone_number() + "')";
let insert_query2 = String::from("INSERT INTO CONTACTS(ID, PHONE) VALUES(") + id + ", '" + &get_phone_number() + "')"; // $ Source[rust/cleartext-storage-database]
let update_query1 = String::from("UPDATE CONTACTS SET HARMLESS='") + &get_harmless() + "' WHERE ID=" + id;
let update_query2 = String::from("UPDATE CONTACTS SET EMAIL='") + &get_email() + "' WHERE ID=" + id;
let s1 = &get_social_security_number();
@@ -59,7 +59,7 @@ async fn test_storage_sql_command(url: &str) -> Result<(), sqlx::Error> {
// execute queries - MySQL, prepared query
let _ = sqlx::query(insert_query1.as_str()).execute(&pool1).await?;
let _ = sqlx::query(insert_query2.as_str()).execute(&pool1).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
let _ = sqlx::query(insert_query2.as_str()).execute(&pool1).await?; // $ Alert[rust/cleartext-storage-database]
let _ = sqlx::query(prepared_query.as_str()).bind(get_harmless()).execute(&pool1).await?;
let _ = sqlx::query(prepared_query.as_str()).bind(get_social_security_number()).execute(&pool1).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
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> {
// execute queries - SQLite, direct variant
let _ = sqlx::raw_sql(insert_query1.as_str()).execute(&mut conn2).await?;
let _ = sqlx::raw_sql(insert_query2.as_str()).execute(&mut conn2).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
let _ = sqlx::raw_sql(insert_query2.as_str()).execute(&mut conn2).await?; // $ Alert[rust/cleartext-storage-database]
// execute queries - SQLite, prepared query
let _ = sqlx::query(insert_query1.as_str()).execute(&mut conn2).await?;
let _ = sqlx::query(insert_query2.as_str()).execute(&mut conn2).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
let _ = sqlx::query(insert_query2.as_str()).execute(&mut conn2).await?; // $ Alert[rust/cleartext-storage-database]
let _ = sqlx::query(prepared_query.as_str()).bind(get_harmless()).execute(&mut conn2).await?;
let _ = sqlx::query(prepared_query.as_str()).bind(get_social_security_number()).execute(&mut conn2).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
// execute queries - SQLite, prepared query variant
let _ = sqlx::query(insert_query1.as_str()).fetch(&mut conn2);
let _ = sqlx::query(insert_query2.as_str()).fetch(&mut conn2); // $ MISSING: Alert[rust/cleartext-storage-database]
let _ = sqlx::query(insert_query2.as_str()).fetch(&mut conn2); // $ Alert[rust/cleartext-storage-database]
let _ = sqlx::query(prepared_query.as_str()).bind(get_harmless()).fetch(&mut conn2);
let _ = sqlx::query(prepared_query.as_str()).bind(get_social_security_number()).fetch(&mut conn2); // $ MISSING: Alert[rust/cleartext-storage-database]
@@ -98,7 +98,7 @@ async fn test_storage_sql_command(url: &str) -> Result<(), sqlx::Error> {
// execute queries - PostgreSQL, prepared query
let _ = sqlx::query(insert_query1.as_str()).execute(&pool3).await?;
let _ = sqlx::query(insert_query2.as_str()).execute(&pool3).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
let _ = sqlx::query(insert_query2.as_str()).execute(&pool3).await?; // $ Alert[rust/cleartext-storage-database]
let _ = sqlx::query(prepared_query.as_str()).bind(get_harmless()).execute(&pool3).await?;
let _ = sqlx::query(prepared_query.as_str()).bind(get_social_security_number()).execute(&pool3).await?; // $ MISSING: Alert[rust/cleartext-storage-database]