Skip to content

Commit 2f8b04b

Browse files
committed
Rust: Models-as-data for flow summaries
1 parent 97ab31a commit 2f8b04b

File tree

16 files changed

+392
-87
lines changed

16 files changed

+392
-87
lines changed

rust/ql/lib/codeql/rust/dataflow/FlowSummary.qll

+2-12
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,7 @@ private import codeql.rust.elements.internal.CallExprBaseImpl::Impl as CallExprB
77
// import all instances below
88
private module Summaries {
99
private import codeql.rust.Frameworks
10-
11-
// TODO: Use models-as-data when it's available
12-
private class UnwrapSummary extends SummarizedCallable::Range {
13-
UnwrapSummary() { this = "lang:core::_::<crate::option::Option>::unwrap" }
14-
15-
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
16-
input = "Argument[self].Variant[crate::option::Option::Some(0)]" and
17-
output = "ReturnValue" and
18-
preservesValue = true
19-
}
20-
}
10+
private import codeql.rust.dataflow.internal.ModelsAsData
2111
}
2212

2313
/** Provides the `Range` class used to define the extent of `LibraryCallable`. */
@@ -62,7 +52,7 @@ module SummarizedCallable {
6252
*
6353
* `preservesValue` indicates whether this is a value-preserving step or a taint-step.
6454
*/
65-
abstract predicate propagatesFlow(string input, string output, boolean preservesValue);
55+
predicate propagatesFlow(string input, string output, boolean preservesValue) { none() }
6656
}
6757
}
6858

rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll

+3-1
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ private class VariantFieldContent extends VariantContent, TVariantFieldContent {
597597
}
598598

