Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions unified/ql/lib/codeql/unified/internal/AnalysisQuality.qll
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,28 @@
}

module CallGraphStatsReport = EntityReportStats<CallGraphStats>;

/**
* Gets summary statistics about taint.
*/
predicate taintStats(string key, int value) {
// The keys must match those in DCA summary profiles
key = "Taint sources - active" and value = count(DataFlow::Node n | Models::isSource(n, "remote"))
or
key = "Taint sources - disabled" and
value = count(DataFlow::Node n | Models::isSource(n, any(string s | s != "remote")))
or
key = "Taint sources - sensitive data" and none()
Comment on lines +117 to +122
or
key = "Taint edges - number of edges" and none()
or
key = "Taint reach - nodes tainted" and none()
or
key = "Taint reach - total non-summary nodes" and none()
or
key = "Taint reach - per million nodes" and none()
or
key = "Taint sinks - query sinks" and value = count(DataFlow::Node n | Models::isSink(n, _))
Comment on lines +117 to +132
or
key = "Taint sinks - cryptographic operations" and none()
}
13 changes: 13 additions & 0 deletions unified/ql/lib/codeql/unified/internal/FacadeAst.qll
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ overlay[local?]
module;

private import codeql.files.FileSystem
private import codeql.unified.internal.NameBinding as NameBinding

