Skip to content

Commit 82e6fbb

Browse files
committed
Swift: Add alert provenance plumbing.
1 parent ba60399 commit 82e6fbb

File tree

4 files changed

+218
-186
lines changed

4 files changed

+218
-186
lines changed

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

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -490,9 +490,9 @@ private module Cached {
490490
* model.
491491
*/
492492
cached
493-
predicate sourceNode(Node node, string kind) {
493+
predicate sourceNode(Node node, string kind, string model) {
494494
exists(SourceSinkInterpretationInput::InterpretNode n |
495-
isSourceNode(n, kind) and n.asNode() = node
495+
isSourceNode(n, kind, model) and n.asNode() = node
496496
)
497497
}
498498

@@ -501,57 +501,76 @@ private module Cached {
501501
* model.
502502
*/
503503
cached
504-
predicate sinkNode(Node node, string kind) {
504+
predicate sinkNode(Node node, string kind, string model) {
505505
exists(SourceSinkInterpretationInput::InterpretNode n |
506-
isSinkNode(n, kind) and n.asNode() = node
506+
isSinkNode(n, kind, model) and n.asNode() = node
507507
)
508508
}
509509
}
510510

511511
import Cached
512512

513+
/**
514+
* Holds if `node` is specified as a source with the given kind in a MaD flow
515+
* model.
516+
*/
517+
predicate sourceNode(Node node, string kind) { sourceNode(node, kind, _) }
518+
519+
/**
520+
* Holds if `node` is specified as a sink with the given kind in a MaD flow
521+
* model.
522+
*/
523+
predicate sinkNode(Node node, string kind) { sinkNode(node, kind, _) }
524+
513525
private predicate interpretSummary(
514-
Function f, string input, string output, string kind, string provenance
526+
Function f, string input, string output, string kind, string provenance, string model
515527
) {
516528
exists(
517529
string namespace, string type, boolean subtypes, string name, string signature, string ext
518530
|
519531
summaryModel(namespace, type, subtypes, name, signature, ext, input, output, kind, provenance) and
532+
model = "" and // TODO: Insert MaD provenance from summaryModel
520533
f = interpretElement(namespace, type, subtypes, name, signature, ext)
521534
)
522535
}
523536

524537
// adapter class for converting Mad summaries to `SummarizedCallable`s
525538
private class SummarizedCallableAdapter extends SummarizedCallable {
526-
SummarizedCallableAdapter() { interpretSummary(this, _, _, _, _) }
539+
SummarizedCallableAdapter() { interpretSummary(this, _, _, _, _, _) }
527540

528-
private predicate relevantSummaryElementManual(string input, string output, string kind) {
541+
private predicate relevantSummaryElementManual(
542+
string input, string output, string kind, string model
543+
) {
529544
exists(Provenance provenance |
530-
interpretSummary(this, input, output, kind, provenance) and
545+
interpretSummary(this, input, output, kind, provenance, model) and
531546
provenance.isManual()
532547
)
533548
}
534549

535-
private predicate relevantSummaryElementGenerated(string input, string output, string kind) {
550+
private predicate relevantSummaryElementGenerated(
551+
string input, string output, string kind, string model
552+
) {
536553
exists(Provenance provenance |
537-
interpretSummary(this, input, output, kind, provenance) and
554+
interpretSummary(this, input, output, kind, provenance, model) and
538555
provenance.isGenerated()
539556
)
540557
}
541558

542-
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
559+
override predicate propagatesFlow(
560+
string input, string output, boolean preservesValue, string model
561+
) {
543562
exists(string kind |
544-
this.relevantSummaryElementManual(input, output, kind)
563+
this.relevantSummaryElementManual(input, output, kind, model)
545564
or
546-
not this.relevantSummaryElementManual(_, _, _) and
547-
this.relevantSummaryElementGenerated(input, output, kind)
565+
not this.relevantSummaryElementManual(_, _, _, _) and
566+
this.relevantSummaryElementGenerated(input, output, kind, model)
548567
|
549568
if kind = "value" then preservesValue = true else preservesValue = false
550569
)
551570
}
552571

553572
override predicate hasProvenance(Provenance provenance) {
554-
interpretSummary(this, _, _, _, provenance)
573+
interpretSummary(this, _, _, _, provenance, _)
555574
}
556575
}
557576

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

Lines changed: 123 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ private import codeql.swift.dataflow.Ssa
77
private import codeql.swift.controlflow.BasicBlocks
88
private import codeql.swift.dataflow.FlowSummary as FlowSummary
99
private import codeql.swift.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
10+
private import codeql.swift.dataflow.ExternalFlow
1011
private import codeql.swift.frameworks.StandardLibrary.PointerTypes
1112
private import codeql.swift.frameworks.StandardLibrary.Array
1213
private import codeql.swift.frameworks.StandardLibrary.Dictionary
@@ -184,142 +185,145 @@ private module Cached {
184185
nodeTo = getParameterDefNode(nodeFrom.asParameter())
185186
}
186187

187-
private predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
188-
exists(Ssa::Definition def |
189-
// Step from assignment RHS to def
190-
def.(Ssa::WriteDefinition).assigns(nodeFrom.getCfgNode()) and
191-
nodeTo.asDefinition() = def
188+
private predicate localFlowStepCommon(Node nodeFrom, Node nodeTo, string model) {
189+
(
190+
exists(Ssa::Definition def |
191+
// Step from assignment RHS to def
192+
def.(Ssa::WriteDefinition).assigns(nodeFrom.getCfgNode()) and
193+
nodeTo.asDefinition() = def
194+
or
195+
// step from def to first read
196+
nodeFrom.asDefinition() = def and
197+
nodeTo.getCfgNode() = def.getAFirstRead() and
198+
(
199+
nodeTo instanceof InoutReturnNodeImpl
200+
implies
201+
nodeTo.(InoutReturnNodeImpl).getParameter() = def.getSourceVariable().asVarDecl()
202+
)
203+
or
204+
// use-use flow
205+
localSsaFlowStepUseUse(def, nodeFrom, nodeTo)
206+
or
207+
localSsaFlowStepUseUse(def, nodeFrom.(PostUpdateNode).getPreUpdateNode(), nodeTo)
208+
or
209+
// step from previous read to Phi node
210+
localFlowSsaInput(nodeFrom, def, nodeTo.asDefinition())
211+
)
192212
or
193-
// step from def to first read
194-
nodeFrom.asDefinition() = def and
195-
nodeTo.getCfgNode() = def.getAFirstRead() and
196-
(
197-
nodeTo instanceof InoutReturnNodeImpl
198-
implies
199-
nodeTo.(InoutReturnNodeImpl).getParameter() = def.getSourceVariable().asVarDecl()
213+
localFlowSsaParamInput(nodeFrom, nodeTo)
214+
or
215+
// flow through `&` (inout argument)
216+
nodeFrom.asExpr() = nodeTo.asExpr().(InOutExpr).getSubExpr()
217+
or
218+
// reverse flow through `&` (inout argument)
219+
nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr().(InOutExpr).getSubExpr() =
220+
nodeTo.(PostUpdateNode).getPreUpdateNode().asExpr()
221+
or
222+
// flow through `try!` and similar constructs
223+
nodeFrom.asExpr() = nodeTo.asExpr().(AnyTryExpr).getSubExpr()
224+
or
225+
// flow through `!`
226+
// note: there's a case in `readStep` that handles when the source is the
227+
// `OptionalSomeContentSet` within the RHS. This case is for when the
228+
// `Optional` itself is tainted (which it usually shouldn't be, but
229+
// retaining this case increases robustness of flow).
230+
nodeFrom.asExpr() = nodeTo.asExpr().(ForceValueExpr).getSubExpr()
231+
or
232+
// read of an optional .some member via `let x: T = y: T?` pattern matching
233+
// note: similar to `ForceValueExpr` this is ideally a content `readStep` but
234+
// in practice we sometimes have taint on the optional itself.
235+
nodeTo.asPattern() = nodeFrom.asPattern().(OptionalSomePattern).getSubPattern()
236+
or
237+
// flow through `?` and `?.`
238+
nodeFrom.asExpr() = nodeTo.asExpr().(BindOptionalExpr).getSubExpr()
239+
or
240+
nodeFrom.asExpr() = nodeTo.asExpr().(OptionalEvaluationExpr).getSubExpr()
241+
or
242+
// flow through unary `+` (which does nothing)
243+
nodeFrom.asExpr() = nodeTo.asExpr().(UnaryPlusExpr).getOperand()
244+
or
245+
// flow through varargs expansions (that wrap an `ArrayExpr` where varargs enter a call)
246+
nodeFrom.asExpr() = nodeTo.asExpr().(VarargExpansionExpr).getSubExpr()
247+
or
248+
// flow through nil-coalescing operator `??`
249+
exists(BinaryExpr nco |
250+
nco.getOperator().(FreeFunction).getName() = "??(_:_:)" and
251+
nodeTo.asExpr() = nco
252+
|
253+
// value argument
254+
nodeFrom.asExpr() = nco.getAnOperand()
255+
or
256+
// unpack closure (the second argument is an `AutoClosureExpr` argument)
257+
nodeFrom.asExpr() = nco.getAnOperand().(AutoClosureExpr).getExpr()
200258
)
201259
or
202-
// use-use flow
203-
localSsaFlowStepUseUse(def, nodeFrom, nodeTo)
260+
// flow through ternary operator `? :`
261+
exists(IfExpr ie |
262+
nodeTo.asExpr() = ie and
263+
nodeFrom.asExpr() = ie.getBranch(_)
264+
)
204265
or
205-
localSsaFlowStepUseUse(def, nodeFrom.(PostUpdateNode).getPreUpdateNode(), nodeTo)
266+
// flow through OpenExistentialExpr (compiler generated expression wrapper)
267+
nodeFrom.asExpr() = nodeTo.asExpr().(OpenExistentialExpr).getSubExpr()
206268
or
207-
// step from previous read to Phi node
208-
localFlowSsaInput(nodeFrom, def, nodeTo.asDefinition())
209-
)
210-
or
211-
localFlowSsaParamInput(nodeFrom, nodeTo)
212-
or
213-
// flow through `&` (inout argument)
214-
nodeFrom.asExpr() = nodeTo.asExpr().(InOutExpr).getSubExpr()
215-
or
216-
// reverse flow through `&` (inout argument)
217-
nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr().(InOutExpr).getSubExpr() =
218-
nodeTo.(PostUpdateNode).getPreUpdateNode().asExpr()
219-
or
220-
// flow through `try!` and similar constructs
221-
nodeFrom.asExpr() = nodeTo.asExpr().(AnyTryExpr).getSubExpr()
222-
or
223-
// flow through `!`
224-
// note: there's a case in `readStep` that handles when the source is the
225-
// `OptionalSomeContentSet` within the RHS. This case is for when the
226-
// `Optional` itself is tainted (which it usually shouldn't be, but
227-
// retaining this case increases robustness of flow).
228-
nodeFrom.asExpr() = nodeTo.asExpr().(ForceValueExpr).getSubExpr()
229-
or
230-
// read of an optional .some member via `let x: T = y: T?` pattern matching
231-
// note: similar to `ForceValueExpr` this is ideally a content `readStep` but
232-
// in practice we sometimes have taint on the optional itself.
233-
nodeTo.asPattern() = nodeFrom.asPattern().(OptionalSomePattern).getSubPattern()
234-
or
235-
// flow through `?` and `?.`
236-
nodeFrom.asExpr() = nodeTo.asExpr().(BindOptionalExpr).getSubExpr()
237-
or
238-
nodeFrom.asExpr() = nodeTo.asExpr().(OptionalEvaluationExpr).getSubExpr()
239-
or
240-
// flow through unary `+` (which does nothing)
241-
nodeFrom.asExpr() = nodeTo.asExpr().(UnaryPlusExpr).getOperand()
242-
or
243-
// flow through varargs expansions (that wrap an `ArrayExpr` where varargs enter a call)
244-
nodeFrom.asExpr() = nodeTo.asExpr().(VarargExpansionExpr).getSubExpr()
245-
or
246-
// flow through nil-coalescing operator `??`
247-
exists(BinaryExpr nco |
248-
nco.getOperator().(FreeFunction).getName() = "??(_:_:)" and
249-
nodeTo.asExpr() = nco
250-
|
251-
// value argument
252-
nodeFrom.asExpr() = nco.getAnOperand()
269+
// flow from Expr to Pattern
270+
exists(Expr e, Pattern p |
271+
nodeFrom.asExpr() = e and
272+
nodeTo.asPattern() = p and
273+
p.getImmediateMatchingExpr() = e
274+
)
253275
or
254-
// unpack closure (the second argument is an `AutoClosureExpr` argument)
255-
nodeFrom.asExpr() = nco.getAnOperand().(AutoClosureExpr).getExpr()
256-
)
257-
or
258-
// flow through ternary operator `? :`
259-
exists(IfExpr ie |
260-
nodeTo.asExpr() = ie and
261-
nodeFrom.asExpr() = ie.getBranch(_)
262-
)
263-
or
264-
// flow through OpenExistentialExpr (compiler generated expression wrapper)
265-
nodeFrom.asExpr() = nodeTo.asExpr().(OpenExistentialExpr).getSubExpr()
266-
or
267-
// flow from Expr to Pattern
268-
exists(Expr e, Pattern p |
269-
nodeFrom.asExpr() = e and
270-
nodeTo.asPattern() = p and
271-
p.getImmediateMatchingExpr() = e
272-
)
273-
or
274-
// flow from Pattern to an identity-preserving sub-Pattern:
275-
nodeTo.asPattern() =
276-
[
277-
nodeFrom.asPattern().(IsPattern).getSubPattern(),
278-
nodeFrom.asPattern().(TypedPattern).getSubPattern()
279-
]
280-
or
281-
// Flow from the last component in a key path chain to
282-
// the return node for the key path.
283-
exists(KeyPathExpr keyPath |
284-
nodeFrom.(KeyPathComponentNodeImpl).getComponent() =
285-
keyPath.getComponent(keyPath.getNumberOfComponents() - 1) and
286-
nodeTo.(KeyPathReturnNodeImpl).getKeyPathExpr() = keyPath
287-
)
288-
or
289-
exists(KeyPathExpr keyPath |
290-
nodeTo.(KeyPathComponentPostUpdateNode).getComponent() =
291-
keyPath.getComponent(keyPath.getNumberOfComponents() - 1) and
292-
nodeFrom.(KeyPathReturnPostUpdateNode).getKeyPathExpr() = keyPath
293-
)
294-
or
295-
// Flow to the result of a keypath assignment
296-
exists(KeyPathApplicationExpr apply, AssignExpr assign |
297-
apply = assign.getDest() and
298-
nodeTo.asExpr() = apply and
299-
nodeFrom.asExpr() = assign.getSource()
300-
)
276+
// flow from Pattern to an identity-preserving sub-Pattern:
277+
nodeTo.asPattern() =
278+
[
279+
nodeFrom.asPattern().(IsPattern).getSubPattern(),
280+
nodeFrom.asPattern().(TypedPattern).getSubPattern()
281+
]
282+
or
283+
// Flow from the last component in a key path chain to
284+
// the return node for the key path.
285+
exists(KeyPathExpr keyPath |
286+
nodeFrom.(KeyPathComponentNodeImpl).getComponent() =
287+
keyPath.getComponent(keyPath.getNumberOfComponents() - 1) and
288+
nodeTo.(KeyPathReturnNodeImpl).getKeyPathExpr() = keyPath
289+
)
290+
or
291+
exists(KeyPathExpr keyPath |
292+
nodeTo.(KeyPathComponentPostUpdateNode).getComponent() =
293+
keyPath.getComponent(keyPath.getNumberOfComponents() - 1) and
294+
nodeFrom.(KeyPathReturnPostUpdateNode).getKeyPathExpr() = keyPath
295+
)
296+
or
297+
// Flow to the result of a keypath assignment
298+
exists(KeyPathApplicationExpr apply, AssignExpr assign |
299+
apply = assign.getDest() and
300+
nodeTo.asExpr() = apply and
301+
nodeFrom.asExpr() = assign.getSource()
302+
)
303+
or
304+
// flow step according to the closure capture library
305+
captureValueStep(nodeFrom, nodeTo)
306+
) and
307+
model = ""
301308
or
302309
// flow through a flow summary (extension of `SummaryModelCsv`)
303310
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
304-
nodeTo.(FlowSummaryNode).getSummaryNode(), true)
305-
or
306-
// flow step according to the closure capture library
307-
captureValueStep(nodeFrom, nodeTo)
311+
nodeTo.(FlowSummaryNode).getSummaryNode(), true, model)
308312
}
309313

310314
/**
311315
* This is the local flow predicate that is used as a building block in global
312316
* data flow.
313317
*/
314318
cached
315-
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
316-
localFlowStepCommon(nodeFrom, nodeTo)
319+
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo, string model) {
320+
localFlowStepCommon(nodeFrom, nodeTo, model)
317321
}
318322

319323
/** This is the local flow predicate that is exposed. */
320324
cached
321325
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
322-
localFlowStepCommon(nodeFrom, nodeTo) or
326+
localFlowStepCommon(nodeFrom, nodeTo, _) or
323327
FlowSummaryImpl::Private::Steps::summaryThroughStepValue(nodeFrom, nodeTo, _)
324328
}
325329

@@ -1396,6 +1400,10 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
13961400
/** Extra data-flow steps needed for lambda flow analysis. */
13971401
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
13981402

1403+
predicate knownSourceModel(Node source, string model) { sourceNode(source, _, model) }
1404+
1405+
predicate knownSinkModel(Node sink, string model) { sinkNode(sink, _, model) }
1406+
13991407
/**
14001408
* Holds if flow is allowed to pass from parameter `p` and back to itself as a
14011409
* side-effect, resulting in a summary from `p` to itself.

0 commit comments

Comments
 (0)