Swift: Add alert provenance plumbing.

This commit is contained in:
Anders Schack-Mulligen
2024-02-05 15:45:28 +01:00
parent ba6039946b
commit 82e6fbbd22
4 changed files with 219 additions and 187 deletions

View File

@@ -490,9 +490,9 @@ private module Cached {
* model.
*/
cached
predicate sourceNode(Node node, string kind) {
predicate sourceNode(Node node, string kind, string model) {
exists(SourceSinkInterpretationInput::InterpretNode n |
isSourceNode(n, kind) and n.asNode() = node
isSourceNode(n, kind, model) and n.asNode() = node
)
}
@@ -501,57 +501,76 @@ private module Cached {
* model.
*/
cached
predicate sinkNode(Node node, string kind) {
predicate sinkNode(Node node, string kind, string model) {
exists(SourceSinkInterpretationInput::InterpretNode n |
isSinkNode(n, kind) and n.asNode() = node
isSinkNode(n, kind, model) and n.asNode() = node
)
}
}
import Cached
/**
* Holds if `node` is specified as a source with the given kind in a MaD flow
* model.
*/
predicate sourceNode(Node node, string kind) { sourceNode(node, kind, _) }
/**
* Holds if `node` is specified as a sink with the given kind in a MaD flow
* model.
*/
predicate sinkNode(Node node, string kind) { sinkNode(node, kind, _) }
private predicate interpretSummary(
Function f, string input, string output, string kind, string provenance
Function f, string input, string output, string kind, string provenance, string model
) {
exists(
string namespace, string type, boolean subtypes, string name, string signature, string ext
|
summaryModel(namespace, type, subtypes, name, signature, ext, input, output, kind, provenance) and
model = "" and // TODO: Insert MaD provenance from summaryModel
f = interpretElement(namespace, type, subtypes, name, signature, ext)
)
}
// adapter class for converting Mad summaries to `SummarizedCallable`s
private class SummarizedCallableAdapter extends SummarizedCallable {
SummarizedCallableAdapter() { interpretSummary(this, _, _, _, _) }
SummarizedCallableAdapter() { interpretSummary(this, _, _, _, _, _) }
private predicate relevantSummaryElementManual(string input, string output, string kind) {
private predicate relevantSummaryElementManual(
string input, string output, string kind, string model
) {
exists(Provenance provenance |
interpretSummary(this, input, output, kind, provenance) and
interpretSummary(this, input, output, kind, provenance, model) and
provenance.isManual()
)
}
private predicate relevantSummaryElementGenerated(string input, string output, string kind) {
private predicate relevantSummaryElementGenerated(
string input, string output, string kind, string model
) {
exists(Provenance provenance |
interpretSummary(this, input, output, kind, provenance) and
interpretSummary(this, input, output, kind, provenance, model) and
provenance.isGenerated()
)
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
override predicate propagatesFlow(
string input, string output, boolean preservesValue, string model
) {
exists(string kind |
this.relevantSummaryElementManual(input, output, kind)
this.relevantSummaryElementManual(input, output, kind, model)
or
not this.relevantSummaryElementManual(_, _, _) and
this.relevantSummaryElementGenerated(input, output, kind)
not this.relevantSummaryElementManual(_, _, _, _) and
this.relevantSummaryElementGenerated(input, output, kind, model)
|
if kind = "value" then preservesValue = true else preservesValue = false
)
}
override predicate hasProvenance(Provenance provenance) {
interpretSummary(this, _, _, _, provenance)
interpretSummary(this, _, _, _, provenance, _)
}
}

View File

@@ -7,6 +7,7 @@ private import codeql.swift.dataflow.Ssa
private import codeql.swift.controlflow.BasicBlocks
private import codeql.swift.dataflow.FlowSummary as FlowSummary
private import codeql.swift.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
private import codeql.swift.dataflow.ExternalFlow
private import codeql.swift.frameworks.StandardLibrary.PointerTypes
private import codeql.swift.frameworks.StandardLibrary.Array
private import codeql.swift.frameworks.StandardLibrary.Dictionary
@@ -184,127 +185,130 @@ private module Cached {
nodeTo = getParameterDefNode(nodeFrom.asParameter())
}
private predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
exists(Ssa::Definition def |
// Step from assignment RHS to def
def.(Ssa::WriteDefinition).assigns(nodeFrom.getCfgNode()) and
nodeTo.asDefinition() = def
or
// step from def to first read
nodeFrom.asDefinition() = def and
nodeTo.getCfgNode() = def.getAFirstRead() and
(
nodeTo instanceof InoutReturnNodeImpl
implies
nodeTo.(InoutReturnNodeImpl).getParameter() = def.getSourceVariable().asVarDecl()
private predicate localFlowStepCommon(Node nodeFrom, Node nodeTo, string model) {
(
exists(Ssa::Definition def |
// Step from assignment RHS to def
def.(Ssa::WriteDefinition).assigns(nodeFrom.getCfgNode()) and
nodeTo.asDefinition() = def
or
// step from def to first read
nodeFrom.asDefinition() = def and
nodeTo.getCfgNode() = def.getAFirstRead() and
(
nodeTo instanceof InoutReturnNodeImpl
implies
nodeTo.(InoutReturnNodeImpl).getParameter() = def.getSourceVariable().asVarDecl()
)
or
// use-use flow
localSsaFlowStepUseUse(def, nodeFrom, nodeTo)
or
localSsaFlowStepUseUse(def, nodeFrom.(PostUpdateNode).getPreUpdateNode(), nodeTo)
or
// step from previous read to Phi node
localFlowSsaInput(nodeFrom, def, nodeTo.asDefinition())
)
or
// use-use flow
localSsaFlowStepUseUse(def, nodeFrom, nodeTo)
localFlowSsaParamInput(nodeFrom, nodeTo)
or
localSsaFlowStepUseUse(def, nodeFrom.(PostUpdateNode).getPreUpdateNode(), nodeTo)
// flow through `&` (inout argument)
nodeFrom.asExpr() = nodeTo.asExpr().(InOutExpr).getSubExpr()
or
// step from previous read to Phi node
localFlowSsaInput(nodeFrom, def, nodeTo.asDefinition())
)
or
localFlowSsaParamInput(nodeFrom, nodeTo)
or
// flow through `&` (inout argument)
nodeFrom.asExpr() = nodeTo.asExpr().(InOutExpr).getSubExpr()
or
// reverse flow through `&` (inout argument)
nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr().(InOutExpr).getSubExpr() =
nodeTo.(PostUpdateNode).getPreUpdateNode().asExpr()
or
// flow through `try!` and similar constructs
nodeFrom.asExpr() = nodeTo.asExpr().(AnyTryExpr).getSubExpr()
or
// flow through `!`
// note: there's a case in `readStep` that handles when the source is the
// `OptionalSomeContentSet` within the RHS. This case is for when the
// `Optional` itself is tainted (which it usually shouldn't be, but
// retaining this case increases robustness of flow).
nodeFrom.asExpr() = nodeTo.asExpr().(ForceValueExpr).getSubExpr()
or
// read of an optional .some member via `let x: T = y: T?` pattern matching
// note: similar to `ForceValueExpr` this is ideally a content `readStep` but
// in practice we sometimes have taint on the optional itself.
nodeTo.asPattern() = nodeFrom.asPattern().(OptionalSomePattern).getSubPattern()
or
// flow through `?` and `?.`
nodeFrom.asExpr() = nodeTo.asExpr().(BindOptionalExpr).getSubExpr()
or
nodeFrom.asExpr() = nodeTo.asExpr().(OptionalEvaluationExpr).getSubExpr()
or
// flow through unary `+` (which does nothing)
nodeFrom.asExpr() = nodeTo.asExpr().(UnaryPlusExpr).getOperand()
or
// flow through varargs expansions (that wrap an `ArrayExpr` where varargs enter a call)
nodeFrom.asExpr() = nodeTo.asExpr().(VarargExpansionExpr).getSubExpr()
or
// flow through nil-coalescing operator `??`
exists(BinaryExpr nco |
nco.getOperator().(FreeFunction).getName() = "??(_:_:)" and
nodeTo.asExpr() = nco
|
// value argument
nodeFrom.asExpr() = nco.getAnOperand()
// reverse flow through `&` (inout argument)
nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr().(InOutExpr).getSubExpr() =
nodeTo.(PostUpdateNode).getPreUpdateNode().asExpr()
or
// unpack closure (the second argument is an `AutoClosureExpr` argument)
nodeFrom.asExpr() = nco.getAnOperand().(AutoClosureExpr).getExpr()
)
or
// flow through ternary operator `? :`
exists(IfExpr ie |
nodeTo.asExpr() = ie and
nodeFrom.asExpr() = ie.getBranch(_)
)
or
// flow through OpenExistentialExpr (compiler generated expression wrapper)
nodeFrom.asExpr() = nodeTo.asExpr().(OpenExistentialExpr).getSubExpr()
or
// flow from Expr to Pattern
exists(Expr e, Pattern p |
nodeFrom.asExpr() = e and
nodeTo.asPattern() = p and
p.getImmediateMatchingExpr() = e
)
or
// flow from Pattern to an identity-preserving sub-Pattern:
nodeTo.asPattern() =
[
nodeFrom.asPattern().(IsPattern).getSubPattern(),
nodeFrom.asPattern().(TypedPattern).getSubPattern()
]
or
// Flow from the last component in a key path chain to
// the return node for the key path.
exists(KeyPathExpr keyPath |
nodeFrom.(KeyPathComponentNodeImpl).getComponent() =
keyPath.getComponent(keyPath.getNumberOfComponents() - 1) and
nodeTo.(KeyPathReturnNodeImpl).getKeyPathExpr() = keyPath
)
or
exists(KeyPathExpr keyPath |
nodeTo.(KeyPathComponentPostUpdateNode).getComponent() =
keyPath.getComponent(keyPath.getNumberOfComponents() - 1) and
nodeFrom.(KeyPathReturnPostUpdateNode).getKeyPathExpr() = keyPath
)
or
// Flow to the result of a keypath assignment
exists(KeyPathApplicationExpr apply, AssignExpr assign |
apply = assign.getDest() and
nodeTo.asExpr() = apply and
nodeFrom.asExpr() = assign.getSource()
)
// flow through `try!` and similar constructs
nodeFrom.asExpr() = nodeTo.asExpr().(AnyTryExpr).getSubExpr()
or
// flow through `!`
// note: there's a case in `readStep` that handles when the source is the
// `OptionalSomeContentSet` within the RHS. This case is for when the
// `Optional` itself is tainted (which it usually shouldn't be, but
// retaining this case increases robustness of flow).
nodeFrom.asExpr() = nodeTo.asExpr().(ForceValueExpr).getSubExpr()
or
// read of an optional .some member via `let x: T = y: T?` pattern matching
// note: similar to `ForceValueExpr` this is ideally a content `readStep` but
// in practice we sometimes have taint on the optional itself.
nodeTo.asPattern() = nodeFrom.asPattern().(OptionalSomePattern).getSubPattern()
or
// flow through `?` and `?.`
nodeFrom.asExpr() = nodeTo.asExpr().(BindOptionalExpr).getSubExpr()
or
nodeFrom.asExpr() = nodeTo.asExpr().(OptionalEvaluationExpr).getSubExpr()
or
// flow through unary `+` (which does nothing)
nodeFrom.asExpr() = nodeTo.asExpr().(UnaryPlusExpr).getOperand()
or
// flow through varargs expansions (that wrap an `ArrayExpr` where varargs enter a call)
nodeFrom.asExpr() = nodeTo.asExpr().(VarargExpansionExpr).getSubExpr()
or
// flow through nil-coalescing operator `??`
exists(BinaryExpr nco |
nco.getOperator().(FreeFunction).getName() = "??(_:_:)" and
nodeTo.asExpr() = nco
|
// value argument
nodeFrom.asExpr() = nco.getAnOperand()
or
// unpack closure (the second argument is an `AutoClosureExpr` argument)
nodeFrom.asExpr() = nco.getAnOperand().(AutoClosureExpr).getExpr()
)
or
// flow through ternary operator `? :`
exists(IfExpr ie |
nodeTo.asExpr() = ie and
nodeFrom.asExpr() = ie.getBranch(_)
)
or
// flow through OpenExistentialExpr (compiler generated expression wrapper)
nodeFrom.asExpr() = nodeTo.asExpr().(OpenExistentialExpr).getSubExpr()
or
// flow from Expr to Pattern
exists(Expr e, Pattern p |
nodeFrom.asExpr() = e and
nodeTo.asPattern() = p and
p.getImmediateMatchingExpr() = e
)
or
// flow from Pattern to an identity-preserving sub-Pattern:
nodeTo.asPattern() =
[
nodeFrom.asPattern().(IsPattern).getSubPattern(),
nodeFrom.asPattern().(TypedPattern).getSubPattern()
]
or
// Flow from the last component in a key path chain to
// the return node for the key path.
exists(KeyPathExpr keyPath |
nodeFrom.(KeyPathComponentNodeImpl).getComponent() =
keyPath.getComponent(keyPath.getNumberOfComponents() - 1) and
nodeTo.(KeyPathReturnNodeImpl).getKeyPathExpr() = keyPath
)
or
exists(KeyPathExpr keyPath |
nodeTo.(KeyPathComponentPostUpdateNode).getComponent() =
keyPath.getComponent(keyPath.getNumberOfComponents() - 1) and
nodeFrom.(KeyPathReturnPostUpdateNode).getKeyPathExpr() = keyPath
)
or
// Flow to the result of a keypath assignment
exists(KeyPathApplicationExpr apply, AssignExpr assign |
apply = assign.getDest() and
nodeTo.asExpr() = apply and
nodeFrom.asExpr() = assign.getSource()
)
or
// flow step according to the closure capture library
captureValueStep(nodeFrom, nodeTo)
) and
model = ""
or
// flow through a flow summary (extension of `SummaryModelCsv`)
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
nodeTo.(FlowSummaryNode).getSummaryNode(), true)
or
// flow step according to the closure capture library
captureValueStep(nodeFrom, nodeTo)
nodeTo.(FlowSummaryNode).getSummaryNode(), true, model)
}
/**
@@ -312,14 +316,14 @@ private module Cached {
* data flow.
*/
cached
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
localFlowStepCommon(nodeFrom, nodeTo)
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo, string model) {
localFlowStepCommon(nodeFrom, nodeTo, model)
}
/** This is the local flow predicate that is exposed. */
cached
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
localFlowStepCommon(nodeFrom, nodeTo) or
localFlowStepCommon(nodeFrom, nodeTo, _) or
FlowSummaryImpl::Private::Steps::summaryThroughStepValue(nodeFrom, nodeTo, _)
}
@@ -1396,6 +1400,10 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
/** Extra data-flow steps needed for lambda flow analysis. */
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
predicate knownSourceModel(Node source, string model) { sourceNode(source, _, model) }
predicate knownSinkModel(Node sink, string model) { sinkNode(sink, _, model) }
/**
* Holds if flow is allowed to pass from parameter `p` and back to itself as a
* side-effect, resulting in a summary from `p` to itself.

View File

@@ -120,12 +120,13 @@ module SourceSinkInterpretationInput implements
* `output`, kind `kind`, and provenance `provenance`.
*/
predicate sourceElement(
SourceOrSinkElement e, string output, string kind, Public::Provenance provenance
SourceOrSinkElement e, string output, string kind, Public::Provenance provenance, string model
) {
exists(
string namespace, string type, boolean subtypes, string name, string signature, string ext
|
sourceModel(namespace, type, subtypes, name, signature, ext, output, kind, provenance) and
model = "" and // TODO: Insert MaD provenance from sourceModel
e = interpretElement(namespace, type, subtypes, name, signature, ext)
)
}
@@ -135,12 +136,13 @@ module SourceSinkInterpretationInput implements
* `input`, kind `kind` and provenance `provenance`.
*/
predicate sinkElement(
SourceOrSinkElement e, string input, string kind, Public::Provenance provenance
SourceOrSinkElement e, string input, string kind, Public::Provenance provenance, string model
) {
exists(
string package, string type, boolean subtypes, string name, string signature, string ext
|
sinkModel(package, type, subtypes, name, signature, ext, input, kind, provenance) and
model = "" and // TODO: Insert MaD provenance from sinkModel
e = interpretElement(package, type, subtypes, name, signature, ext)
)
}

View File

@@ -20,63 +20,66 @@ private module Cached {
* in all global taint flow configurations.
*/
cached
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// Flow through one argument of `appendLiteral` and `appendInterpolation` and to the second argument.
// This is needed for string interpolation generated by the compiler. An interpolated string
// like `"I am \(n) years old."` is represented as
// ```
// $interpolated = ""
// appendLiteral(&$interpolated, "I am ")
// appendInterpolation(&$interpolated, n)
// appendLiteral(&$interpolated, " years old.")
// ```
exists(ApplyExpr apply, ExprCfgNode e |
nodeFrom.asExpr() = [apply.getAnArgument().getExpr(), apply.getQualifier()] and
apply.getStaticTarget().getName() = ["appendLiteral(_:)", "appendInterpolation(_:)"] and
e.getExpr() = apply.getQualifier() and
nodeTo.(PostUpdateNodeImpl).getPreUpdateNode().getCfgNode() = e
)
or
// Flow from the computation of the interpolated string literal to the result of the interpolation.
exists(InterpolatedStringLiteralExpr interpolated |
nodeTo.asExpr() = interpolated and
nodeFrom.asExpr() = interpolated.getAppendingExpr()
)
or
// allow flow through arithmetic (this case includes string concatenation)
nodeTo.asExpr().(ArithmeticOperation).getAnOperand() = nodeFrom.asExpr()
or
// allow flow through bitwise operations
nodeTo.asExpr().(BitwiseOperation).getAnOperand() = nodeFrom.asExpr()
or
// allow flow through assignment operations (e.g. `+=`)
exists(AssignOperation op |
nodeFrom.asExpr() = op.getSource() and
nodeTo.asExpr() = op.getDest()
)
or
// flow through a subscript access
exists(SubscriptExpr se |
se.getBase() = nodeFrom.asExpr() and
se = nodeTo.asExpr()
)
or
// flow through autoclosure expressions (which turn value arguments into closure arguments);
// if the value is tainted, it's helpful to consider the autoclosure itself to be tainted as
// well for the purposes of matching sink models.
nodeFrom.asExpr() = nodeTo.asExpr().(AutoClosureExpr).getExpr()
or
// flow through the read of a content that inherits taint
exists(DataFlow::ContentSet f |
readStep(nodeFrom, f, nodeTo) and
f.getAReadContent() instanceof TaintInheritingContent
)
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, string model) {
(
// Flow through one argument of `appendLiteral` and `appendInterpolation` and to the second argument.
// This is needed for string interpolation generated by the compiler. An interpolated string
// like `"I am \(n) years old."` is represented as
// ```
// $interpolated = ""
// appendLiteral(&$interpolated, "I am ")
// appendInterpolation(&$interpolated, n)
// appendLiteral(&$interpolated, " years old.")
// ```
exists(ApplyExpr apply, ExprCfgNode e |
nodeFrom.asExpr() = [apply.getAnArgument().getExpr(), apply.getQualifier()] and
apply.getStaticTarget().getName() = ["appendLiteral(_:)", "appendInterpolation(_:)"] and
e.getExpr() = apply.getQualifier() and
nodeTo.(PostUpdateNodeImpl).getPreUpdateNode().getCfgNode() = e
)
or
// Flow from the computation of the interpolated string literal to the result of the interpolation.
exists(InterpolatedStringLiteralExpr interpolated |
nodeTo.asExpr() = interpolated and
nodeFrom.asExpr() = interpolated.getAppendingExpr()
)
or
// allow flow through arithmetic (this case includes string concatenation)
nodeTo.asExpr().(ArithmeticOperation).getAnOperand() = nodeFrom.asExpr()
or
// allow flow through bitwise operations
nodeTo.asExpr().(BitwiseOperation).getAnOperand() = nodeFrom.asExpr()
or
// allow flow through assignment operations (e.g. `+=`)
exists(AssignOperation op |
nodeFrom.asExpr() = op.getSource() and
nodeTo.asExpr() = op.getDest()
)
or
// flow through a subscript access
exists(SubscriptExpr se |
se.getBase() = nodeFrom.asExpr() and
se = nodeTo.asExpr()
)
or
// flow through autoclosure expressions (which turn value arguments into closure arguments);
// if the value is tainted, it's helpful to consider the autoclosure itself to be tainted as
// well for the purposes of matching sink models.
nodeFrom.asExpr() = nodeTo.asExpr().(AutoClosureExpr).getExpr()
or
// flow through the read of a content that inherits taint
exists(DataFlow::ContentSet f |
readStep(nodeFrom, f, nodeTo) and
f.getAReadContent() instanceof TaintInheritingContent
)
) and
model = ""
or
// flow through a flow summary (extension of `SummaryModelCsv`)
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
nodeTo.(FlowSummaryNode).getSummaryNode(), false)
nodeTo.(FlowSummaryNode).getSummaryNode(), false, model)
or
any(AdditionalTaintStep a).step(nodeFrom, nodeTo)
any(AdditionalTaintStep a).step(nodeFrom, nodeTo) and model = "AdditionalTaintStep"
}
/**
@@ -87,7 +90,7 @@ private module Cached {
predicate localTaintStepCached(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
DataFlow::localFlowStep(nodeFrom, nodeTo)
or
defaultAdditionalTaintStep(nodeFrom, nodeTo)
defaultAdditionalTaintStep(nodeFrom, nodeTo, _)
or
// Simple flow through library code is included in the exposed local
// step relation, even though flow is technically inter-procedural