From 84b50606ed1b4f6f4236732d9d36d61e3cf6065f Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 6 Nov 2025 17:31:16 +0000 Subject: [PATCH 1/6] Rust: Add example queries pack. --- docs/codeql/reusables/rust-further-reading.rst | 1 + rust/ql/examples/qlpack.lock.yml | 4 ++++ rust/ql/examples/qlpack.yml | 7 +++++++ 3 files changed, 12 insertions(+) create mode 100644 rust/ql/examples/qlpack.lock.yml create mode 100644 rust/ql/examples/qlpack.yml 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 From 6ce0a0d9df53a2f9b90d962b57e8c7615b4ce887 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 6 Nov 2025 17:50:59 +0000 Subject: [PATCH 2/6] Rust: Add example from the basic-query-for-rust-code.rst. --- rust/ql/examples/snippets/empty_if.ql | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 rust/ql/examples/snippets/empty_if.ql diff --git a/rust/ql/examples/snippets/empty_if.ql b/rust/ql/examples/snippets/empty_if.ql new file mode 100644 index 000000000000..a4270a115b6e --- /dev/null +++ b/rust/ql/examples/snippets/empty_if.ql @@ -0,0 +1,15 @@ +/** + * @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 + +from IfExpr ifExpr +where + ifExpr.getThen().(BlockExpr).getStmtList().getNumberOfStmtOrExpr() = 0 and + not exists(ifExpr.getElse()) +select ifExpr, "This 'if' expression is redundant." From 49aefe2110fbee92a96084b2ad59b33205c8095b Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 6 Nov 2025 18:49:55 +0000 Subject: [PATCH 3/6] Rust: Add simple SQL injection example. --- .../examples/snippets/simple_sql_injection.ql | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 rust/ql/examples/snippets/simple_sql_injection.ql 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..fd59ddd4c3e0 --- /dev/null +++ b/rust/ql/examples/snippets/simple_sql_injection.ql @@ -0,0 +1,30 @@ +/** + * @name Database query built from user-controlled sources + * @description Finds places where a value from a remote or local user input + * is used as an argument to the `sqlx_core::query::query` + * function. + * @id rust/examples/simple-sql-injection + * @tags example + */ + +import rust +import codeql.rust.dataflow.DataFlow +import codeql.rust.dataflow.TaintTracking +import codeql.rust.Concepts + +module SqlInjectionConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { node instanceof ActiveThreatModelSource } + + predicate isSink(DataFlow::Node node) { + exists(CallExpr call | + call.getStaticTarget().getCanonicalPath() = "sqlx_core::query::query" and + call.getArg(0) = node.asExpr().getExpr() + ) + } +} + +module SqlInjectionFlow = TaintTracking::Global; + +from DataFlow::Node sourceNode, DataFlow::Node sinkNode +where SqlInjectionFlow::flow(sourceNode, sinkNode) +select sinkNode, "This query depends on a $@.", sourceNode, "user-provided value" From 7b6e06e8de164fd7b7948262c54e8da16838f25f Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:30:53 +0000 Subject: [PATCH 4/6] Rust: Add simple constant password example. --- .../snippets/simple_constant_password.ql | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 rust/ql/examples/snippets/simple_constant_password.ql 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..87b7603ba1b0 --- /dev/null +++ b/rust/ql/examples/snippets/simple_constant_password.ql @@ -0,0 +1,30 @@ +/** + * @name Constant password + * @description Finds places where a string literal is used in a function call + * argument named something like "password". + * @id rust/examples/simple-constant-password + * @tags example + */ + +import rust +import codeql.rust.dataflow.DataFlow +import codeql.rust.dataflow.TaintTracking + +module ConstantPasswordConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { 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() + ) + } +} + +module ConstantPasswordFlow = TaintTracking::Global; + +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() From 7e3ab99d6b49856c7c2b0dee6c3939e6543f2b22 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:48:03 +0000 Subject: [PATCH 5/6] Rust: Add much more detailed code comments, since these are examples. --- rust/ql/examples/snippets/empty_if.ql | 3 +++ .../snippets/simple_constant_password.ql | 21 +++++++++++++++++-- .../examples/snippets/simple_sql_injection.ql | 15 ++++++++++--- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/rust/ql/examples/snippets/empty_if.ql b/rust/ql/examples/snippets/empty_if.ql index a4270a115b6e..5a4a62e41b61 100644 --- a/rust/ql/examples/snippets/empty_if.ql +++ b/rust/ql/examples/snippets/empty_if.ql @@ -8,8 +8,11 @@ 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." diff --git a/rust/ql/examples/snippets/simple_constant_password.ql b/rust/ql/examples/snippets/simple_constant_password.ql index 87b7603ba1b0..202029994f49 100644 --- a/rust/ql/examples/snippets/simple_constant_password.ql +++ b/rust/ql/examples/snippets/simple_constant_password.ql @@ -1,7 +1,7 @@ /** * @name Constant password * @description Finds places where a string literal is used in a function call - * argument named something like "password". + * argument that looks like a password. * @id rust/examples/simple-constant-password * @tags example */ @@ -10,8 +10,23 @@ 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.asExpr().getExpr() instanceof StringLiteralExpr } + 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%" @@ -23,8 +38,10 @@ module ConstantPasswordConfig implements DataFlow::ConfigSig { } } +// 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 index fd59ddd4c3e0..0a991118a506 100644 --- a/rust/ql/examples/snippets/simple_sql_injection.ql +++ b/rust/ql/examples/snippets/simple_sql_injection.ql @@ -1,8 +1,7 @@ /** * @name Database query built from user-controlled sources * @description Finds places where a value from a remote or local user input - * is used as an argument to the `sqlx_core::query::query` - * function. + * is used as the first argument of a call to `sqlx_core::query::query`. * @id rust/examples/simple-sql-injection * @tags example */ @@ -12,10 +11,18 @@ 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 instanceof ActiveThreatModelSource } + 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() @@ -23,8 +30,10 @@ module SqlInjectionConfig implements DataFlow::ConfigSig { } } +// 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" From 61481b51e7bed367aca37893e4019f4d67343552 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:52:56 +0000 Subject: [PATCH 6/6] Rust: Change note. --- rust/ql/src/change-notes/2025-11-07-example-queries.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 rust/ql/src/change-notes/2025-11-07-example-queries.md 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.