Skip to content

Commit f2767eb

Browse files
authored
Merge pull request #9972 from MathiasVP/swift-taint-through-interpolated-strings
Swift: Taint through interpolated strings
2 parents 10710e2 + 6cfeb24 commit f2767eb

File tree

17 files changed

+545
-43
lines changed

17 files changed

+545
-43
lines changed

swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,21 +96,31 @@ module Stmts {
9696

9797
override predicate propagatesAbnormal(ControlFlowElement node) { none() }
9898

99+
private predicate isBodyOfTapExpr() { any(TapExpr tap).getBody() = ast }
100+
101+
// Note: If the brace statement is the body of a `TapExpr`, the first element is the variable
102+
// declaration (see https://github.com/apple/swift/blob/main/include/swift/AST/Expr.h#L848)
103+
// that's initialized by the `TapExpr`. In `TapExprTree` we've already visited this declaration,
104+
// along with its initializer. So we skip the first element here.
105+
private AstNode getFirstElement() {
106+
if this.isBodyOfTapExpr() then result = ast.getElement(1) else result = ast.getFirstElement()
107+
}
108+
99109
override predicate first(ControlFlowElement first) {
100110
this.firstInner(first)
101111
or
102-
not exists(ast.getFirstElement()) and first.asAstNode() = ast
112+
not exists(this.getFirstElement()) and first.asAstNode() = ast
103113
}
104114

105115
override predicate last(ControlFlowElement last, Completion c) {
106116
this.lastInner(last, c)
107117
or
108-
not exists(ast.getFirstElement()) and
118+
not exists(this.getFirstElement()) and
109119
last.asAstNode() = ast and
110120
c instanceof SimpleCompletion
111121
}
112122

113-
predicate firstInner(ControlFlowElement first) { astFirst(ast.getFirstElement(), first) }
123+
predicate firstInner(ControlFlowElement first) { astFirst(this.getFirstElement(), first) }
114124

115125
/** Gets the body of the i'th `defer` statement. */
116126
private BraceStmt getDeferStmtBody(int i) {
@@ -1334,10 +1344,38 @@ module Exprs {
13341344
override InterpolatedStringLiteralExpr ast;
13351345

13361346
final override ControlFlowElement getChildElement(int i) {
1337-
none() // TODO
1347+
i = 0 and
1348+
result.asAstNode() = ast.getAppendingExpr().getFullyConverted()
1349+
}
1350+
}
1351+
1352+
/** Control-flow for a `TapExpr`. See the QLDoc for `TapExpr` for the semantics of a `TapExpr`. */
1353+
private class TapExprTree extends AstStandardPostOrderTree {
1354+
override TapExpr ast;
1355+
1356+
final override ControlFlowElement getChildElement(int i) {
1357+
// We first visit the local variable declaration.
1358+
i = 0 and
1359+
result.asAstNode() = ast.getVar()
1360+
or
1361+
// Then we visit the expression that gives the local variable its initial value.
1362+
i = 1 and
1363+
result.asAstNode() = ast.getSubExpr().getFullyConverted()
1364+
or
1365+
// And finally, we visit the body that potentially mutates the local variable.
1366+
// Note that the CFG for the body will skip the first element in the
1367+
// body because it's guarenteed to be the variable declaration
1368+
// that we've already visited at i = 0. See the explanation
1369+
// in `BraceStmtTree` for why this is necessary.
1370+
i = 2 and
1371+
result.asAstNode() = ast.getBody()
13381372
}
13391373
}
13401374

1375+
private class OpaqueValueExprTree extends AstLeafTree {
1376+
override OpaqueValueExpr ast;
1377+
}
1378+
13411379
module DeclRefExprs {
13421380
class DeclRefExprLValueTree extends AstLeafTree {
13431381
override DeclRefExpr ast;

swift/ql/lib/codeql/swift/dataflow/Ssa.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ module Ssa {
8080
cached
8181
predicate isInoutDef(ExprCfgNode argument) {
8282
exists(
83-
CallExpr c, BasicBlock bb, int blockIndex, int argIndex, VarDecl v, InOutExpr argExpr // TODO: use CFG node for assignment expr
83+
ApplyExpr c, BasicBlock bb, int blockIndex, int argIndex, VarDecl v, InOutExpr argExpr // TODO: use CFG node for assignment expr
8484
|
8585
this.definesAt(v, bb, blockIndex) and
8686
bb.getNode(blockIndex).getNode().asAstNode() = c and

swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,7 @@ private module Cached {
6464
TExprNode(CfgNode n, Expr e) { hasExprNode(n, e) } or
6565
TSsaDefinitionNode(Ssa::Definition def) or
6666
TInoutReturnNode(ParamDecl param) { param.isInout() } or
67-
TInOutUpdateNode(ParamDecl param, CallExpr call) {
68-
param.isInout() and
69-
call.getStaticTarget() = param.getDeclaringFunction()
70-
} or
67+
TInOutUpdateNode(Argument arg) { arg.getExpr() instanceof InOutExpr } or
7168
TSummaryNode(FlowSummary::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNodeState state)
7269

7370
private predicate hasExprNode(CfgNode n, Expr e) {
@@ -202,7 +199,7 @@ abstract class ArgumentNode extends Node {
202199

203200
private module ArgumentNodes {
204201
class NormalArgumentNode extends ExprNode, ArgumentNode {
205-
NormalArgumentNode() { exists(CallExpr call | call.getAnArgument().getExpr() = this.asExpr()) }
202+
NormalArgumentNode() { exists(ApplyExpr call | call.getAnArgument().getExpr() = this.asExpr()) }
206203

207204
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
208205
call.asCall().getArgument(pos.(PositionalArgumentPosition).getIndex()).getExpr() =
@@ -280,23 +277,22 @@ private module OutNodes {
280277
}
281278

282279
class InOutUpdateNode extends OutNode, TInOutUpdateNode, NodeImpl {
283-
ParamDecl param;
284-
CallExpr call;
280+
Argument arg;
285281

286-
InOutUpdateNode() { this = TInOutUpdateNode(param, call) }
282+
InOutUpdateNode() { this = TInOutUpdateNode(arg) }
287283

288284
override DataFlowCall getCall(ReturnKind kind) {
289-
result.asCall().getExpr() = call and
290-
kind.(ParamReturnKind).getIndex() = param.getIndex()
285+
result.asCall().getExpr() = arg.getApplyExpr() and
286+
kind.(ParamReturnKind).getIndex() = arg.getIndex()
291287
}
292288

293289
override DataFlowCallable getEnclosingCallable() {
294290
result = this.getCall(_).getEnclosingCallable()
295291
}
296292

297-
override Location getLocationImpl() { result = call.getLocation() }
293+
override Location getLocationImpl() { result = arg.getLocation() }
298294

299-
override string toStringImpl() { result = param.toString() }
295+
override string toStringImpl() { result = arg.toString() }
300296
}
301297
}
302298

swift/ql/lib/codeql/swift/dataflow/internal/SsaImplSpecific.qll

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain)
2929
certain = true
3030
)
3131
or
32-
exists(CallExpr call, Argument arg |
32+
exists(ApplyExpr call, Argument arg |
3333
arg.getExpr().(InOutExpr).getSubExpr() = v.getAnAccess() and
3434
call.getAnArgument() = arg and
3535
bb.getNode(i).getNode().asAstNode() = call and
@@ -39,6 +39,13 @@ predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain)
3939
v instanceof ParamDecl and
4040
bb.getNode(i).getNode().asAstNode() = v and
4141
certain = true
42+
or
43+
// Mark the subexpression as a write of the local variable declared in the `TapExpr`.
44+
exists(TapExpr tap |
45+
v = tap.getVar() and
46+
bb.getNode(i).getNode().asAstNode() = tap.getSubExpr() and
47+
certain = true
48+
)
4249
}
4350

4451
private predicate isLValue(DeclRefExpr ref) { any(AssignExpr assign).getDest() = ref }
@@ -58,4 +65,11 @@ predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain)
5865
bb.getScope() = func and
5966
certain = true
6067
)
68+
or
69+
// Mark the `TapExpr` as a read of the of the local variable.
70+
exists(TapExpr tap |
71+
v = tap.getVar() and
72+
bb.getNode(i).getNode().asAstNode() = tap and
73+
certain = true
74+
)
6175
}

swift/ql/lib/codeql/swift/dataflow/internal/TaintTrackingPrivate.qll

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ private import swift
22
private import DataFlowPrivate
33
private import TaintTrackingPublic
44
private import codeql.swift.dataflow.DataFlow
5+
private import codeql.swift.dataflow.Ssa
6+
private import codeql.swift.controlflow.CfgNodes
57

68
/**
79
* Holds if `node` should be a sanitizer in all global taint flow configurations
@@ -16,7 +18,30 @@ private module Cached {
1618
* in all global taint flow configurations.
1719
*/
1820
cached
19-
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { none() }
21+
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
22+
// Flow through one argument of `appendLiteral` and `appendInterpolation` and to the second argument.
23+
// This is needed for string interpolation generated by the compiler. An interpolated string
24+
// like `"I am \(n) years old."` is represented as
25+
// ```
26+
// $interpolated = ""
27+
// appendLiteral(&$interpolated, "I am ")
28+
// appendInterpolation(&$interpolated, n)
29+
// appendLiteral(&$interpolated, " years old.")
30+
// ```
31+
exists(ApplyExpr apply1, ApplyExpr apply2, ExprCfgNode e |
32+
nodeFrom.asExpr() = [apply1, apply2].getAnArgument().getExpr() and
33+
apply1.getFunction() = apply2 and
34+
apply2.getStaticTarget().getName() = ["appendLiteral(_:)", "appendInterpolation(_:)"] and
35+
e.getExpr() = apply2.getAnArgument().getExpr() and
36+
nodeTo.asDefinition().(Ssa::WriteDefinition).isInoutDef(e)
37+
)
38+
or
39+
// Flow from the computation of the interpolated string literal to the result of the interpolation.
40+
exists(InterpolatedStringLiteralExpr interpolated |
41+
nodeTo.asExpr() = interpolated and
42+
nodeFrom.asExpr() = interpolated.getAppendingExpr()
43+
)
44+
}
2045

2146
/**
2247
* Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local

swift/ql/lib/codeql/swift/elements/expr/Argument.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ class Argument extends ArgumentBase {
55
override string toString() { result = this.getLabel() + ": " + this.getExpr().toString() }
66

77
int getIndex() { any(ApplyExpr apply).getArgument(result) = this }
8+
9+
ApplyExpr getApplyExpr() { result.getAnArgument() = this }
810
}
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
// generated by codegen/codegen.py, remove this comment if you wish to edit this file
21
private import codeql.swift.generated.expr.TapExpr
32

3+
/**
4+
* A `TapExpr` is an internal expression generated by the Swift compiler.
5+
*
6+
* If `e` is a `TapExpr`, the semantics of evaluating `e` is:
7+
* 1. Create a local variable `e.getVar()` and assign it the value `e.getSubExpr()`.
8+
* 2. Execute `e.getBody()` which potentially modifies the local variable.
9+
* 3. Return the value of the local variable.
10+
*/
411
class TapExpr extends TapExprBase { }

0 commit comments

Comments
 (0)