Skip to content

Commit 9dd7b20

Browse files
authored
Merge pull request #18960 from github/aibaars/rust-tainted-path
Rust: TaintedPath query
2 parents 0a0ec18 + bf76505 commit 9dd7b20

30 files changed

+707
-13
lines changed

python/ql/lib/semmle/python/Concepts.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ module Path {
181181
}
182182
}
183183

184-
/** A data-flow node that checks that a path is safe to access. */
184+
/** A data-flow node that checks that a path is safe to access in some way, for example by having a controlled prefix. */
185185
class SafeAccessCheck extends DataFlow::ExprNode {
186186
SafeAccessCheck() { this = DataFlow::BarrierGuard<safeAccessCheck/3>::getABarrierNode() }
187187
}
@@ -192,7 +192,7 @@ module Path {
192192

193193
/** Provides a class for modeling new path safety checks. */
194194
module SafeAccessCheck {
195-
/** A data-flow node that checks that a path is safe to access. */
195+
/** A data-flow node that checks that a path is safe to access in some way, for example by having a controlled prefix. */
196196
abstract class Range extends DataFlow::GuardNode {
197197
/** Holds if this guard validates `node` upon evaluating to `branch`. */
198198
abstract predicate checks(ControlFlowNode node, boolean branch);

rust/ql/integration-tests/hello-project/summary.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
| Macro calls - resolved | 2 |
1616
| Macro calls - total | 2 |
1717
| Macro calls - unresolved | 0 |
18-
| Taint edges - number of edges | 1671 |
18+
| Taint edges - number of edges | 1674 |
1919
| Taint reach - nodes tainted | 0 |
2020
| Taint reach - per million nodes | 0 |
2121
| Taint sinks - cryptographic operations | 0 |

rust/ql/integration-tests/hello-workspace/summary.cargo.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
| Macro calls - resolved | 2 |
1616
| Macro calls - total | 2 |
1717
| Macro calls - unresolved | 0 |
18-
| Taint edges - number of edges | 1671 |
18+
| Taint edges - number of edges | 1674 |
1919
| Taint reach - nodes tainted | 0 |
2020
| Taint reach - per million nodes | 0 |
2121
| Taint sinks - cryptographic operations | 0 |

rust/ql/integration-tests/hello-workspace/summary.rust-project.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
| Macro calls - resolved | 2 |
1616
| Macro calls - total | 2 |
1717
| Macro calls - unresolved | 0 |
18-
| Taint edges - number of edges | 1671 |
18+
| Taint edges - number of edges | 1674 |
1919
| Taint reach - nodes tainted | 0 |
2020
| Taint reach - per million nodes | 0 |
2121
| Taint sinks - cryptographic operations | 0 |

rust/ql/lib/codeql/rust/Concepts.qll

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ private import codeql.rust.dataflow.DataFlow
88
private import codeql.threatmodels.ThreatModels
99
private import codeql.rust.Frameworks
1010
private import codeql.rust.dataflow.FlowSource
11+
private import codeql.rust.controlflow.ControlFlowGraph as Cfg
12+
private import codeql.rust.controlflow.CfgNodes as CfgNodes
1113

1214
/**
1315
* A data flow source for a specific threat-model.
@@ -264,3 +266,38 @@ module Cryptography {
264266

265267
class CryptographicAlgorithm = SC::CryptographicAlgorithm;
266268
}
269+
270+
/** Provides classes for modeling path-related APIs. */
271+
module Path {
272+
final class PathNormalization = PathNormalization::Range;
273+
274+
/** Provides a class for modeling new path normalization APIs. */
275+
module PathNormalization {
276+
/**
277+
* A data-flow node that performs path normalization. This is often needed in order
278+
* to safely access paths.
279+
*/
280+
abstract class Range extends DataFlow::Node {
281+
/** Gets an argument to this path normalization that is interpreted as a path. */
282+
abstract DataFlow::Node getPathArg();
283+
}
284+
}
285+
286+
/** A data-flow node that checks that a path is safe to access in some way, for example by having a controlled prefix. */
287+
class SafeAccessCheck extends DataFlow::ExprNode {
288+
SafeAccessCheck() { this = DataFlow::BarrierGuard<safeAccessCheck/3>::getABarrierNode() }
289+
}
290+
291+
private predicate safeAccessCheck(CfgNodes::AstCfgNode g, Cfg::CfgNode node, boolean branch) {
292+
g.(SafeAccessCheck::Range).checks(node, branch)
293+
}
294+
295+
/** Provides a class for modeling new path safety checks. */
296+
module SafeAccessCheck {
297+
/** A data-flow node that checks that a path is safe to access in some way, for example by having a controlled prefix. */
298+
abstract class Range extends CfgNodes::AstCfgNode {
299+
/** Holds if this guard validates `node` upon evaluating to `branch`. */
300+
abstract predicate checks(Cfg::CfgNode node, boolean branch);
301+
}
302+
}
303+
}

rust/ql/lib/codeql/rust/Frameworks.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33
*/
44

55
private import codeql.rust.frameworks.rustcrypto.RustCrypto
6+
private import codeql.rust.frameworks.Poem
67
private import codeql.rust.frameworks.Sqlx
78
private import codeql.rust.frameworks.stdlib.Clone
9+
private import codeql.rust.frameworks.stdlib.Stdlib

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ private import codeql.dataflow.DataFlow
88
private import internal.DataFlowImpl as DataFlowImpl
99
private import internal.Node as Node
1010
private import internal.Content as Content
11+
private import codeql.rust.controlflow.ControlFlowGraph as Cfg
12+
private import codeql.rust.controlflow.CfgNodes as CfgNodes
1113

1214
/**
1315
* Provides classes for performing local (intra-procedural) and global
@@ -16,6 +18,8 @@ private import internal.Content as Content
1618
module DataFlow {
1719
final class Node = Node::NodePublic;
1820

21+
final class ExprNode = Node::ExprNode;
22+
1923
/**
2024
* The value of a parameter at function entry, viewed as a node in a data
2125
* flow graph.
@@ -56,4 +60,31 @@ module DataFlow {
5660
predicate localFlow(Node::Node source, Node::Node sink) { localFlowStep*(source, sink) }
5761

5862
import DataFlowMake<Location, DataFlowImpl::RustDataFlow>
63+
64+
/**
65+
* Holds if the guard `g` validates the expression `e` upon evaluating to `v`.
66+
*
67+
* The expression `e` is expected to be a syntactic part of the guard `g`.
68+
* For example, the guard `g` might be a call `isSafe(x)` and the expression `e`
69+
* the argument `x`.
70+
*/
71+
signature predicate guardChecksSig(CfgNodes::AstCfgNode g, Cfg::CfgNode e, boolean branch);
72+
73+
/**
74+
* Provides a set of barrier nodes for a guard that validates an expression.
75+
*
76+
* This is expected to be used in `isBarrier`/`isSanitizer` definitions
77+
* in data flow and taint tracking.
78+
*/
79+
module BarrierGuard<guardChecksSig/3 guardChecks> {
80+
private import internal.DataFlowImpl::SsaFlow as SsaFlow
81+
private import internal.SsaImpl as SsaImpl
82+
83+
/** Gets a node that is safely guarded by the given guard check. */
84+
pragma[nomagic]
85+
Node getABarrierNode() {
86+
SsaFlow::asNode(result) =
87+
SsaImpl::DataFlowIntegration::BarrierGuard<guardChecks/3>::getABarrierNode()
88+
}
89+
}
5990
}

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,41 @@ final class SingletonContentSet extends ContentSet, TSingletonContentSet {
214214
override Content getAReadContent() { result = c }
215215
}
216216

217+
/**
218+
* A step in a flow summary defined using `OptionalStep[name]`. An `OptionalStep` is "opt-in", which means
219+
* that by default the step is not present in the flow summary and needs to be explicitly enabled by defining
220+
* an additional flow step.
221+
*/
222+
final class OptionalStep extends ContentSet, TOptionalStep {
223+
override string toString() {
224+
exists(string name |
225+
this = TOptionalStep(name) and
226+
result = "OptionalStep[" + name + "]"
227+
)
228+
}
229+
230+
override Content getAStoreContent() { none() }
231+
232+
override Content getAReadContent() { none() }
233+
}
234+
235+
/**
236+
* A step in a flow summary defined using `OptionalBarrier[name]`. An `OptionalBarrier` is "opt-out", by default
237+
* data can flow freely through the step. Flow through the step can be explicity blocked by defining its node as a barrier.
238+
*/
239+
final class OptionalBarrier extends ContentSet, TOptionalBarrier {
240+
override string toString() {
241+
exists(string name |
242+
this = TOptionalBarrier(name) and
243+
result = "OptionalBarrier[" + name + "]"
244+
)
245+
}
246+
247+
override Content getAStoreContent() { none() }
248+
249+
override Content getAReadContent() { none() }
250+
}
251+
217252
private import codeql.rust.internal.CachedStages
218253

219254
cached

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

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,12 @@ module RustDataFlow implements InputSig<Location> {
624624
model = ""
625625
or
626626
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, model)
627+
or
628+
// Add flow through optional barriers. This step is then blocked by the barrier for queries that choose to use the barrier.
629+
FlowSummaryImpl::Private::Steps::summaryReadStep(nodeFrom
630+
.(Node::FlowSummaryNode)
631+
.getSummaryNode(), TOptionalBarrier(_), nodeTo.(Node::FlowSummaryNode).getSummaryNode()) and
632+
model = ""
627633
}
628634

629635
/**
@@ -753,7 +759,17 @@ module RustDataFlow implements InputSig<Location> {
753759
)
754760
or
755761
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), cs,
756-
node2.(FlowSummaryNode).getSummaryNode())
762+
node2.(FlowSummaryNode).getSummaryNode()) and
763+
not isSpecialContentSet(cs)
764+
}
765+
766+
/**
767+
* Holds if `cs` is used to encode a special operation as a content component, but should not
768+
* be treated as an ordinary content component.
769+
*/
770+
private predicate isSpecialContentSet(ContentSet cs) {
771+
cs instanceof TOptionalStep or
772+
cs instanceof TOptionalBarrier
757773
}
758774

759775
pragma[nomagic]
@@ -850,7 +866,8 @@ module RustDataFlow implements InputSig<Location> {
850866
storeContentStep(node1, cs.(SingletonContentSet).getContent(), node2)
851867
or
852868
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), cs,
853-
node2.(FlowSummaryNode).getSummaryNode())
869+
node2.(FlowSummaryNode).getSummaryNode()) and
870+
not isSpecialContentSet(cs)
854871
}
855872

