Skip to content

Commit ff2ea7e

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

File tree

17 files changed

+505
-341
lines changed

17 files changed

+505
-341
lines changed

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

Lines changed: 51 additions & 24 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,30 @@ private module Aliases {
316314
class LambdaCallKindAlias = LambdaCallKind;
317315
}
318316

317+
/**
318+
* Index assignments like `a[i] = rhs` are treated as `*a.index_mut(i) = rhs`,
319+
* so they should in principle be handled by `referenceAssignment`.
320+
*
321+
* However, this would require support for [generalized reverse flow][1], which
322+
* is not yet implemented, so instead we simulate reverse flow where it would
323+
* have applied via the model for `<_ as core::ops::index::IndexMut>::index_mut`.
324+
*
325+
* The same is the case for compound assignments like `a[i] += rhs`, which are
326+
* treated as `(*a.index_mut(i)).add_assign(rhs)`.
327+
*
328+
* [1]: https://github.com/github/codeql/pull/18109
329+
*/
330+
predicate indexAssignment(
331+
AssignmentOperation assignment, IndexExpr index, Node rhs, PostUpdateNode base, Content c
332+
) {
333+
assignment.getLhs() = index and
334+
rhs.asExpr() = assignment.getRhs() and
335+
base.getPreUpdateNode().asExpr() = index.getBase() and
336+
c instanceof ElementContent and
337+
// simulate that the flow summary applies
338+
not index.getResolvedTarget().fromSource()
339+
}
340+
319341
module RustDataFlow implements InputSig<Location> {
320342
private import Aliases
321343
private import codeql.rust.dataflow.DataFlow
@@ -363,6 +385,7 @@ module RustDataFlow implements InputSig<Location> {
363385
node instanceof ClosureParameterNode or
364386
node instanceof DerefBorrowNode or
365387
node instanceof DerefOutNode or
388+
node instanceof IndexOutNode or
366389
node.asExpr() instanceof ParenExpr or
367390
nodeIsHidden(node.(PostUpdateNode).getPreUpdateNode())
368391
}
@@ -559,12 +582,6 @@ module RustDataFlow implements InputSig<Location> {
559582
access = c.(FieldContent).getAnAccess()
560583
)
561584
or
562-
exists(IndexExpr arr |
563-
c instanceof ElementContent and
564-
node1.asExpr() = arr.getBase() and
565-
node2.asExpr() = arr
566-
)
567-
or
568585
exists(ForExpr for |
569586
c instanceof ElementContent and
570587
node1.asExpr() = for.getIterable() and
@@ -590,6 +607,12 @@ module RustDataFlow implements InputSig<Location> {
590607
node2.asExpr() = deref
591608
)
592609
or
610+
exists(IndexExpr index |
611+
c instanceof ReferenceContent and
612+
node1.(IndexOutNode).getIndexExpr() = index and
613+
node2.asExpr() = index
614+
)
615+
or
593616
// Read from function return
594617
exists(DataFlowCall call |
595618
lambdaCall(call, _, node1) and
@@ -651,13 +674,19 @@ module RustDataFlow implements InputSig<Location> {
651674
}
652675

653676
pragma[nomagic]
654-
private predicate referenceAssignment(Node node1, Node node2, ReferenceContent c) {
655-
exists(AssignmentExpr assignment, PrefixExpr deref |
656-
assignment.getLhs() = deref and
657-
deref.getOperatorName() = "*" and
677+
private predicate referenceAssignment(
678+
Node node1, Node node2, Expr lhs, boolean clears, ReferenceContent c
679+
) {
680+
exists(AssignmentExpr assignment |
681+
assignment.getLhs() = lhs and
658682
node1.asExpr() = assignment.getRhs() and
659-
node2.asExpr() = deref.getExpr() and
660683
exists(c)
684+
|
685+
node2.(DerefOutNode).getDerefExpr() = lhs and
686+
clears = true
687+
or
688+
node2.(IndexOutNode).getIndexExpr() = lhs and
689+
clears = false
661690
)
662691
}
663692

@@ -701,14 +730,14 @@ module RustDataFlow implements InputSig<Location> {
701730
or
702731
fieldAssignment(node1, node2.(PostUpdateNode).getPreUpdateNode(), c)
703732
or
704-
referenceAssignment(node1, node2.(PostUpdateNode).getPreUpdateNode(), c)
733+
referenceAssignment(node1, node2.(PostUpdateNode).getPreUpdateNode(), _, _, c)
705734
or
706-
exists(AssignmentExpr assignment, IndexExpr index |
707-
c instanceof ElementContent and
708-
assignment.getLhs() = index and
709-
node1.asExpr() = assignment.getRhs() and
710-
node2.(PostUpdateNode).getPreUpdateNode().asExpr() = index.getBase()
711-
)
735+
indexAssignment(any(AssignmentExpr ae), _, node1, node2, c)
736+
or
737+
// Compund assignment like `a[i] += rhs` are modeled as a store step from `rhs`
738+
// to `[post] a[i]`, followed by a taint step into `[post] a`.
739+
indexAssignment(any(CompoundAssignmentExpr cae),
740+
node2.(PostUpdateNode).getPreUpdateNode().asExpr(), node1, _, c)
712741
or
713742
referenceExprToExpr(node1, node2, c)
714743
or
@@ -745,7 +774,7 @@ module RustDataFlow implements InputSig<Location> {
745774
predicate clearsContent(Node n, ContentSet cs) {
746775
fieldAssignment(_, n, cs.(SingletonContentSet).getContent())
747776
or
748-
referenceAssignment(_, n, cs.(SingletonContentSet).getContent())
777+
referenceAssignment(_, _, n.asExpr(), true, cs.(SingletonContentSet).getContent())
749778
or
750779
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), cs)
751780
or
@@ -989,9 +1018,7 @@ private module Cached {
9891018
newtype TDataFlowCall =
9901019
TCall(Call call) {
9911020
Stages::DataFlowStage::ref() and
992-
call.hasEnclosingCfgScope() and
993-
// TODO: Handle index expressions as calls in data flow.
994-
not call instanceof IndexExpr
1021+
call.hasEnclosingCfgScope()
9951022
} or
9961023
TSummaryCall(
9971024
FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver

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

Lines changed: 43 additions & 3 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

@@ -514,7 +553,8 @@ newtype TNode =
514553
TExprPostUpdateNode(Expr e) {
515554
e.hasEnclosingCfgScope() and
516555
(
517-
isArgumentForCall(e, _, _)
556+
isArgumentForCall(e, _, _) and
557+
not (e = any(CompoundAssignmentExpr cae).getLhs() and e instanceof VariableAccess)
518558
or
519559
lambdaCallExpr(_, _, e)
520560
or
@@ -526,7 +566,6 @@ newtype TNode =
526566
or
527567
e =
528568
[
529-
any(IndexExpr i).getBase(), //
530569
any(FieldExpr access).getContainer(), //
531570
any(TryExpr try).getExpr(), //
532571
any(AwaitExpr a).getExpr(), //
@@ -542,6 +581,7 @@ newtype TNode =
542581
borrow = true
543582
} or
544583
TDerefOutNode(DerefExpr de, Boolean isPost) or
584+
TIndexOutNode(IndexExpr ie, Boolean isPost) or
545585
TSsaNode(SsaImpl::DataFlowIntegration::SsaNode node) or
546586
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) {
547587
forall(AstNode n | n = sn.getSinkElement() or n = sn.getSourceElement() |

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ module RustTaintTracking implements InputSig<Location, RustDataFlow> {
6565
or
6666
succ.(Node::PostUpdateNode).getPreUpdateNode().asExpr() =
6767
getPostUpdateReverseStep(pred.(Node::PostUpdateNode).getPreUpdateNode().asExpr(), false)
68+
or
69+
indexAssignment(any(CompoundAssignmentExpr cae),
70+
pred.(Node::PostUpdateNode).getPreUpdateNode().asExpr(), _, succ, _)
6871
)
6972
or
7073
FlowSummaryImpl::Private::Steps::summaryLocalStep(pred.(Node::FlowSummaryNode).getSummaryNode(),

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: 13 additions & 3 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
@@ -665,7 +666,7 @@ private predicate typeEquality(AstNode n1, TypePath prefix1, AstNode n2, TypePat
665666

666667
/**
667668
* Holds if `child` is a child of `parent`, and the Rust compiler applies [least
668-
* upper bound (LUB) coercion](1) to infer the type of `parent` from the type of
669+
* upper bound (LUB) coercion][1] to infer the type of `parent` from the type of
669670
* `child`.
670671
*
671672
* In this case, we want type information to only flow from `child` to `parent`,
@@ -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)