Skip to content

Commit fd8821f

Browse files
authored
Merge pull request #21475 from owen-mc/rust/mad-barriers
Rust: Add support for defining barriers and barrier guards using models-as-data
2 parents cc99867 + 8d16a2b commit fd8821f

File tree

12 files changed

+415
-32
lines changed

12 files changed

+415
-32
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* Provides classes and predicates for defining barriers and barrier guards.
3+
*
4+
* Flow barriers and barrier guards defined here feed into data flow configurations as follows:
5+
*
6+
* ```text
7+
* data from *.model.yml | QL extensions of FlowBarrier::Range
8+
* v v
9+
* FlowBarrier (associated with a models-as-data kind string)
10+
* v
11+
* barrierNode predicate | other QL defined barriers, for example using concepts
12+
* v v
13+
* various Barrier classes for specific data flow configurations
14+
*
15+
* data from *.model.yml | QL extensions of FlowBarrierGuard::Range
16+
* v v
17+
* FlowBarrierGuard (associated with a models-as-data kind string)
18+
* v
19+
* barrierNode predicate | other QL defined barrier guards, for example using concepts
20+
* v v
21+
* various Barrier classes for specific data flow configurations
22+
* ```
23+
*
24+
* New barriers and barrier guards should be defined using models-as-data, QL extensions of
25+
* `FlowBarrier::Range`/`FlowBarrierGuard::Range`, or concepts. Data flow configurations should use the
26+
* `barrierNode` predicate and/or concepts to define their barriers.
27+
*/
28+
29+
private import rust
30+
private import internal.FlowSummaryImpl as Impl
31+
private import internal.DataFlowImpl as DataFlowImpl
32+
33+
// import all instances below
34+
private module Barriers {
35+
private import codeql.rust.Frameworks
36+
private import codeql.rust.dataflow.internal.ModelsAsData
37+
}
38+
39+
/** Provides the `Range` class used to define the extent of `FlowBarrier`. */
40+
module FlowBarrier {
41+
/** A flow barrier. */
42+
abstract class Range extends Impl::Public::BarrierElement {
43+
bindingset[this]
44+
Range() { any() }
45+
46+
override predicate isBarrier(
47+
string output, string kind, Impl::Public::Provenance provenance, string model
48+
) {
49+
this.isBarrier(output, kind) and provenance = "manual" and model = ""
50+
}
51+
52+
/**
53+
* Holds if this element is a flow barrier of kind `kind`, where data
54+
* flows out as described by `output`.
55+
*/
56+
predicate isBarrier(string output, string kind) { none() }
57+
}
58+
}
59+
60+
final class FlowBarrier = FlowBarrier::Range;
61+
62+
/** Provides the `Range` class used to define the extent of `FlowBarrierGuard`. */
63+
module FlowBarrierGuard {
64+
/** A flow barrier guard. */
65+
abstract class Range extends Impl::Public::BarrierGuardElement {
66+
bindingset[this]
67+
Range() { any() }
68+
69+
override predicate isBarrierGuard(
70+
string input, string branch, string kind, Impl::Public::Provenance provenance, string model
71+
) {
72+
this.isBarrierGuard(input, branch, kind) and provenance = "manual" and model = ""
73+
}
74+
75+
/**
76+
* Holds if this element is a flow barrier guard of kind `kind`, for data
77+
* flowing in as described by `input`, when `this` evaluates to `branch`.
78+
*/
79+
predicate isBarrierGuard(string input, string branch, string kind) { none() }
80+
}
81+
}
82+
83+
final class FlowBarrierGuard = FlowBarrierGuard::Range;
84+
85+
predicate barrierNode = DataFlowImpl::barrierNode/2;

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

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,64 @@ private module Cached {
11571157
cached
11581158
predicate sinkNode(Node n, string kind) { n.(FlowSummaryNode).isSink(kind, _) }
11591159

1160+
private newtype TKindModelPair =
1161+
TMkPair(string kind, string model) {
1162+
FlowSummaryImpl::Private::barrierGuardSpec(_, _, _, kind, model)
1163+
}
1164+
1165+
private boolean convertAcceptingValue(FlowSummaryImpl::Public::AcceptingValue av) {
1166+
av.isTrue() and result = true
1167+
or
1168+
av.isFalse() and result = false
1169+
// Remaining cases are not supported yet, they depend on the shared Guards library.
1170+
// or
1171+
// av.isNoException() and result.getDualValue().isThrowsException()
1172+
// or
1173+
// av.isZero() and result.asIntValue() = 0
1174+
// or
1175+
// av.isNotZero() and result.getDualValue().asIntValue() = 0
1176+
// or
1177+
// av.isNull() and result.isNullValue()
1178+
// or
1179+
// av.isNotNull() and result.isNonNullValue()
1180+
}
1181+
1182+
private predicate barrierGuardChecks(AstNode g, Expr e, boolean gv, TKindModelPair kmp) {
1183+
exists(
1184+
FlowSummaryImpl::Public::BarrierGuardElement b,
1185+
FlowSummaryImpl::Private::SummaryComponentStack stack,
1186+
FlowSummaryImpl::Public::AcceptingValue acceptingvalue, string kind, string model
1187+
|
1188+
FlowSummaryImpl::Private::barrierGuardSpec(b, stack, acceptingvalue, kind, model) and
1189+
e = FlowSummaryImpl::StepsInput::getSinkNode(b, stack.headOfSingleton()).asExpr() and
1190+
kmp = TMkPair(kind, model) and
1191+
gv = convertAcceptingValue(acceptingvalue) and
1192+
g = b.getCall()
1193+
)
1194+
}
1195+
1196+
/** Holds if `n` is a flow barrier of kind `kind` and model `model`. */
1197+
cached
1198+
predicate barrierNode(Node n, string kind, string model) {
1199+
exists(
1200+
FlowSummaryImpl::Public::BarrierElement b,
1201+
FlowSummaryImpl::Private::SummaryComponentStack stack
1202+
|
1203+
FlowSummaryImpl::Private::barrierSpec(b, stack, kind, model)
1204+
|
1205+
n = FlowSummaryImpl::StepsInput::getSourceNode(b, stack, false)
1206+
or
1207+
// For barriers like `Argument[0]` we want to target the pre-update node
1208+
n =
1209+
FlowSummaryImpl::StepsInput::getSourceNode(b, stack, true)
1210+
.(PostUpdateNode)
1211+
.getPreUpdateNode()
1212+
)
1213+
or
1214+
ParameterizedBarrierGuard<TKindModelPair, barrierGuardChecks/4>::getABarrierNode(TMkPair(kind,
1215+
model)) = n
1216+
}
1217+
11601218
/**
11611219
* A step in a flow summary defined using `OptionalStep[name]`. An `OptionalStep` is "opt-in", which means
11621220
* that by default the step is not present in the flow summary and needs to be explicitly enabled by defining
@@ -1180,3 +1238,34 @@ private module Cached {
11801238
}
11811239

11821240
import Cached
1241+
1242+
/** Holds if `n` is a flow barrier of kind `kind`. */
1243+
predicate barrierNode(Node n, string kind) { barrierNode(n, kind, _) }
1244+
1245+
bindingset[this]
1246+
private signature class ParamSig;
1247+
1248+
private module WithParam<ParamSig P> {
1249+
/**
1250+
* Holds if the guard `g` validates the expression `e` upon evaluating to `gv`.
1251+
*
1252+
* The expression `e` is expected to be a syntactic part of the guard `g`.
1253+
* For example, the guard `g` might be a call `isSafe(x)` and the expression `e`
1254+
* the argument `x`.
1255+
*/
1256+
signature predicate guardChecksSig(AstNode g, Expr e, boolean branch, P param);
1257+
}
1258+
1259+
/**
1260+
* Provides a set of barrier nodes for a guard that validates an expression.
1261+
*
1262+
* This is expected to be used in `isBarrier`/`isSanitizer` definitions
1263+
* in data flow and taint tracking.
1264+
*/
1265+
module ParameterizedBarrierGuard<ParamSig P, WithParam<P>::guardChecksSig/4 guardChecks> {
1266+
/** Gets a node that is safely guarded by the given guard check. */
1267+
Node getABarrierNode(P param) {
1268+
SsaFlow::asNode(result) =
1269+
SsaImpl::DataFlowIntegration::ParameterizedBarrierGuard<P, guardChecks/4>::getABarrierNode(param)
1270+
}
1271+
}

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ module Input implements InputSig<Location, RustDataFlow> {
143143

144144
private import Make<Location, RustDataFlow, Input> as Impl
145145

146-
private module StepsInput implements Impl::Private::StepsInputSig {
146+
module StepsInput implements Impl::Private::StepsInputSig {
147147
DataFlowCall getACall(Public::SummarizedCallable sc) { result.asCall().getStaticTarget() = sc }
148148

149149
/** Gets the argument of `source` described by `sc`, if any. */
@@ -171,18 +171,27 @@ private module StepsInput implements Impl::Private::StepsInputSig {
171171
result.asCfgScope() = source.getEnclosingCfgScope()
172172
}
173173

174-
RustDataFlow::Node getSourceNode(Input::SourceBase source, Impl::Private::SummaryComponentStack s) {
174+
additional RustDataFlow::Node getSourceNode(
175+
Input::SourceBase source, Impl::Private::SummaryComponentStack s, boolean isArgPostUpdate
176+
) {
175177
s.head() = Impl::Private::SummaryComponent::return(_) and
176-
result.asExpr() = source.getCall()
178+
result.asExpr() = source.getCall() and
179+
isArgPostUpdate = false
177180
or
178181
exists(RustDataFlow::ArgumentPosition pos, Expr arg |
179182
s.head() = Impl::Private::SummaryComponent::parameter(pos) and
180183
arg = getSourceNodeArgument(source, s.tail().headOfSingleton()) and
181-
result.asParameter() = getCallable(arg).getParam(pos.getPosition())
184+
result.asParameter() = getCallable(arg).getParam(pos.getPosition()) and
185+
isArgPostUpdate = false
182186
)
183187
or
184188
result.(RustDataFlow::PostUpdateNode).getPreUpdateNode().asExpr() =
185-
getSourceNodeArgument(source, s.headOfSingleton())
189+
getSourceNodeArgument(source, s.headOfSingleton()) and
190+
isArgPostUpdate = true
191+
}
192+
193+
RustDataFlow::Node getSourceNode(Input::SourceBase source, Impl::Private::SummaryComponentStack s) {
194+
result = getSourceNode(source, s, _)
186195
}
187196

188197
RustDataFlow::Node getSinkNode(Input::SinkBase sink, Impl::Private::SummaryComponent sc) {

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

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
*/
4545

4646
private import rust
47+
private import codeql.rust.dataflow.FlowBarrier
4748
private import codeql.rust.dataflow.FlowSummary
4849
private import codeql.rust.dataflow.FlowSource
4950
private import codeql.rust.dataflow.FlowSink
@@ -98,6 +99,29 @@ extensible predicate neutralModel(
9899
string path, string kind, string provenance, QlBuiltins::ExtensionId madId
99100
);
100101

102+
/**
103+
* Holds if in a call to the function with canonical path `path`, the value referred
104+
* to by `output` is a barrier of the given `kind` and `madId` is the data
105+
* extension row number.
106+
*/
107+
extensible predicate barrierModel(
108+
string path, string output, string kind, string provenance, QlBuiltins::ExtensionId madId
109+
);
110+
111+
/**
112+
* Holds if in a call to the function with canonical path `path`, the value referred
113+
* to by `input` is a barrier guard of the given `kind` and `madId` is the data
114+
* extension row number.
115+
*
116+
* The value referred to by `input` is assumed to lead to an argument of a call
117+
* (possibly `self`), and the call is guarding the argument. `branch` is either `true`
118+
* or `false`, indicating which branch of the guard is protecting the argument.
119+
*/
120+
extensible predicate barrierGuardModel(
121+
string path, string input, string branch, string kind, string provenance,
122+
QlBuiltins::ExtensionId madId
123+
);
124+
101125
/**
102126
* Holds if the given extension tuple `madId` should pretty-print as `model`.
103127
*
@@ -123,6 +147,16 @@ predicate interpretModelForTest(QlBuiltins::ExtensionId madId, string model) {
123147
neutralModel(path, kind, _, madId) and
124148
model = "Neutral: " + path + "; " + kind
125149
)
150+
or
151+
exists(string path, string output, string kind |
152+
barrierModel(path, output, kind, _, madId) and
153+
model = "Barrier: " + path + "; " + output + "; " + kind
154+
)
155+
or
156+
exists(string path, string input, string branch, string kind |
157+
barrierGuardModel(path, input, branch, kind, _, madId) and
158+
model = "Barrier guard: " + path + "; " + input + "; " + branch + "; " + kind
159+
)
126160
}
127161

128162
private class SummarizedCallableFromModel extends SummarizedCallable::Range {
@@ -206,6 +240,40 @@ private class FlowSinkFromModel extends FlowSink::Range {
206240
}
207241
}
208242

243+
private class FlowBarrierFromModel extends FlowBarrier::Range {
244+
private string path;
245+
246+
FlowBarrierFromModel() {
247+
barrierModel(path, _, _, _, _) and
248+
this.callResolvesTo(path)
249+
}
250+
251+
override predicate isBarrier(string output, string kind, Provenance provenance, string model) {
252+
exists(QlBuiltins::ExtensionId madId |
253+
barrierModel(path, output, kind, provenance, madId) and
254+
model = "MaD:" + madId.toString()
255+
)
256+
}
257+
}
258+
259+
private class FlowBarrierGuardFromModel extends FlowBarrierGuard::Range {
260+
private string path;
261+
262+
FlowBarrierGuardFromModel() {
263+
barrierGuardModel(path, _, _, _, _, _) and
264+
this.callResolvesTo(path)
265+
}
266+
267+
override predicate isBarrierGuard(
268+
string input, string branch, string kind, Provenance provenance, string model
269+
) {
270+
exists(QlBuiltins::ExtensionId madId |
271+
barrierGuardModel(path, input, branch, kind, provenance, madId) and
272+
model = "MaD:" + madId.toString()
273+
)
274+
}
275+
}
276+
209277
private module Debug {
210278
private import FlowSummaryImpl
211279
private import Private

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,12 @@ class FlowSummaryNode extends Node, TFlowSummaryNode {
8282
result = this.getSummaryNode().getSinkElement()
8383
}
8484

85-
/** Holds is this node is a source node of kind `kind`. */
85+
/** Holds if this node is a source node of kind `kind`. */
8686
predicate isSource(string kind, string model) {
8787
this.getSummaryNode().(FlowSummaryImpl::Private::SourceOutputNode).isEntry(kind, model)
8888
}
8989

90-
/** Holds is this node is a sink node of kind `kind`. */
90+
/** Holds if this node is a sink node of kind `kind`. */
9191
predicate isSink(string kind, string model) {
9292
this.getSummaryNode().(FlowSummaryImpl::Private::SinkInputNode).isExit(kind, model)
9393
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,31 @@ private module Cached {
305305

306306
predicate getABarrierNode = getABarrierNodeImpl/0;
307307
}
308+
309+
bindingset[this]
310+
private signature class ParamSig;
311+
312+
private module WithParam<ParamSig P> {
313+
signature predicate guardChecksSig(AstNode g, Expr e, boolean branch, P param);
314+
}
315+
316+
overlay[global]
317+
cached // nothing is actually cached
318+
module ParameterizedBarrierGuard<ParamSig P, WithParam<P>::guardChecksSig/4 guardChecks> {
319+
private predicate guardChecksAdjTypes(
320+
DataFlowIntegrationInput::Guard g, DataFlowIntegrationInput::Expr e,
321+
DataFlowIntegrationInput::GuardValue branch, P param
322+
) {
323+
guardChecks(g, e, branch, param)
324+
}
325+
326+
private Node getABarrierNodeImpl(P param) {
327+
result =
328+
DataFlowIntegrationImpl::BarrierGuardWithState<P, guardChecksAdjTypes/4>::getABarrierNode(param)
329+
}
330+
331+
predicate getABarrierNode = getABarrierNodeImpl/1;
332+
}
308333
}
309334
}
310335

rust/ql/lib/codeql/rust/dataflow/internal/empty.model.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,13 @@ extensions:
1515
pack: codeql/rust-all
1616
extensible: summaryModel
1717
data: []
18+
19+
- addsTo:
20+
pack: codeql/rust-all
21+
extensible: barrierModel
22+
data: []
23+
24+
- addsTo:
25+
pack: codeql/rust-all
26+
extensible: barrierGuardModel
27+
data: []

0 commit comments

Comments
 (0)