Skip to content

Commit c95a2ea

Browse files
committed
Rust: Handle x[y] expressions as *.index(y) calls in data flow
1 parent 50c54de commit c95a2ea

File tree

17 files changed

+506
-343
lines changed

17 files changed

+506
-343
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ private module Input implements InputSig<Location, RustDataFlow> {
2222
n.(Node::PostUpdateNode).getPreUpdateNode().asExpr() = getPostUpdateReverseStep(_, _)
2323
or
2424
FlowSummaryImpl::Private::Steps::sourceLocalStep(_, n, _)
25+
or
26+
assignmentPostUpdateNode(_, n)
2527
}
2628

2729
predicate missingLocationExclude(RustDataFlow::Node n) { not exists(n.asExpr().getLocation()) }

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

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,6 @@ final class ArgumentPosition extends ParameterPosition {
141141
* Holds if `arg` is an argument of `call` at the position `pos`.
142142
*/
143143
predicate isArgumentForCall(Expr arg, Call call, ArgumentPosition pos) {
144-
// TODO: Handle index expressions as calls in data flow.
145-
not call instanceof IndexExpr and
146144
arg = pos.getArgument(call)
147145
}
148146

@@ -316,6 +314,20 @@ private module Aliases {
316314
class LambdaCallKindAlias = LambdaCallKind;
317315
}
318316

317+
/**
318+
* Holds if `n` is the post-update node for the left-hand side of `assignment`.
319+
*
320+
* For such assignments, we add a local flow step from the right-hand side to
321+
* `n`, which means that for assignments like `*x = y`, we get an induced
322+
* reverse `*`-store step into `[post] x`.
323+
*
324+
* This also applies to field assignments, `x.field = y`, and index assignments,
325+
* `x[index] = y`.
326+
*/
327+
predicate assignmentPostUpdateNode(AssignmentExpr assignment, PostUpdateNode n) {
328+
n.getPreUpdateNode().asExpr() = assignment.getLhs()
329+
}
330+
319331
module RustDataFlow implements InputSig<Location> {
320332
private import Aliases
321333
private import codeql.rust.dataflow.DataFlow
@@ -363,7 +375,9 @@ module RustDataFlow implements InputSig<Location> {
363375
node instanceof ClosureParameterNode or
364376
node instanceof DerefBorrowNode or
365377
node instanceof DerefOutNode or
378+
node instanceof IndexOutNode or
366379
node.asExpr() instanceof ParenExpr or
380+
assignmentPostUpdateNode(_, node) or
367381
nodeIsHidden(node.(PostUpdateNode).getPreUpdateNode())
368382
}
369383

@@ -386,6 +400,8 @@ module RustDataFlow implements InputSig<Location> {
386400
node.asPat() = match.getAnArm().getPat()
387401
)
388402
or
403+
node.asExpr() = any(AssignmentExpr ae).getRhs()
404+
or
389405
FlowSummaryImpl::Private::Steps::sourceLocalStep(_, node, _)
390406
or
391407
FlowSummaryImpl::Private::Steps::sinkLocalStep(node, _, _)
@@ -488,6 +504,12 @@ module RustDataFlow implements InputSig<Location> {
488504
.(Node::FlowSummaryNode)
489505
.getSummaryNode(), TOptionalBarrier(_), nodeTo.(Node::FlowSummaryNode).getSummaryNode()) and
490506
model = ""
507+
or
508+
exists(AssignmentExpr assignment |
509+
nodeFrom.asExpr() = assignment.getRhs() and
510+
assignmentPostUpdateNode(assignment, nodeTo) and
511+
model = ""
512+
)
491513
}
492514

493515
/**
@@ -559,12 +581,6 @@ module RustDataFlow implements InputSig<Location> {
559581
access = c.(FieldContent).getAnAccess()
560582
)
561583
or
562-
exists(IndexExpr arr |
563-
c instanceof ElementContent and
564-
node1.asExpr() = arr.getBase() and
565-
node2.asExpr() = arr
566-
)
567-
or
568584
exists(ForExpr for |
569585
c instanceof ElementContent and
570586
node1.asExpr() = for.getIterable() and
@@ -590,6 +606,12 @@ module RustDataFlow implements InputSig<Location> {
590606
node2.asExpr() = deref
591607
)
592608
or
609+
exists(IndexExpr index |
610+
c instanceof ReferenceContent and
611+
node1.(IndexOutNode).getIndexExpr() = index and
612+
node2.asExpr() = index
613+
)
614+
or
593615
// Read from function return
594616
exists(DataFlowCall call |
595617
lambdaCall(call, _, node1) and
@@ -652,9 +674,8 @@ module RustDataFlow implements InputSig<Location> {
652674

653675
pragma[nomagic]
654676
private predicate referenceAssignment(Node node1, Node node2, ReferenceContent c) {
655-
exists(AssignmentExpr assignment, PrefixExpr deref |
677+
exists(AssignmentExpr assignment, DerefExpr deref |
656678
assignment.getLhs() = deref and
657-
deref.getOperatorName() = "*" and
658679
node1.asExpr() = assignment.getRhs() and
659680
node2.asExpr() = deref.getExpr() and
660681
exists(c)
@@ -699,15 +720,17 @@ module RustDataFlow implements InputSig<Location> {
699720
node2.(NameNode).getName() = p.getName()
700721
)
701722
or
702-
fieldAssignment(node1, node2.(PostUpdateNode).getPreUpdateNode(), c)
703-
or
704-
referenceAssignment(node1, node2.(PostUpdateNode).getPreUpdateNode(), c)
705-
or
706-
exists(AssignmentExpr assignment, IndexExpr index |
723+
// Since generalized reverse flow is not yet supported, we add a special case
724+
// for assignments into arrays and slices
725+
exists(IndexExpr index |
726+
node1.(PostUpdateNode).getPreUpdateNode().asExpr() = index and
727+
node2.(PostUpdateNode).getPreUpdateNode().asExpr() = index.getBase() and
707728
c instanceof ElementContent and
708-
assignment.getLhs() = index and
709-
node1.asExpr() = assignment.getRhs() and
710-
node2.(PostUpdateNode).getPreUpdateNode().asExpr() = index.getBase()
729+
index.getResolvedTarget().getCanonicalPath() =
730+
[
731+
"<[;] as core::ops::index::IndexMut>::index_mut",
732+
"<[] as core::ops::index::IndexMut>::index_mut"
733+
]
711734
)
712735
or
713736
referenceExprToExpr(node1, node2, c)
@@ -989,9 +1012,7 @@ private module Cached {
9891012
newtype TDataFlowCall =
9901013
TCall(Call call) {
9911014
Stages::DataFlowStage::ref() and
992-
call.hasEnclosingCfgScope() and
993-
// TODO: Handle index expressions as calls in data flow.
994-
not call instanceof IndexExpr
1015+
call.hasEnclosingCfgScope()
9951016
} or
9961017
TSummaryCall(
9971018
FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver

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

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,8 @@ final private class ExprOutNode extends ExprNode, OutNode {
350350
ExprOutNode() {
351351
exists(Call call |
352352
call = this.asExpr() and
353-
not call instanceof DerefExpr // Handled by `DerefOutNode`
353+
not call instanceof DerefExpr and // Handled by `DerefOutNode`
354+
not call instanceof IndexExpr // Handled by `IndexOutNode`
354355
)
355356
}
356357

@@ -387,6 +388,32 @@ class DerefOutNode extends OutNode, TDerefOutNode {
387388
override string toString() { result = de.toString() + " [pre-dereferenced]" }
388389
}
389390

391+
/**
392+
* A node that represents the value of a `x[y]` expression _before_ implicit
393+
* dereferencing:
394+
*
395+
* `x[y]` equivalent to `*x.index(y)`, and this node represents the
396+
* `x.index(y)` part.
397+
*/
398+
class IndexOutNode extends OutNode, TIndexOutNode {
399+
IndexExpr ie;
400+
401+
IndexOutNode() { this = TIndexOutNode(ie, false) }
402+
403+
IndexExpr getIndexExpr() { result = ie }
404+
405+
override CfgScope getCfgScope() { result = ie.getEnclosingCfgScope() }
406+
407+
override DataFlowCall getCall(ReturnKind kind) {
408+
result.asCall() = ie and
409+
kind = TNormalReturnKind()
410+
}
411+
412+
override Location getLocation() { result = ie.getLocation() }
413+
414+
override string toString() { result = ie.toString() + " [pre-dereferenced]" }
415+
}
416+
390417
final class SummaryOutNode extends FlowSummaryNode, OutNode {
391418
private DataFlowCall call;
392419
private ReturnKind kind_;
@@ -476,6 +503,18 @@ class DerefOutPostUpdateNode extends PostUpdateNode, TDerefOutNode {
476503
override Location getLocation() { result = de.getLocation() }
477504
}
478505

506+
class IndexOutPostUpdateNode extends PostUpdateNode, TIndexOutNode {
507+
IndexExpr ie;
508+
509+
IndexOutPostUpdateNode() { this = TIndexOutNode(ie, true) }
510+
511+
override IndexOutNode getPreUpdateNode() { result = TIndexOutNode(ie, false) }
512+
513+
override CfgScope getCfgScope() { result = ie.getEnclosingCfgScope() }
514+
515+
override Location getLocation() { result = ie.getLocation() }
516+
}
517+
479518
final class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNode {
480519
private FlowSummaryNode pre;
481520

@@ -526,7 +565,9 @@ newtype TNode =
526565
or
527566
e =
528567
[
529-
any(IndexExpr i).getBase(), //
568+
any(DerefExpr de), //
569+
any(IndexExpr ie), //
570+
any(FieldExpr fe), //
530571
any(FieldExpr access).getContainer(), //
531572
any(TryExpr try).getExpr(), //
532573
any(AwaitExpr a).getExpr(), //
@@ -542,6 +583,7 @@ newtype TNode =
542583
borrow = true
543584
} or
544585
TDerefOutNode(DerefExpr de, Boolean isPost) or
586+
TIndexOutNode(IndexExpr ie, Boolean isPost) or
545587
TSsaNode(SsaImpl::DataFlowIntegration::SsaNode node) or
546588
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) {
547589
forall(AstNode n | n = sn.getSinkElement() or n = sn.getSourceElement() |

rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -676,12 +676,12 @@ module Impl {
676676
predicate isCapture() { this.getEnclosingCfgScope() != v.getEnclosingCfgScope() }
677677
}
678678

679-
/** Holds if `e` occurs in the LHS of an assignment or compound assignment. */
680-
private predicate assignmentExprDescendant(AssignmentExpr ae, Expr e) {
681-
e = ae.getLhs()
679+
/** Holds if `e` occurs in the LHS of an assignment operation. */
680+
predicate assignmentOperationDescendant(AssignmentOperation ao, Expr e) {
681+
e = ao.getLhs()
682682
or
683683
exists(Expr mid |
684-
assignmentExprDescendant(ae, mid) and
684+
assignmentOperationDescendant(ao, mid) and
685685
getImmediateParentAdj(e) = mid and
686686
not mid instanceof DerefExpr and
687687
not mid instanceof FieldExpr and
@@ -696,7 +696,7 @@ module Impl {
696696
cached
697697
VariableWriteAccess() {
698698
Cached::ref() and
699-
assignmentExprDescendant(ae, this)
699+
assignmentOperationDescendant(ae, this)
700700
}
701701

702702
/** Gets the assignment expression that has this write access in the left-hand side. */

rust/ql/lib/codeql/rust/frameworks/stdlib/core.model.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ extensions:
66
# Builtin deref
77
- ["<& as core::ops::deref::Deref>::deref", "Argument[self].Reference", "ReturnValue", "value", "manual"]
88
- ["<&mut as core::ops::deref::Deref>::deref", "Argument[self].Reference", "ReturnValue", "value", "manual"]
9+
# Index
10+
- ["<_ as core::ops::index::Index>::index", "Argument[self].Reference.Element", "ReturnValue.Reference", "value", "manual"]
11+
- ["<_ as core::ops::index::IndexMut>::index_mut", "Argument[self].Reference.Element", "ReturnValue.Reference", "value", "manual"]
912
# Arithmetic
1013
- ["<_ as core::ops::arith::Add>::add", "Argument[self]", "ReturnValue", "taint", "manual"]
1114
- ["<_ as core::ops::arith::Add>::add", "Argument[0]", "ReturnValue", "taint", "manual"]

rust/ql/lib/codeql/rust/internal/TypeInference.qll

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ private import TypeMention
99
private import typeinference.FunctionType
1010
private import typeinference.FunctionOverloading as FunctionOverloading
1111
private import typeinference.BlanketImplementation as BlanketImplementation
12+
private import codeql.rust.elements.internal.VariableImpl::Impl as VariableImpl
1213
private import codeql.rust.internal.CachedStages
1314
private import codeql.typeinference.internal.TypeInference
1415
private import codeql.rust.frameworks.stdlib.Stdlib
@@ -1636,9 +1637,14 @@ private module MethodResolution {
16361637
}
16371638

16381639
private class MethodCallIndexExpr extends MethodCall instanceof IndexExpr {
1640+
private predicate isInMutableContext() {
1641+
// todo: does not handle all cases yet
1642+
VariableImpl::assignmentOperationDescendant(_, this)
1643+
}
1644+
16391645
pragma[nomagic]
16401646
override predicate hasNameAndArity(string name, int arity) {
1641-
name = "index" and
1647+
(if this.isInMutableContext() then name = "index_mut" else name = "index") and
16421648
arity = 1
16431649
}
16441650

@@ -1652,7 +1658,11 @@ private module MethodResolution {
16521658

16531659
override predicate supportsAutoDerefAndBorrow() { any() }
16541660

1655-
override Trait getTrait() { result.getCanonicalPath() = "core::ops::index::Index" }
1661+
override Trait getTrait() {
1662+
if this.isInMutableContext()
1663+
then result.getCanonicalPath() = "core::ops::index::IndexMut"
1664+
else result.getCanonicalPath() = "core::ops::index::Index"
1665+
}
16561666
}
16571667

16581668
private class MethodCallCallExpr extends MethodCall instanceof CallExpr {

0 commit comments

Comments
 (0)