module Unified {
private import Ast::Unified as G
Expand Down Expand Up @@ -145,6 +146,13 @@ module Unified {
class ClassLikeDeclaration extends G::ClassLikeDeclaration {
/** Gets the name of this declaration. */
string getName() { result = this.getNameNode().getValue() }

/** Gets a direct base class of this class. */
ClassLikeDeclaration getABaseClass() {
result.getNameNode() =
NameBinding::getStaticBindingTarget(NameBinding::getIdentifierFromRef(this.getABaseType()
.getType()))
}
}

class ConstructorDeclaration extends G::ConstructorDeclaration {
Expand Down Expand Up @@ -260,5 +268,10 @@ module Unified {

/** Gets the number of arguments passed to this call, not counting implicit arguments like receiver. */
int getNumberOfArguments() { result = count(this.getAnArgument()) }

/** Gets the number of arguments passed to this call, not counting implicit arguments like receiver. */
Comment thread
asgerf marked this conversation as resolved.
Outdated
int getNumberOfPositionalArguments() {
result = count(Argument arg | arg = this.getAnArgument() and arg.isPositional())
}
}
}
321 changes: 321 additions & 0 deletions unified/ql/lib/codeql/unified/internal/mad/LegacyMaD.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
/**
* Provides an approximate interpreter for MaD models imported from legacy Swift.
*/

private import unified
private import codeql.dataflow.internal.AccessPathSyntax

extensible predicate legacySourceModel(
string namespace, string type, boolean subtypes, string name, string signature, string ext,
string output, string kind, string provenance
);

extensible predicate legacySinkModel(
string namespace, string type, boolean subtypes, string name, string signature, string ext,
string input, string kind, string provenance
);

extensible predicate legacySummaryModel(
string namespace, string type, boolean subtypes, string name, string signature, string ext,
string input, string output, string kind, string provenance
);

private predicate modelContainsMethodName(string name) {
legacySourceModel(_, _, _, name, _, _, _, _, _)
or
legacySinkModel(_, _, _, name, _, _, _, _, _)
or
legacySummaryModel(_, _, _, name, _, _, _, _, _, _)
}

private predicate parsedRawMethodName(string rawName, string name, string argLabels) {
modelContainsMethodName(rawName) and
exists(string regex |
regex = "([^(]+)\\((.*)\\)" and
name = rawName.regexpCapture(regex, 1) and
argLabels = rawName.regexpCapture(regex, 2)
Comment thread
asgerf marked this conversation as resolved.
)
}

private string getNameFromExpr(Expr e) {
result = e.(Identifier).getValue()
or
result = e.(MemberAccessExpr).getMemberName()
}

private string getCalleeName(CallExpr call) { result = getNameFromExpr(call.getCallee()) }

private string getArgLabelsFromCall(CallExpr call) {
result =
concat(Argument arg, string name, int group |
arg = call.getAnArgument() and
(
arg.isPositional() and
name = "_" and
group = 0
or
name = arg.getName() and
group = 1
)
|
name + ":" order by group, name
)
}

pragma[nomagic]
private predicate callSelector(CallExpr call, string name, string argLabels) {
name = getCalleeName(call) and
argLabels = getArgLabelsFromCall(call)
}

private import codeql.unified.internal.NameBinding as NameBinding

private predicate isSubclassOfType(ClassLikeDeclaration cls, string typeName) {
typeName = any(Selector s).getTypeString() and
(
getNameFromExpr(cls.getABaseType().getType()) = typeName
or
isSubclassOfType(cls.getABaseClass(), typeName)
)
}

private string getArgLabelsFromCallable(Callable callable) {
result =
concat(Parameter param, string name, int group |
param.getParent() = callable and
(
param.isPositional() and
name = "_" and
group = 0
or
name = param.getExternalName() and
group = 1
)
|
name + ":" order by group, name
)
}

pragma[nomagic]
private predicate callableSelector(
FunctionDeclaration callable, string type, string name, string argLabels
) {
name = callable.getName() and
argLabels = getArgLabelsFromCallable(callable) and
exists(ClassLikeDeclaration cls |
callable = cls.getAMember() and
isSubclassOfType(cls, type)
)
}

private newtype TSelector =
MkSelector(string type, boolean subtypes, string name, string argLabels, string argTypes) {
exists(string rawName |
legacySourceModel(_, type, subtypes, rawName, argTypes, _, _, _, _)
or
legacySinkModel(_, type, subtypes, rawName, argTypes, _, _, _, _)
or
legacySummaryModel(_, type, subtypes, rawName, argTypes, _, _, _, _, _)
|
parsedRawMethodName(rawName, name, argLabels)
)
}

/**
* A tuple from a model that identifies call sites.
*/
private class Selector extends TSelector {
string getTypeString() { this = MkSelector(result, _, _, _, _) }

boolean getSubtypesFlag() { this = MkSelector(_, result, _, _, _) }

string getName() { this = MkSelector(_, _, result, _, _) }

string getArgLabels() { this = MkSelector(_, _, _, result, _) }

string getArgTypes() { this = MkSelector(_, _, _, _, result) }

private string ppSubtypes() {
if this.getSubtypesFlag() = true then result = "+" else result = ""
}

string toString() {
result =
this.getTypeString() + this.ppSubtypes() + "." + this.getName() + "(" + this.getArgLabels() +
")" + this.getArgTypes()
}

/** Holds if `name,argLabels` should be used to join with `callSelector`. */
private predicate effectiveCallSelector(string name, string argLabels) {
this = MkSelector(_, _, name, argLabels, _) and
name != "init"
or
// For "init" models there are two issues at play:
// - Constructor calls do not mention "init", they just mention the type name, e.g. `String(...)` not `String.init(...)`.
// - The name "init" is too common to match on anyway. It is more precise to match on the type name in this case.
//
// So to wire up "init" calls correctly, we just use the type name as the method name.
this = MkSelector(name, _, "init", argLabels, _)
Comment thread
asgerf marked this conversation as resolved.
Outdated
}

predicate matchesCall(CallExpr call) {
exists(string name, string argLabels |
this.effectiveCallSelector(name, argLabels) and
callSelector(call, name, argLabels)
)
}

predicate matchesCallable(Callable callable) {
exists(string type, string name, string argLabels |
callableSelector(callable, type, name, argLabels) and
this = MkSelector(type, true, name, argLabels, _)
)
}
}

private predicate isAccessPath(string path) {
legacySourceModel(_, _, _, _, _, _, path, _, _) // output
or
legacySinkModel(_, _, _, _, _, _, path, _, _) // input
or
legacySummaryModel(_, _, _, _, _, _, path, _, _, _) // input
or
legacySummaryModel(_, _, _, _, _, _, _, path, _, _) // output
}

private module AccessPathOutput = AccessPath<isAccessPath/1>;

private import AccessPathOutput

/**
* Gets the input to `call` specified by `token`. Only singleton access paths are supported.
*/
bindingset[call, token]
private DataFlow::Node getSinkFromCall(CallExpr call, AccessPathToken token) {
token.getName() = "Argument" and
exists(Argument arg |
arg = call.getAnArgument() and
result.asExpr() = arg.getValue()
|
arg.getPositionalIndex() = parseIntUnbounded(token.getAnArgument())
Comment thread
asgerf marked this conversation as resolved.
or
arg.getName() + ":" = token.getAnArgument()
)
or
token = "Argument[self]" and
result.isReceiverArgument(call)
}

private import codeql.unified.internal.dataflow.AllDataFlow

/**
* Gets the output from `call` specified by `token`. Only singleton access paths are supported.
*/
bindingset[call, token]
private DataFlow::Node getSourceFromCall(CallExpr call, AccessPathToken token) {
token = "ReturnValue" and
result.asExpr() = call
or
result = getPostUpdateNode(getSinkFromCall(call, token))
}

/**
* Gets the sink from `callable` specified by `token`. Only singleton access paths are supported.
*/
bindingset[callable, token]
private DataFlow::Node getSinkFromCallable(Callable callable, AccessPathToken token) {
token = "ReturnValue" and
result.asExpr() = any(ReturnExpr r | r.getEnclosingCallable() = callable).getValue() // TODO: expose canonical return node in data flow
}

/**
* Gets the source from `callable` specified by `token`. Only singleton access paths are supported.
*/
bindingset[callable, token]
private DataFlow::Node getSourceFromCallable(Callable callable, AccessPathToken token) {
token.getName() = "Parameter" and
exists(Parameter param |
param.getParent() = callable and
result.asExpr() = param.getPattern()
|
param.getPositionalIndex() = parseIntUnbounded(token.getAnArgument())
or
param.getExternalName() + ":" = token.getAnArgument()
)
or
token = "Parameter[self]" and
result.isReceiverParameter(callable)
}

private predicate normalizedSourceModel(
Selector selector, AccessPath output, string kind, string provenance
) {
exists(
string type, boolean subtypes, string rawName, string name, string argLabels, string argTypes
|
legacySourceModel(_, type, subtypes, rawName, argTypes, _, output, kind, provenance) and
parsedRawMethodName(rawName, name, argLabels) and
selector = MkSelector(type, subtypes, name, argLabels, argTypes)
)
}

private predicate normalizedSinkModel(
Selector selector, AccessPath output, string kind, string provenance
) {
exists(
string type, boolean subtypes, string rawName, string name, string argLabels, string argTypes
|
legacySinkModel(_, type, subtypes, rawName, argTypes, _, output, kind, provenance) and
parsedRawMethodName(rawName, name, argLabels) and
selector = MkSelector(type, subtypes, name, argLabels, argTypes)
)
}

/**
* Gets a flow source with the given `kind` and `provenance`.
*/
DataFlow::Node getASource(string kind, string provenance) {
exists(Selector selector, AccessPath output |
normalizedSourceModel(selector, output, kind, provenance)
|
exists(CallExpr call |
selector.matchesCall(call) and
result = getSourceFromCall(call, output)
)
or
exists(Callable callable |
selector.matchesCallable(callable) and
result = getSourceFromCallable(callable, output)
)
)
}

/**
* Gets a sink with the given `kind` and `provenance`.
*/
DataFlow::Node getASink(string kind, string provenance) {
exists(Selector selector, AccessPath input |
normalizedSinkModel(selector, input, kind, provenance)
|
exists(CallExpr call |
selector.matchesCall(call) and
result = getSinkFromCall(call, input)
)
or
exists(Callable callable |
selector.matchesCallable(callable) and
result = getSinkFromCallable(callable, input)
)
)
}

module Public {
/** Provides access to data from library models. */
module Models {
/** Holds if `node` is a source of the given `kind`. */
predicate isSource(DataFlow::Node node, string kind) { node = getASource(kind, _) }

/** Holds if `node` is a sink of the given `kind`. */
predicate isSink(DataFlow::Node node, string kind) { node = getASink(kind, _) }
}
}
Loading
Loading