Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/codeql/reusables/rust-further-reading.rst
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
- `CodeQL queries for Rust <https://github.com/github/codeql/tree/main/rust/ql/src>`__
- `Example queries for Rust <https://github.com/github/codeql/tree/main/rust/ql/examples>`__
- `CodeQL library reference for Rust <https://codeql.github.com/codeql-standard-libraries/rust/>`__
4 changes: 4 additions & 0 deletions rust/ql/examples/qlpack.lock.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
dependencies: {}
compiled: false
lockVersion: 1.0.0
7 changes: 7 additions & 0 deletions rust/ql/examples/qlpack.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: codeql/rust-examples
groups:
- rust
- examples
dependencies:
codeql/rust-all: ${workspace}
warnOnImplicitThis: true
18 changes: 18 additions & 0 deletions rust/ql/examples/snippets/empty_if.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @name Empty 'if' statement
* @description Finds 'if' statements where the "then" branch is empty and no
* "else" branch exists.
* @id rust/examples/empty-if
* @tags example
*/

import rust

// find 'if' statements...
from IfExpr ifExpr
where
// where the 'then' branch is empty
ifExpr.getThen().(BlockExpr).getStmtList().getNumberOfStmtOrExpr() = 0 and
// and no 'else' branch exists
not exists(ifExpr.getElse())
select ifExpr, "This 'if' expression is redundant."
47 changes: 47 additions & 0 deletions rust/ql/examples/snippets/simple_constant_password.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @name Constant password
* @description Finds places where a string literal is used in a function call
* argument that looks like a password.
* @id rust/examples/simple-constant-password
* @tags example
*/

import rust
import codeql.rust.dataflow.DataFlow
import codeql.rust.dataflow.TaintTracking

/**
* A data flow configuration for tracking flow from a string literal to a function
* call argument that looks like a password. For example:
* ```
* fn set_password(password: &str) { ... }
*
* ...
*
* let pwd = "123456"; // source
* set_password(pwd); // sink (argument 0)
* ```
*/
module ConstantPasswordConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) {
// `node` is a string literal
node.asExpr().getExpr() instanceof StringLiteralExpr
}

predicate isSink(DataFlow::Node node) {
// `node` is an argument whose corresponding parameter name matches the pattern "pass%"
exists(CallExpr call, Function target, int argIndex |
call.getStaticTarget() = target and
target.getParam(argIndex).getPat().(IdentPat).getName().getText().matches("pass%") and
call.getArg(argIndex) = node.asExpr().getExpr()
)
}
}

// instantiate the data flow configuration as a global taint tracking module
module ConstantPasswordFlow = TaintTracking::Global<ConstantPasswordConfig>;

// report flows from sources to sinks
from DataFlow::Node sourceNode, DataFlow::Node sinkNode
where ConstantPasswordFlow::flow(sourceNode, sinkNode)
select sinkNode, "The value $@ is used as a constant password.", sourceNode, sourceNode.toString()
39 changes: 39 additions & 0 deletions rust/ql/examples/snippets/simple_sql_injection.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @name Database query built from user-controlled sources
* @description Finds places where a value from a remote or local user input
* is used as the first argument of a call to `sqlx_core::query::query`.
* @id rust/examples/simple-sql-injection
* @tags example
*/

import rust
import codeql.rust.dataflow.DataFlow
import codeql.rust.dataflow.TaintTracking
import codeql.rust.Concepts

/**
* A data flow configuration for tracking flow from a user input (threat model
* source) to the first argument of a call to `sqlx_core::query::query`.
*/
module SqlInjectionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) {
// `node` is a user input (threat model source)
node instanceof ActiveThreatModelSource
}

predicate isSink(DataFlow::Node node) {
// `node` is the first argument of a call to `sqlx_core::query::query`
exists(CallExpr call |
call.getStaticTarget().getCanonicalPath() = "sqlx_core::query::query" and
call.getArg(0) = node.asExpr().getExpr()
)
}
}

// instantiate the data flow configuration as a global taint tracking module
module SqlInjectionFlow = TaintTracking::Global<SqlInjectionConfig>;

// report flows from sources to sinks
from DataFlow::Node sourceNode, DataFlow::Node sinkNode
where SqlInjectionFlow::flow(sourceNode, sinkNode)
select sinkNode, "This query depends on a $@.", sourceNode, "user-provided value"
4 changes: 4 additions & 0 deletions rust/ql/src/change-notes/2025-11-07-example-queries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: newQuery
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would've thought newQuery meant a new query meant for running by end users. Though I don't know what other category we could pick, so maybe this is the best option?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the conclusion I came to. It felt more appropriate than minorAnalysis. I guess the main impact of this will be which part of the compiled change notes this part will appear in.

I'm not sure though, I'll request a docs review...

---
* Added three example queries (`rust/examples/empty-if`, `rust/examples/simple-sql-injection` and `rust/examples/simple-constant-password`) to help developers learn to write CodeQL queries for Rust.