856873
/**
@@ -1136,7 +1153,14 @@ private module Cached {
11361153
newtype TReturnKind = TNormalReturnKind()
11371154

11381155
cached
1139-
newtype TContentSet = TSingletonContentSet(Content c)
1156+
newtype TContentSet =
1157+
TSingletonContentSet(Content c) or
1158+
TOptionalStep(string name) {
1159+
name = any(FlowSummaryImpl::Private::AccessPathToken tok).getAnArgument("OptionalStep")
1160+
} or
1161+
TOptionalBarrier(string name) {
1162+
name = any(FlowSummaryImpl::Private::AccessPathToken tok).getAnArgument("OptionalBarrier")
1163+
}
11401164

11411165
/** Holds if `n` is a flow source of kind `kind`. */
11421166
cached
@@ -1145,6 +1169,27 @@ private module Cached {
11451169
/** Holds if `n` is a flow sink of kind `kind`. */
11461170
cached
11471171
predicate sinkNode(Node n, string kind) { n.(FlowSummaryNode).isSink(kind, _) }
1172+
1173+
/**
1174+
* A step in a flow summary defined using `OptionalStep[name]`. An `OptionalStep` is "opt-in", which means
1175+
* that by default the step is not present in the flow summary and needs to be explicitly enabled by defining
1176+
* an additional flow step.
1177+
*/
1178+
cached
1179+
predicate optionalStep(Node node1, string name, Node node2) {
1180+
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(),
1181+
TOptionalStep(name), node2.(FlowSummaryNode).getSummaryNode())
1182+
}
1183+
1184+
/**
1185+
* A step in a flow summary defined using `OptionalBarrier[name]`. An `OptionalBarrier` is "opt-out", by default
1186+
* data can flow freely through the step. Flow through the step can be explicity blocked by defining its node as a barrier.
1187+
*/
1188+
cached
1189+
predicate optionalBarrier(Node node, string name) {
1190+
FlowSummaryImpl::Private::Steps::summaryReadStep(_, TOptionalBarrier(name),
1191+
node.(FlowSummaryNode).getSummaryNode())
1192+
}
11481193
}
11491194

11501195
import Cached

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ module Input implements InputSig<Location, RustDataFlow> {
107107
c = TFutureContent() and
108108
arg = ""
109109
)
110+
or
111+
cs = TOptionalStep(arg) and result = "OptionalStep"
112+
or
113+
cs = TOptionalBarrier(arg) and result = "OptionalBarrier"
110114
}
111115

112116
string encodeReturn(ReturnKind rk, string arg) { none() }

0 commit comments

Comments
 (0)