599599
/** A canonical path pointing to a struct. */
600-
private class StructCanonicalPath extends MkStructCanonicalPath {
600+
class StructCanonicalPath extends MkStructCanonicalPath {
601601
CrateOriginOption crate;
602602
string path;
603603

@@ -606,6 +606,8 @@ private class StructCanonicalPath extends MkStructCanonicalPath {
606606
/** Gets the underlying struct. */
607607
Struct getStruct() { hasExtendedCanonicalPath(result, crate, path) }
608608

609+
string getExtendedCanonicalPath() { result = path }
610+
609611
string toString() { result = this.getStruct().getName().getText() }
610612

611613
Location getLocation() { result = this.getStruct().getLocation() }

rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll

+16
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,22 @@ module Input implements InputSig<Location, RustDataFlow> {
3232
arg = v.getExtendedCanonicalPath() + "::" + field
3333
)
3434
)
35+
or
36+
exists(StructCanonicalPath s, string field |
37+
result = "Struct" and
38+
c = TStructFieldContent(s, field) and
39+
arg = s.getExtendedCanonicalPath() + "::" + field
40+
)
41+
or
42+
result = "ArrayElement" and
43+
c = TArrayElement() and
44+
arg = ""
45+
or
46+
exists(int pos |
47+
result = "Tuple" and
48+
c = TTuplePositionContent(pos) and
49+
arg = pos.toString()
50+
)
3551
)
3652
}
3753

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Defines extensible predicates for contributing library models from data extensions.
3+
*/
4+
5+
private import rust
6+
private import codeql.rust.dataflow.FlowSummary
7+
8+
/**
9+
* Holds if in a call to the function with canonical path `path`, defined in the
10+
* crate `crate`, the value referred to by `output` is a flow source of the given
11+
* `kind`.
12+
*
13+
* `output = "ReturnValue"` simply means the result of the call itself.
14+
*
15+
* The following kinds are supported:
16+
*
17+
* - `remote`: a general remote flow source.
18+
*/
19+
extensible predicate sourceModel(
20+
string crate, string path, string output, string kind, string provenance,
21+
QlBuiltins::ExtensionId madId
22+
);
23+
24+
/**
25+
* Holds if in a call to the function with canonical path `path`, defined in the
26+
* crate `crate`, the value referred to by `input` is a flow sink of the given
27+
* `kind`.
28+
*
29+
* For example, `input = Argument[0]` means the first argument of the call.
30+
*
31+
* The following kinds are supported:
32+
*
33+
* - `sql-injection`: a flow sink for SQL injection.
34+
*/
35+
extensible predicate sinkModel(
36+
string crate, string path, string input, string kind, string provenance,
37+
QlBuiltins::ExtensionId madId
38+
);
39+
40+
/**
41+
* Holds if in a call to the function with canonical path `path`, defined in the
42+
* crate `crate`, the value referred to by `input` can flow to the value referred
43+
* to by `output`.
44+
*
45+
* `kind` should be either `value` or `taint`, for value-preserving or taint-preserving
46+
* steps, respectively.
47+
*/
48+
extensible predicate summaryModel(
49+
string crate, string path, string input, string output, string kind, string provenance,
50+
QlBuiltins::ExtensionId madId
51+
);
52+
53+
/**
54+
* Holds if the given extension tuple `madId` should pretty-print as `model`.
55+
*
56+
* This predicate should only be used in tests.
57+
*/
58+
predicate interpretModelForTest(QlBuiltins::ExtensionId madId, string model) {
59+
exists(string crate, string path, string output, string kind |
60+
sourceModel(crate, path, kind, output, _, madId) and
61+
model = "Source: " + crate + "; " + path + "; " + output + "; " + kind
62+
)
63+
or
64+
exists(string crate, string path, string input, string kind |
65+
sinkModel(crate, path, kind, input, _, madId) and
66+
model = "Sink: " + crate + "; " + path + "; " + input + "; " + kind
67+
)
68+
or
69+
exists(string type, string path, string input, string output, string kind |
70+
summaryModel(type, path, input, output, kind, _, madId) and
71+
model = "Summary: " + type + "; " + path + "; " + input + "; " + output + "; " + kind
72+
)
73+
}
74+
75+
private class SummarizedCallableFromModel extends SummarizedCallable::Range {
76+
private string crate;
77+
private string path;
78+
79+
SummarizedCallableFromModel() {
80+
summaryModel(crate, path, _, _, _, _, _) and
81+
this = crate + "::_::" + path
82+
}
83+
84+
override predicate propagatesFlow(
85+
string input, string output, boolean preservesValue, string model
86+
) {
87+
exists(string kind, QlBuiltins::ExtensionId madId |
88+
summaryModel(crate, path, input, output, kind, _, madId) and
89+
model = "MaD:" + madId.toString()
90+
|
91+
kind = "value" and
92+
preservesValue = true
93+
or
94+
kind = "taint" and
95+
preservesValue = false
96+
)
97+
}
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
extensions:
2+
# Make sure that the extensible model predicates have at least one definition
3+
# to avoid errors about undefined extensionals.
4+
- addsTo:
5+
pack: codeql/rust-all
6+
extensible: sourceModel
7+
data: []
8+
9+
- addsTo:
10+
pack: codeql/rust-all
11+
extensible: sinkModel
12+
data: []
13+
14+
- addsTo:
15+
pack: codeql/rust-all
16+
extensible: summaryModel
17+
data: []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/rust-all
4+
extensible: summaryModel
5+
data:
6+
- ["lang:core", "<crate::option::Option>::unwrap", "Argument[self].Variant[crate::option::Option::Some(0)]", "ReturnValue", "value", "manual"]

rust/ql/lib/qlpack.yml

+2
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ dependencies:
1313
codeql/ssa: ${workspace}
1414
codeql/tutorial: ${workspace}
1515
codeql/util: ${workspace}
16+
dataExtensions:
17+
- /**/*.model.yml
1618
warnOnImplicitThis: true

rust/ql/test/library-tests/dataflow/local/inline-flow.expected

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
models
2+
| 1 | Summary: lang:core; <crate::option::Option>::unwrap; Argument[self].Variant[crate::option::Option::Some(0)]; ReturnValue; value |
23
edges
34
| main.rs:19:13:19:21 | source(...) | main.rs:20:10:20:10 | s | provenance | |
45
| main.rs:24:13:24:21 | source(...) | main.rs:27:10:27:10 | c | provenance | |
@@ -35,7 +36,7 @@ edges
3536
| main.rs:214:14:214:14 | n | main.rs:214:25:214:25 | n | provenance | |
3637
| main.rs:224:14:224:29 | Some(...) [Some] | main.rs:225:10:225:11 | s1 [Some] | provenance | |
3738
| main.rs:224:19:224:28 | source(...) | main.rs:224:14:224:29 | Some(...) [Some] | provenance | |
38-
| main.rs:225:10:225:11 | s1 [Some] | main.rs:225:10:225:20 | s1.unwrap(...) | provenance | |
39+
| main.rs:225:10:225:11 | s1 [Some] | main.rs:225:10:225:20 | s1.unwrap(...) | provenance | MaD:1 |
3940
| main.rs:229:14:229:29 | Some(...) [Some] | main.rs:231:14:231:15 | s1 [Some] | provenance | |
4041
| main.rs:229:19:229:28 | source(...) | main.rs:229:14:229:29 | Some(...) [Some] | provenance | |
4142
| main.rs:231:14:231:15 | s1 [Some] | main.rs:231:14:231:16 | TryExpr | provenance | |

rust/ql/test/library-tests/dataflow/models/main.rs

+91
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,102 @@ fn test_set_var_field() {
9090
}
9191
}
9292

