diff --git a/docs/codeql/reusables/rust-further-reading.rst b/docs/codeql/reusables/rust-further-reading.rst index a82dee7f28e1..c8d18cc77b01 100644 --- a/docs/codeql/reusables/rust-further-reading.rst +++ b/docs/codeql/reusables/rust-further-reading.rst @@ -1,2 +1,3 @@ - `CodeQL queries for Rust `__ +- `Example queries for Rust `__ - `CodeQL library reference for Rust `__ diff --git a/rust/ql/examples/qlpack.lock.yml b/rust/ql/examples/qlpack.lock.yml new file mode 100644 index 000000000000..06dd07fc7dc7 --- /dev/null +++ b/rust/ql/examples/qlpack.lock.yml @@ -0,0 +1,4 @@ +--- +dependencies: {} +compiled: false +lockVersion: 1.0.0 diff --git a/rust/ql/examples/qlpack.yml b/rust/ql/examples/qlpack.yml new file mode 100644 index 000000000000..41adabd2c70e --- /dev/null +++ b/rust/ql/examples/qlpack.yml @@ -0,0 +1,7 @@ +name: codeql/rust-examples +groups: + - rust + - examples +dependencies: + codeql/rust-all: ${workspace} +warnOnImplicitThis: true diff --git a/rust/ql/examples/snippets/empty_if.ql b/rust/ql/examples/snippets/empty_if.ql new file mode 100644 index 000000000000..90df6b95b22e --- /dev/null +++ b/rust/ql/examples/snippets/empty_if.ql @@ -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().getStmtList().getNumberOfStmtOrExpr() = 0 and + // and no 'else' branch exists + not exists(ifExpr.getElse()) +select ifExpr, "This 'if' expression is redundant." diff --git a/rust/ql/examples/snippets/simple_constant_password.ql b/rust/ql/examples/snippets/simple_constant_password.ql new file mode 100644 index 000000000000..202029994f49 --- /dev/null +++ b/rust/ql/examples/snippets/simple_constant_password.ql @@ -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; + +// 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() diff --git a/rust/ql/examples/snippets/simple_sql_injection.ql b/rust/ql/examples/snippets/simple_sql_injection.ql new file mode 100644 index 000000000000..0a991118a506 --- /dev/null +++ b/rust/ql/examples/snippets/simple_sql_injection.ql @@ -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; + +// 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" diff --git a/rust/ql/src/change-notes/2025-11-07-example-queries.md b/rust/ql/src/change-notes/2025-11-07-example-queries.md new file mode 100644 index 000000000000..9e11d4e0a93d --- /dev/null +++ b/rust/ql/src/change-notes/2025-11-07-example-queries.md @@ -0,0 +1,4 @@ +--- +category: newQuery +--- +* 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.