diff --git a/rust/ql/lib/codeql/rust/dataflow/FlowSummary.qll b/rust/ql/lib/codeql/rust/dataflow/FlowSummary.qll index f0457c960ceb..d1ba69ba22d2 100644 --- a/rust/ql/lib/codeql/rust/dataflow/FlowSummary.qll +++ b/rust/ql/lib/codeql/rust/dataflow/FlowSummary.qll @@ -7,17 +7,7 @@ private import codeql.rust.elements.internal.CallExprBaseImpl::Impl as CallExprB // import all instances below private module Summaries { private import codeql.rust.Frameworks - - // TODO: Use models-as-data when it's available - private class UnwrapSummary extends SummarizedCallable::Range { - UnwrapSummary() { this = "lang:core::_::::unwrap" } - - override predicate propagatesFlow(string input, string output, boolean preservesValue) { - input = "Argument[self].Variant[crate::option::Option::Some(0)]" and - output = "ReturnValue" and - preservesValue = true - } - } + private import codeql.rust.dataflow.internal.ModelsAsData } /** Provides the `Range` class used to define the extent of `LibraryCallable`. */ @@ -62,7 +52,7 @@ module SummarizedCallable { * * `preservesValue` indicates whether this is a value-preserving step or a taint-step. */ - abstract predicate propagatesFlow(string input, string output, boolean preservesValue); + predicate propagatesFlow(string input, string output, boolean preservesValue) { none() } } } diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll index 80fb80e7dc6d..068429ce09b0 100644 --- a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll +++ b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll @@ -597,7 +597,7 @@ private class VariantFieldContent extends VariantContent, TVariantFieldContent { } /** A canonical path pointing to a struct. */ -private class StructCanonicalPath extends MkStructCanonicalPath { +class StructCanonicalPath extends MkStructCanonicalPath { CrateOriginOption crate; string path; @@ -606,6 +606,8 @@ private class StructCanonicalPath extends MkStructCanonicalPath { /** Gets the underlying struct. */ Struct getStruct() { hasExtendedCanonicalPath(result, crate, path) } + string getExtendedCanonicalPath() { result = path } + string toString() { result = this.getStruct().getName().getText() } Location getLocation() { result = this.getStruct().getLocation() } diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll b/rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll index 3503ad073328..492764b3cf54 100644 --- a/rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll +++ b/rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll @@ -32,6 +32,22 @@ module Input implements InputSig { arg = v.getExtendedCanonicalPath() + "::" + field ) ) + or + exists(StructCanonicalPath s, string field | + result = "Struct" and + c = TStructFieldContent(s, field) and + arg = s.getExtendedCanonicalPath() + "::" + field + ) + or + result = "ArrayElement" and + c = TArrayElement() and + arg = "" + or + exists(int pos | + result = "Tuple" and + c = TTuplePositionContent(pos) and + arg = pos.toString() + ) ) } diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/ModelsAsData.qll b/rust/ql/lib/codeql/rust/dataflow/internal/ModelsAsData.qll new file mode 100644 index 000000000000..244cd6f300f0 --- /dev/null +++ b/rust/ql/lib/codeql/rust/dataflow/internal/ModelsAsData.qll @@ -0,0 +1,140 @@ +/** + * Defines extensible predicates for contributing library models from data extensions. + * + * The extensible relations have the following columns: + * + * - Sources: + * `crate; path; output; kind; provenance` + * - Sinks: + * `crate; path; input; kind; provenance` + * - Summaries: + * `crate; path; input; output; kind; provenance` + * + * The interpretation of a row is similar to API-graphs with a left-to-right + * reading. + * + * 1. The `crate` column selects a crate. + * 2. The `path` column selects a function with the given canonical path within + * the crate. + * 3. The `input` column specifies how data enters the element selected by the + * first 2 columns, and the `output` column specifies how data leaves the + * element selected by the first 2 columns. Both `input` and `output` are + * `.`-separated lists of "access path tokens" to resolve, starting at the + * selected function. + * + * The following tokens are supported: + * - `Argument[n]`: the `n`-th argument to a call. May be a range of form `x..y` (inclusive) + * and/or a comma-separated list. + * - `Parameter[n]`: the `n`-th parameter of a callback. May be a range of form `x..y` (inclusive) + * and/or a comma-separated list. + * - `ReturnValue`: the value returned by a function call. + * - `ArrayElement`: an element of an array. + * - `Variant[v::f]`: field `f` of the variant with canonical path `v`, for example + * `Variant[crate::ihex::Record::Data::value]`. + * - `Variant[v(i)]`: position `i` inside the variant with canonical path `v`, for example + * `Variant[crate::option::Option::Some(0)]`. + * - `Struct[s::f]`: field `f` of the struct with canonical path `v`, for example + * `Struct[crate::process::Child::stdin]`. + * - `Tuple[i]`: the `i`th element of a tuple. + * 4. The `kind` column is a tag that can be referenced from QL to determine to + * which classes the interpreted elements should be added. For example, for + * sources `"remote"` indicates a default remote flow source, and for summaries + * `"taint"` indicates a default additional taint step and `"value"` indicates a + * globally applicable value-preserving step. + * 5. The `provenance` column is mainly used internally, and should be set to `"manual"` for + * all custom models. + */ + +private import rust +private import codeql.rust.dataflow.FlowSummary + +/** + * Holds if in a call to the function with canonical path `path`, defined in the + * crate `crate`, the value referred to by `output` is a flow source of the given + * `kind`. + * + * `output = "ReturnValue"` simply means the result of the call itself. + * + * For more information on the `kind` parameter, see + * https://github.com/github/codeql/blob/main/docs/codeql/reusables/threat-model-description.rst. + */ +extensible predicate sourceModel( + string crate, string path, string output, string kind, string provenance, + QlBuiltins::ExtensionId madId +); + +/** + * Holds if in a call to the function with canonical path `path`, defined in the + * crate `crate`, the value referred to by `input` is a flow sink of the given + * `kind`. + * + * For example, `input = Argument[0]` means the first argument of the call. + * + * The following kinds are supported: + * + * - `sql-injection`: a flow sink for SQL injection. + */ +extensible predicate sinkModel( + string crate, string path, string input, string kind, string provenance, + QlBuiltins::ExtensionId madId +); + +/** + * Holds if in a call to the function with canonical path `path`, defined in the + * crate `crate`, the value referred to by `input` can flow to the value referred + * to by `output`. + * + * `kind` should be either `value` or `taint`, for value-preserving or taint-preserving + * steps, respectively. + */ +extensible predicate summaryModel( + string crate, string path, string input, string output, string kind, string provenance, + QlBuiltins::ExtensionId madId +); + +/** + * Holds if the given extension tuple `madId` should pretty-print as `model`. + * + * This predicate should only be used in tests. + */ +predicate interpretModelForTest(QlBuiltins::ExtensionId madId, string model) { + exists(string crate, string path, string output, string kind | + sourceModel(crate, path, kind, output, _, madId) and + model = "Source: " + crate + "; " + path + "; " + output + "; " + kind + ) + or + exists(string crate, string path, string input, string kind | + sinkModel(crate, path, kind, input, _, madId) and + model = "Sink: " + crate + "; " + path + "; " + input + "; " + kind + ) + or + exists(string type, string path, string input, string output, string kind | + summaryModel(type, path, input, output, kind, _, madId) and + model = "Summary: " + type + "; " + path + "; " + input + "; " + output + "; " + kind + ) +} + +private class SummarizedCallableFromModel extends SummarizedCallable::Range { + private string crate; + private string path; + + SummarizedCallableFromModel() { + summaryModel(crate, path, _, _, _, _, _) and + this = crate + "::_::" + path + } + + override predicate propagatesFlow( + string input, string output, boolean preservesValue, string model + ) { + exists(string kind, QlBuiltins::ExtensionId madId | + summaryModel(crate, path, input, output, kind, _, madId) and + model = "MaD:" + madId.toString() + | + kind = "value" and + preservesValue = true + or + kind = "taint" and + preservesValue = false + ) + } +} diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/empty.model.yml b/rust/ql/lib/codeql/rust/dataflow/internal/empty.model.yml new file mode 100644 index 000000000000..1a33951dfc38 --- /dev/null +++ b/rust/ql/lib/codeql/rust/dataflow/internal/empty.model.yml @@ -0,0 +1,17 @@ +extensions: + # Make sure that the extensible model predicates have at least one definition + # to avoid errors about undefined extensionals. + - addsTo: + pack: codeql/rust-all + extensible: sourceModel + data: [] + + - addsTo: + pack: codeql/rust-all + extensible: sinkModel + data: [] + + - addsTo: + pack: codeql/rust-all + extensible: summaryModel + data: [] diff --git a/rust/ql/lib/codeql/rust/frameworks/stdlib/lang-core.model.yml b/rust/ql/lib/codeql/rust/frameworks/stdlib/lang-core.model.yml new file mode 100644 index 000000000000..db61e6c70b5b --- /dev/null +++ b/rust/ql/lib/codeql/rust/frameworks/stdlib/lang-core.model.yml @@ -0,0 +1,6 @@ +extensions: + - addsTo: + pack: codeql/rust-all + extensible: summaryModel + data: + - ["lang:core", "::unwrap", "Argument[self].Variant[crate::option::Option::Some(0)]", "ReturnValue", "value", "manual"] diff --git a/rust/ql/lib/qlpack.yml b/rust/ql/lib/qlpack.yml index 53ccf6dfced4..181e992287ce 100644 --- a/rust/ql/lib/qlpack.yml +++ b/rust/ql/lib/qlpack.yml @@ -13,4 +13,6 @@ dependencies: codeql/ssa: ${workspace} codeql/tutorial: ${workspace} codeql/util: ${workspace} +dataExtensions: + - /**/*.model.yml warnOnImplicitThis: true diff --git a/rust/ql/test/library-tests/dataflow/local/inline-flow.expected b/rust/ql/test/library-tests/dataflow/local/inline-flow.expected index 9ee2f23f08c6..d464f5625817 100644 --- a/rust/ql/test/library-tests/dataflow/local/inline-flow.expected +++ b/rust/ql/test/library-tests/dataflow/local/inline-flow.expected @@ -1,4 +1,5 @@ models +| 1 | Summary: lang:core; ::unwrap; Argument[self].Variant[crate::option::Option::Some(0)]; ReturnValue; value | edges | main.rs:19:13:19:21 | source(...) | main.rs:20:10:20:10 | s | provenance | | | main.rs:24:13:24:21 | source(...) | main.rs:27:10:27:10 | c | provenance | | @@ -35,7 +36,7 @@ edges | main.rs:214:14:214:14 | n | main.rs:214:25:214:25 | n | provenance | | | main.rs:224:14:224:29 | Some(...) [Some] | main.rs:225:10:225:11 | s1 [Some] | provenance | | | main.rs:224:19:224:28 | source(...) | main.rs:224:14:224:29 | Some(...) [Some] | provenance | | -| main.rs:225:10:225:11 | s1 [Some] | main.rs:225:10:225:20 | s1.unwrap(...) | provenance | | +| main.rs:225:10:225:11 | s1 [Some] | main.rs:225:10:225:20 | s1.unwrap(...) | provenance | MaD:1 | | main.rs:229:14:229:29 | Some(...) [Some] | main.rs:231:14:231:15 | s1 [Some] | provenance | | | main.rs:229:19:229:28 | source(...) | main.rs:229:14:229:29 | Some(...) [Some] | provenance | | | main.rs:231:14:231:15 | s1 [Some] | main.rs:231:14:231:16 | TryExpr | provenance | | diff --git a/rust/ql/test/library-tests/dataflow/models/main.rs b/rust/ql/test/library-tests/dataflow/models/main.rs index 337cec5a220e..dbff546732a5 100644 --- a/rust/ql/test/library-tests/dataflow/models/main.rs +++ b/rust/ql/test/library-tests/dataflow/models/main.rs @@ -90,11 +90,102 @@ fn test_set_var_field() { } } +struct MyStruct { + field1: i64, + field2: i64, +} + +// has a flow model +fn get_struct_field(s: MyStruct) -> i64 { + 0 +} + +fn test_get_struct_field() { + let s = source(6); + let my_struct = MyStruct { + field1: s, + field2: 0, + }; + sink(get_struct_field(my_struct)); // $ hasValueFlow=6 + let my_struct2 = MyStruct { + field1: 0, + field2: s, + }; + sink(get_struct_field(my_struct2)); +} + +// has a flow model +fn set_struct_field(i: i64) -> MyStruct { + MyStruct { + field1: 0, + field2: 1, + } +} + +fn test_set_struct_field() { + let s = source(7); + let my_struct = set_struct_field(s); + sink(my_struct.field1); + sink(my_struct.field2); // $ MISSING: hasValueFlow=7 +} + +// has a flow model +fn get_array_element(a: [i64; 1]) -> i64 { + 0 +} + +fn test_get_array_element() { + let s = source(8); + sink(get_array_element([s])); // $ hasValueFlow=8 +} + +// has a flow model +fn set_array_element(i: i64) -> [i64; 1] { + [0] +} + +fn test_set_array_element() { + let s = source(9); + let arr = set_array_element(s); + sink(arr[0]); // $ hasValueFlow=9 +} + +// has a flow model +fn get_tuple_element(a: (i64, i64)) -> i64 { + 0 +} + +fn test_get_tuple_element() { + let s = source(10); + let t = (s, 0); + sink(get_tuple_element(t)); // $ hasValueFlow=10 + let t = (0, s); + sink(get_tuple_element(t)); +} + +// has a flow model +fn set_tuple_element(i: i64) -> (i64, i64) { + (0, 1) +} + +fn test_set_tuple_element() { + let s = source(11); + let t = set_tuple_element(s); + sink(t.0); + sink(t.1); // $ hasValueFlow=11 +} + fn main() { test_identify(); test_get_var_pos(); test_set_var_pos(); test_get_var_field(); test_set_var_field(); + test_get_struct_field(); + test_set_struct_field(); + test_get_array_element(); + test_set_array_element(); + test_get_tuple_element(); + test_set_tuple_element(); let dummy = Some(0); // ensure that the the `lang:core` crate is extracted } diff --git a/rust/ql/test/library-tests/dataflow/models/models.expected b/rust/ql/test/library-tests/dataflow/models/models.expected index 6ebc72099cae..8c5ce17f8c83 100644 --- a/rust/ql/test/library-tests/dataflow/models/models.expected +++ b/rust/ql/test/library-tests/dataflow/models/models.expected @@ -1,25 +1,35 @@ models +| 1 | Summary: repo::test; crate::coerce; Argument[0]; ReturnValue; taint | +| 2 | Summary: repo::test; crate::get_array_element; Argument[0].ArrayElement; ReturnValue; value | +| 3 | Summary: repo::test; crate::get_struct_field; Argument[0].Struct[crate::MyStruct::field1]; ReturnValue; value | +| 4 | Summary: repo::test; crate::get_tuple_element; Argument[0].Tuple[0]; ReturnValue; value | +| 5 | Summary: repo::test; crate::get_var_field; Argument[0].Variant[crate::MyFieldEnum::C::field_c]; ReturnValue; value | +| 6 | Summary: repo::test; crate::get_var_pos; Argument[0].Variant[crate::MyPosEnum::A(0)]; ReturnValue; value | +| 7 | Summary: repo::test; crate::set_array_element; Argument[0]; ReturnValue.ArrayElement; value | +| 8 | Summary: repo::test; crate::set_tuple_element; Argument[0]; ReturnValue.Tuple[1]; value | +| 9 | Summary: repo::test; crate::set_var_field; Argument[0]; ReturnValue.Variant[crate::MyFieldEnum::D::field_d]; value | +| 10 | Summary: repo::test; crate::set_var_pos; Argument[0]; ReturnValue.Variant[crate::MyPosEnum::B(0)]; value | edges | main.rs:15:13:15:21 | source(...) | main.rs:16:19:16:19 | s | provenance | | | main.rs:15:13:15:21 | source(...) | main.rs:16:19:16:19 | s | provenance | | -| main.rs:16:19:16:19 | s | main.rs:16:10:16:20 | identity(...) | provenance | | -| main.rs:16:19:16:19 | s | main.rs:16:10:16:20 | identity(...) | provenance | | +| main.rs:16:19:16:19 | s | main.rs:16:10:16:20 | identity(...) | provenance | QL | +| main.rs:16:19:16:19 | s | main.rs:16:10:16:20 | identity(...) | provenance | QL | | main.rs:25:13:25:22 | source(...) | main.rs:26:17:26:17 | s | provenance | | -| main.rs:26:17:26:17 | s | main.rs:26:10:26:18 | coerce(...) | provenance | | +| main.rs:26:17:26:17 | s | main.rs:26:10:26:18 | coerce(...) | provenance | MaD:1 | | main.rs:40:13:40:21 | source(...) | main.rs:41:27:41:27 | s | provenance | | | main.rs:40:13:40:21 | source(...) | main.rs:41:27:41:27 | s | provenance | | | main.rs:41:14:41:28 | ...::A(...) [A] | main.rs:42:22:42:23 | e1 [A] | provenance | | | main.rs:41:14:41:28 | ...::A(...) [A] | main.rs:42:22:42:23 | e1 [A] | provenance | | | main.rs:41:27:41:27 | s | main.rs:41:14:41:28 | ...::A(...) [A] | provenance | | | main.rs:41:27:41:27 | s | main.rs:41:14:41:28 | ...::A(...) [A] | provenance | | -| main.rs:42:22:42:23 | e1 [A] | main.rs:42:10:42:24 | get_var_pos(...) | provenance | | -| main.rs:42:22:42:23 | e1 [A] | main.rs:42:10:42:24 | get_var_pos(...) | provenance | | +| main.rs:42:22:42:23 | e1 [A] | main.rs:42:10:42:24 | get_var_pos(...) | provenance | MaD:6 | +| main.rs:42:22:42:23 | e1 [A] | main.rs:42:10:42:24 | get_var_pos(...) | provenance | MaD:6 | | main.rs:53:13:53:21 | source(...) | main.rs:54:26:54:26 | s | provenance | | | main.rs:53:13:53:21 | source(...) | main.rs:54:26:54:26 | s | provenance | | | main.rs:54:14:54:27 | set_var_pos(...) [B] | main.rs:57:9:57:23 | ...::B(...) [B] | provenance | | | main.rs:54:14:54:27 | set_var_pos(...) [B] | main.rs:57:9:57:23 | ...::B(...) [B] | provenance | | -| main.rs:54:26:54:26 | s | main.rs:54:14:54:27 | set_var_pos(...) [B] | provenance | | -| main.rs:54:26:54:26 | s | main.rs:54:14:54:27 | set_var_pos(...) [B] | provenance | | +| main.rs:54:26:54:26 | s | main.rs:54:14:54:27 | set_var_pos(...) [B] | provenance | MaD:10 | +| main.rs:54:26:54:26 | s | main.rs:54:14:54:27 | set_var_pos(...) [B] | provenance | MaD:10 | | main.rs:57:9:57:23 | ...::B(...) [B] | main.rs:57:22:57:22 | i | provenance | | | main.rs:57:9:57:23 | ...::B(...) [B] | main.rs:57:22:57:22 | i | provenance | | | main.rs:57:22:57:22 | i | main.rs:57:33:57:33 | i | provenance | | @@ -30,18 +40,56 @@ edges | main.rs:73:14:73:42 | ...::C {...} [C] | main.rs:74:24:74:25 | e1 [C] | provenance | | | main.rs:73:40:73:40 | s | main.rs:73:14:73:42 | ...::C {...} [C] | provenance | | | main.rs:73:40:73:40 | s | main.rs:73:14:73:42 | ...::C {...} [C] | provenance | | -| main.rs:74:24:74:25 | e1 [C] | main.rs:74:10:74:26 | get_var_field(...) | provenance | | -| main.rs:74:24:74:25 | e1 [C] | main.rs:74:10:74:26 | get_var_field(...) | provenance | | +| main.rs:74:24:74:25 | e1 [C] | main.rs:74:10:74:26 | get_var_field(...) | provenance | MaD:5 | +| main.rs:74:24:74:25 | e1 [C] | main.rs:74:10:74:26 | get_var_field(...) | provenance | MaD:5 | | main.rs:85:13:85:21 | source(...) | main.rs:86:28:86:28 | s | provenance | | | main.rs:85:13:85:21 | source(...) | main.rs:86:28:86:28 | s | provenance | | | main.rs:86:14:86:29 | set_var_field(...) [D] | main.rs:89:9:89:37 | ...::D {...} [D] | provenance | | | main.rs:86:14:86:29 | set_var_field(...) [D] | main.rs:89:9:89:37 | ...::D {...} [D] | provenance | | -| main.rs:86:28:86:28 | s | main.rs:86:14:86:29 | set_var_field(...) [D] | provenance | | -| main.rs:86:28:86:28 | s | main.rs:86:14:86:29 | set_var_field(...) [D] | provenance | | +| main.rs:86:28:86:28 | s | main.rs:86:14:86:29 | set_var_field(...) [D] | provenance | MaD:9 | +| main.rs:86:28:86:28 | s | main.rs:86:14:86:29 | set_var_field(...) [D] | provenance | MaD:9 | | main.rs:89:9:89:37 | ...::D {...} [D] | main.rs:89:35:89:35 | i | provenance | | | main.rs:89:9:89:37 | ...::D {...} [D] | main.rs:89:35:89:35 | i | provenance | | | main.rs:89:35:89:35 | i | main.rs:89:47:89:47 | i | provenance | | | main.rs:89:35:89:35 | i | main.rs:89:47:89:47 | i | provenance | | +| main.rs:104:13:104:21 | source(...) | main.rs:106:17:106:17 | s | provenance | | +| main.rs:104:13:104:21 | source(...) | main.rs:106:17:106:17 | s | provenance | | +| main.rs:105:21:108:5 | MyStruct {...} [MyStruct.field1] | main.rs:109:27:109:35 | my_struct [MyStruct.field1] | provenance | | +| main.rs:105:21:108:5 | MyStruct {...} [MyStruct.field1] | main.rs:109:27:109:35 | my_struct [MyStruct.field1] | provenance | | +| main.rs:106:17:106:17 | s | main.rs:105:21:108:5 | MyStruct {...} [MyStruct.field1] | provenance | | +| main.rs:106:17:106:17 | s | main.rs:105:21:108:5 | MyStruct {...} [MyStruct.field1] | provenance | | +| main.rs:109:27:109:35 | my_struct [MyStruct.field1] | main.rs:109:10:109:36 | get_struct_field(...) | provenance | MaD:3 | +| main.rs:109:27:109:35 | my_struct [MyStruct.field1] | main.rs:109:10:109:36 | get_struct_field(...) | provenance | MaD:3 | +| main.rs:138:13:138:21 | source(...) | main.rs:139:29:139:29 | s | provenance | | +| main.rs:138:13:138:21 | source(...) | main.rs:139:29:139:29 | s | provenance | | +| main.rs:139:28:139:30 | [...] [array[]] | main.rs:139:10:139:31 | get_array_element(...) | provenance | MaD:2 | +| main.rs:139:28:139:30 | [...] [array[]] | main.rs:139:10:139:31 | get_array_element(...) | provenance | MaD:2 | +| main.rs:139:29:139:29 | s | main.rs:139:28:139:30 | [...] [array[]] | provenance | | +| main.rs:139:29:139:29 | s | main.rs:139:28:139:30 | [...] [array[]] | provenance | | +| main.rs:148:13:148:21 | source(...) | main.rs:149:33:149:33 | s | provenance | | +| main.rs:148:13:148:21 | source(...) | main.rs:149:33:149:33 | s | provenance | | +| main.rs:149:15:149:34 | set_array_element(...) [array[]] | main.rs:150:10:150:12 | arr [array[]] | provenance | | +| main.rs:149:15:149:34 | set_array_element(...) [array[]] | main.rs:150:10:150:12 | arr [array[]] | provenance | | +| main.rs:149:33:149:33 | s | main.rs:149:15:149:34 | set_array_element(...) [array[]] | provenance | MaD:7 | +| main.rs:149:33:149:33 | s | main.rs:149:15:149:34 | set_array_element(...) [array[]] | provenance | MaD:7 | +| main.rs:150:10:150:12 | arr [array[]] | main.rs:150:10:150:15 | arr[0] | provenance | | +| main.rs:150:10:150:12 | arr [array[]] | main.rs:150:10:150:15 | arr[0] | provenance | | +| main.rs:159:13:159:22 | source(...) | main.rs:160:14:160:14 | s | provenance | | +| main.rs:159:13:159:22 | source(...) | main.rs:160:14:160:14 | s | provenance | | +| main.rs:160:13:160:18 | TupleExpr [tuple.0] | main.rs:161:28:161:28 | t [tuple.0] | provenance | | +| main.rs:160:13:160:18 | TupleExpr [tuple.0] | main.rs:161:28:161:28 | t [tuple.0] | provenance | | +| main.rs:160:14:160:14 | s | main.rs:160:13:160:18 | TupleExpr [tuple.0] | provenance | | +| main.rs:160:14:160:14 | s | main.rs:160:13:160:18 | TupleExpr [tuple.0] | provenance | | +| main.rs:161:28:161:28 | t [tuple.0] | main.rs:161:10:161:29 | get_tuple_element(...) | provenance | MaD:4 | +| main.rs:161:28:161:28 | t [tuple.0] | main.rs:161:10:161:29 | get_tuple_element(...) | provenance | MaD:4 | +| main.rs:172:13:172:22 | source(...) | main.rs:173:31:173:31 | s | provenance | | +| main.rs:172:13:172:22 | source(...) | main.rs:173:31:173:31 | s | provenance | | +| main.rs:173:13:173:32 | set_tuple_element(...) [tuple.1] | main.rs:175:10:175:10 | t [tuple.1] | provenance | | +| main.rs:173:13:173:32 | set_tuple_element(...) [tuple.1] | main.rs:175:10:175:10 | t [tuple.1] | provenance | | +| main.rs:173:31:173:31 | s | main.rs:173:13:173:32 | set_tuple_element(...) [tuple.1] | provenance | MaD:8 | +| main.rs:173:31:173:31 | s | main.rs:173:13:173:32 | set_tuple_element(...) [tuple.1] | provenance | MaD:8 | +| main.rs:175:10:175:10 | t [tuple.1] | main.rs:175:10:175:12 | t.1 | provenance | | +| main.rs:175:10:175:10 | t [tuple.1] | main.rs:175:10:175:12 | t.1 | provenance | | nodes | main.rs:15:13:15:21 | source(...) | semmle.label | source(...) | | main.rs:15:13:15:21 | source(...) | semmle.label | source(...) | @@ -96,6 +144,54 @@ nodes | main.rs:89:35:89:35 | i | semmle.label | i | | main.rs:89:47:89:47 | i | semmle.label | i | | main.rs:89:47:89:47 | i | semmle.label | i | +| main.rs:104:13:104:21 | source(...) | semmle.label | source(...) | +| main.rs:104:13:104:21 | source(...) | semmle.label | source(...) | +| main.rs:105:21:108:5 | MyStruct {...} [MyStruct.field1] | semmle.label | MyStruct {...} [MyStruct.field1] | +| main.rs:105:21:108:5 | MyStruct {...} [MyStruct.field1] | semmle.label | MyStruct {...} [MyStruct.field1] | +| main.rs:106:17:106:17 | s | semmle.label | s | +| main.rs:106:17:106:17 | s | semmle.label | s | +| main.rs:109:10:109:36 | get_struct_field(...) | semmle.label | get_struct_field(...) | +| main.rs:109:10:109:36 | get_struct_field(...) | semmle.label | get_struct_field(...) | +| main.rs:109:27:109:35 | my_struct [MyStruct.field1] | semmle.label | my_struct [MyStruct.field1] | +| main.rs:109:27:109:35 | my_struct [MyStruct.field1] | semmle.label | my_struct [MyStruct.field1] | +| main.rs:138:13:138:21 | source(...) | semmle.label | source(...) | +| main.rs:138:13:138:21 | source(...) | semmle.label | source(...) | +| main.rs:139:10:139:31 | get_array_element(...) | semmle.label | get_array_element(...) | +| main.rs:139:10:139:31 | get_array_element(...) | semmle.label | get_array_element(...) | +| main.rs:139:28:139:30 | [...] [array[]] | semmle.label | [...] [array[]] | +| main.rs:139:28:139:30 | [...] [array[]] | semmle.label | [...] [array[]] | +| main.rs:139:29:139:29 | s | semmle.label | s | +| main.rs:139:29:139:29 | s | semmle.label | s | +| main.rs:148:13:148:21 | source(...) | semmle.label | source(...) | +| main.rs:148:13:148:21 | source(...) | semmle.label | source(...) | +| main.rs:149:15:149:34 | set_array_element(...) [array[]] | semmle.label | set_array_element(...) [array[]] | +| main.rs:149:15:149:34 | set_array_element(...) [array[]] | semmle.label | set_array_element(...) [array[]] | +| main.rs:149:33:149:33 | s | semmle.label | s | +| main.rs:149:33:149:33 | s | semmle.label | s | +| main.rs:150:10:150:12 | arr [array[]] | semmle.label | arr [array[]] | +| main.rs:150:10:150:12 | arr [array[]] | semmle.label | arr [array[]] | +| main.rs:150:10:150:15 | arr[0] | semmle.label | arr[0] | +| main.rs:150:10:150:15 | arr[0] | semmle.label | arr[0] | +| main.rs:159:13:159:22 | source(...) | semmle.label | source(...) | +| main.rs:159:13:159:22 | source(...) | semmle.label | source(...) | +| main.rs:160:13:160:18 | TupleExpr [tuple.0] | semmle.label | TupleExpr [tuple.0] | +| main.rs:160:13:160:18 | TupleExpr [tuple.0] | semmle.label | TupleExpr [tuple.0] | +| main.rs:160:14:160:14 | s | semmle.label | s | +| main.rs:160:14:160:14 | s | semmle.label | s | +| main.rs:161:10:161:29 | get_tuple_element(...) | semmle.label | get_tuple_element(...) | +| main.rs:161:10:161:29 | get_tuple_element(...) | semmle.label | get_tuple_element(...) | +| main.rs:161:28:161:28 | t [tuple.0] | semmle.label | t [tuple.0] | +| main.rs:161:28:161:28 | t [tuple.0] | semmle.label | t [tuple.0] | +| main.rs:172:13:172:22 | source(...) | semmle.label | source(...) | +| main.rs:172:13:172:22 | source(...) | semmle.label | source(...) | +| main.rs:173:13:173:32 | set_tuple_element(...) [tuple.1] | semmle.label | set_tuple_element(...) [tuple.1] | +| main.rs:173:13:173:32 | set_tuple_element(...) [tuple.1] | semmle.label | set_tuple_element(...) [tuple.1] | +| main.rs:173:31:173:31 | s | semmle.label | s | +| main.rs:173:31:173:31 | s | semmle.label | s | +| main.rs:175:10:175:10 | t [tuple.1] | semmle.label | t [tuple.1] | +| main.rs:175:10:175:10 | t [tuple.1] | semmle.label | t [tuple.1] | +| main.rs:175:10:175:12 | t.1 | semmle.label | t.1 | +| main.rs:175:10:175:12 | t.1 | semmle.label | t.1 | subpaths testFailures invalidSpecComponent @@ -111,3 +207,13 @@ invalidSpecComponent | main.rs:74:10:74:26 | get_var_field(...) | main.rs:72:13:72:21 | source(...) | main.rs:74:10:74:26 | get_var_field(...) | $@ | main.rs:72:13:72:21 | source(...) | source(...) | | main.rs:89:47:89:47 | i | main.rs:85:13:85:21 | source(...) | main.rs:89:47:89:47 | i | $@ | main.rs:85:13:85:21 | source(...) | source(...) | | main.rs:89:47:89:47 | i | main.rs:85:13:85:21 | source(...) | main.rs:89:47:89:47 | i | $@ | main.rs:85:13:85:21 | source(...) | source(...) | +| main.rs:109:10:109:36 | get_struct_field(...) | main.rs:104:13:104:21 | source(...) | main.rs:109:10:109:36 | get_struct_field(...) | $@ | main.rs:104:13:104:21 | source(...) | source(...) | +| main.rs:109:10:109:36 | get_struct_field(...) | main.rs:104:13:104:21 | source(...) | main.rs:109:10:109:36 | get_struct_field(...) | $@ | main.rs:104:13:104:21 | source(...) | source(...) | +| main.rs:139:10:139:31 | get_array_element(...) | main.rs:138:13:138:21 | source(...) | main.rs:139:10:139:31 | get_array_element(...) | $@ | main.rs:138:13:138:21 | source(...) | source(...) | +| main.rs:139:10:139:31 | get_array_element(...) | main.rs:138:13:138:21 | source(...) | main.rs:139:10:139:31 | get_array_element(...) | $@ | main.rs:138:13:138:21 | source(...) | source(...) | +| main.rs:150:10:150:15 | arr[0] | main.rs:148:13:148:21 | source(...) | main.rs:150:10:150:15 | arr[0] | $@ | main.rs:148:13:148:21 | source(...) | source(...) | +| main.rs:150:10:150:15 | arr[0] | main.rs:148:13:148:21 | source(...) | main.rs:150:10:150:15 | arr[0] | $@ | main.rs:148:13:148:21 | source(...) | source(...) | +| main.rs:161:10:161:29 | get_tuple_element(...) | main.rs:159:13:159:22 | source(...) | main.rs:161:10:161:29 | get_tuple_element(...) | $@ | main.rs:159:13:159:22 | source(...) | source(...) | +| main.rs:161:10:161:29 | get_tuple_element(...) | main.rs:159:13:159:22 | source(...) | main.rs:161:10:161:29 | get_tuple_element(...) | $@ | main.rs:159:13:159:22 | source(...) | source(...) | +| main.rs:175:10:175:12 | t.1 | main.rs:172:13:172:22 | source(...) | main.rs:175:10:175:12 | t.1 | $@ | main.rs:172:13:172:22 | source(...) | source(...) | +| main.rs:175:10:175:12 | t.1 | main.rs:172:13:172:22 | source(...) | main.rs:175:10:175:12 | t.1 | $@ | main.rs:172:13:172:22 | source(...) | source(...) | diff --git a/rust/ql/test/library-tests/dataflow/models/models.ext.yml b/rust/ql/test/library-tests/dataflow/models/models.ext.yml new file mode 100644 index 000000000000..d06fc442e2a4 --- /dev/null +++ b/rust/ql/test/library-tests/dataflow/models/models.ext.yml @@ -0,0 +1,16 @@ +extensions: + - addsTo: + pack: codeql/rust-all + extensible: summaryModel + data: + - ["repo::test", "crate::coerce", "Argument[0]", "ReturnValue", "taint", "manual"] + - ["repo::test", "crate::get_var_pos", "Argument[0].Variant[crate::MyPosEnum::A(0)]", "ReturnValue", "value", "manual"] + - ["repo::test", "crate::set_var_pos", "Argument[0]", "ReturnValue.Variant[crate::MyPosEnum::B(0)]", "value", "manual"] + - ["repo::test", "crate::get_var_field", "Argument[0].Variant[crate::MyFieldEnum::C::field_c]", "ReturnValue", "value", "manual"] + - ["repo::test", "crate::set_var_field", "Argument[0]", "ReturnValue.Variant[crate::MyFieldEnum::D::field_d]", "value", "manual"] + - ["repo::test", "crate::get_struct_field", "Argument[0].Struct[crate::MyStruct::field1]", "ReturnValue", "value", "manual"] + - ["repo::test", "crate::set_struct_field", "Argument[0]", "ReturnValue.Struct[crate::MyStruct::field2]", "value", "manual"] + - ["repo::test", "crate::get_array_element", "Argument[0].ArrayElement", "ReturnValue", "value", "manual"] + - ["repo::test", "crate::set_array_element", "Argument[0]", "ReturnValue.ArrayElement", "value", "manual"] + - ["repo::test", "crate::get_tuple_element", "Argument[0].Tuple[0]", "ReturnValue", "value", "manual"] + - ["repo::test", "crate::set_tuple_element", "Argument[0]", "ReturnValue.Tuple[1]", "value", "manual"] diff --git a/rust/ql/test/library-tests/dataflow/models/models.ql b/rust/ql/test/library-tests/dataflow/models/models.ql index 53c3f5de4be3..e456d6d1c1c1 100644 --- a/rust/ql/test/library-tests/dataflow/models/models.ql +++ b/rust/ql/test/library-tests/dataflow/models/models.ql @@ -15,63 +15,18 @@ query predicate invalidSpecComponent(SummarizedCallable sc, string s, string c) Private::External::invalidSpecComponent(s, c) } +// not defined in `models.ext.yml`, in order to test that we can also define +// models directly in QL private class SummarizedCallableIdentity extends SummarizedCallable::Range { SummarizedCallableIdentity() { this = "repo::test::_::crate::identity" } - override predicate propagatesFlow(string input, string output, boolean preservesValue) { + override predicate propagatesFlow( + string input, string output, boolean preservesValue, string provenance + ) { input = "Argument[0]" and output = "ReturnValue" and - preservesValue = true - } -} - -private class SummarizedCallableCoerce extends SummarizedCallable::Range { - SummarizedCallableCoerce() { this = "repo::test::_::crate::coerce" } - - override predicate propagatesFlow(string input, string output, boolean preservesValue) { - input = "Argument[0]" and - output = "ReturnValue" and - preservesValue = false - } -} - -private class SummarizedCallableGetVarPos extends SummarizedCallable::Range { - SummarizedCallableGetVarPos() { this = "repo::test::_::crate::get_var_pos" } - - override predicate propagatesFlow(string input, string output, boolean preservesValue) { - input = "Argument[0].Variant[crate::MyPosEnum::A(0)]" and - output = "ReturnValue" and - preservesValue = true - } -} - -private class SummarizedCallableSetVarPos extends SummarizedCallable::Range { - SummarizedCallableSetVarPos() { this = "repo::test::_::crate::set_var_pos" } - - override predicate propagatesFlow(string input, string output, boolean preservesValue) { - input = "Argument[0]" and - output = "ReturnValue.Variant[crate::MyPosEnum::B(0)]" and - preservesValue = true - } -} - -private class SummarizedCallableGetVarField extends SummarizedCallable::Range { - SummarizedCallableGetVarField() { this = "repo::test::_::crate::get_var_field" } - - override predicate propagatesFlow(string input, string output, boolean preservesValue) { - input = "Argument[0].Variant[crate::MyFieldEnum::C::field_c]" and - output = "ReturnValue" and - preservesValue = true - } -} - -private class SummarizedCallableSetVarField extends SummarizedCallable::Range { - SummarizedCallableSetVarField() { this = "repo::test::_::crate::set_var_field" } - - override predicate propagatesFlow(string input, string output, boolean preservesValue) { - input = "Argument[0]" and - output = "ReturnValue.Variant[crate::MyFieldEnum::D::field_d]" and - preservesValue = true + preservesValue = true and + provenance = "QL" } } diff --git a/rust/ql/test/query-tests/security/CWE-089/SqlInjection.qlref b/rust/ql/test/query-tests/security/CWE-089/SqlInjection.qlref index 504d27ff30cc..7aee10fcc4a5 100644 --- a/rust/ql/test/query-tests/security/CWE-089/SqlInjection.qlref +++ b/rust/ql/test/query-tests/security/CWE-089/SqlInjection.qlref @@ -1,2 +1,4 @@ query: queries/security/CWE-089/SqlInjection.ql -postprocess: utils/InlineExpectationsTestQuery.ql +postprocess: + - utils/PrettyPrintModels.ql + - utils/InlineExpectationsTestQuery.ql diff --git a/rust/ql/test/utils/InlineFlowTest.qll b/rust/ql/test/utils/InlineFlowTest.qll index dcf8ad8c4459..cb5b9f72abb2 100644 --- a/rust/ql/test/utils/InlineFlowTest.qll +++ b/rust/ql/test/utils/InlineFlowTest.qll @@ -9,6 +9,7 @@ private import codeql.rust.controlflow.CfgNodes private import codeql.rust.dataflow.DataFlow private import codeql.rust.dataflow.internal.DataFlowImpl private import codeql.rust.dataflow.internal.TaintTrackingImpl +private import codeql.rust.dataflow.internal.ModelsAsData as MaD private import internal.InlineExpectationsTestImpl as InlineExpectationsTestImpl // Holds if the target expression of `call` is a path and the string representation of the path is `name`. @@ -38,7 +39,7 @@ private module FlowTestImpl implements InputSig { exists(sink) } - predicate interpretModelForTest(QlBuiltins::ExtensionId madId, string model) { none() } + predicate interpretModelForTest = MaD::interpretModelForTest/2; } import InlineFlowTestMake diff --git a/rust/ql/test/utils/PrettyPrintModels.ql b/rust/ql/test/utils/PrettyPrintModels.ql new file mode 100644 index 000000000000..9740f20433fa --- /dev/null +++ b/rust/ql/test/utils/PrettyPrintModels.ql @@ -0,0 +1,7 @@ +/** + * @kind test-postprocess + */ + +import codeql.rust.dataflow.internal.ModelsAsData +import codeql.dataflow.test.ProvenancePathGraph +import codeql.dataflow.test.ProvenancePathGraph::TestPostProcessing::TranslateProvenanceResults diff --git a/rust/ql/test/utils/ProvenancePathGraph.qll b/rust/ql/test/utils/ProvenancePathGraph.qll new file mode 100644 index 000000000000..fd5b771941d5 --- /dev/null +++ b/rust/ql/test/utils/ProvenancePathGraph.qll @@ -0,0 +1,8 @@ +private import codeql.dataflow.DataFlow as DF +private import codeql.dataflow.test.ProvenancePathGraph as Graph +private import codeql.rust.dataflow.internal.ModelsAsData + +/** Transforms a `PathGraph` by printing the provenance information. */ +module ShowProvenance PathGraph> { + import Graph::ShowProvenance +}