93+
struct MyStruct {
94+
field1: i64,
95+
field2: i64,
96+
}
97+
98+
// has a flow model
99+
fn get_struct_field(s: MyStruct) -> i64 {
100+
0
101+
}
102+
103+
fn test_get_struct_field() {
104+
let s = source(6);
105+
let my_struct = MyStruct {
106+
field1: s,
107+
field2: 0,
108+
};
109+
sink(get_struct_field(my_struct)); // $ hasValueFlow=6
110+
let my_struct2 = MyStruct {
111+
field1: 0,
112+
field2: s,
113+
};
114+
sink(get_struct_field(my_struct2));
115+
}
116+
117+
// has a flow model
118+
fn set_struct_field(i: i64) -> MyStruct {
119+
MyStruct {
120+
field1: 0,
121+
field2: 1,
122+
}
123+
}
124+
125+
fn test_set_struct_field() {
126+
let s = source(7);
127+
let my_struct = set_struct_field(s);
128+
sink(my_struct.field1);
129+
sink(my_struct.field2); // $ MISSING: hasValueFlow=7
130+
}
131+
132+
// has a flow model
133+
fn get_array_element(a: [i64; 1]) -> i64 {
134+
0
135+
}
136+
137+
fn test_get_array_element() {
138+
let s = source(8);
139+
sink(get_array_element([s])); // $ hasValueFlow=8
140+
}
141+
142+
// has a flow model
143+
fn set_array_element(i: i64) -> [i64; 1] {
144+
[0]
145+
}
146+
147+
fn test_set_array_element() {
148+
let s = source(9);
149+
let arr = set_array_element(s);
150+
sink(arr[0]); // $ hasValueFlow=9
151+
}
152+
153+
// has a flow model
154+
fn get_tuple_element(a: (i64, i64)) -> i64 {
155+
0
156+
}
157+
158+
fn test_get_tuple_element() {
159+
let s = source(10);
160+
let t = (s, 0);
161+
sink(get_tuple_element(t)); // $ hasValueFlow=10
162+
let t = (0, s);
163+
sink(get_tuple_element(t));
164+
}
165+
166+
// has a flow model
167+
fn set_tuple_element(i: i64) -> (i64, i64) {
168+
(0, 1)
169+
}
170+
171+
fn test_set_tuple_element() {
172+
let s = source(11);
173+
let t = set_tuple_element(s);
174+
sink(t.0);
175+
sink(t.1); // $ hasValueFlow=11
176+
}
177+
93178
fn main() {
94179
test_identify();
95180
test_get_var_pos();
96181
test_set_var_pos();
97182
test_get_var_field();
98183
test_set_var_field();
184+
test_get_struct_field();
185+
test_set_struct_field();
186+
test_get_array_element();
187+
test_set_array_element();
188+
test_get_tuple_element();
189+
test_set_tuple_element();
99190
let dummy = Some(0); // ensure that the the `lang:core` crate is extracted
100191
}

0 commit comments

Comments
 (0)