Skip to content

Swift: Risky or Broken Cryptographic Algorithm Query #13649

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
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
117 changes: 114 additions & 3 deletions swift/ql/lib/codeql/swift/frameworks/Cryptography/CommonCrypto.qll
Original file line number Diff line number Diff line change
@@ -1,15 +1,126 @@
/** Provides models and concepts from the CommonCrypto library */
/** Provides models and concepts from the CommonCrypto C library */

private import swift
private import codeql.swift.dataflow.DataFlow
private import codeql.swift.dataflow.ExternalFlow
private import codeql.swift.security.Cryptography

private class CommonCryptoCallNode extends CryptographicOperation::Range {
CommonCryptoCallNode() { none() }
/**
* A model for CommonCrypto functions that permit taint flow.
*/
private class CommonCryptoSummaries extends SummaryModelCsv {
override predicate row(string row) {
row =
[
// "namespace; type; subtypes; name; signature; ext; input; output; kind"
";;false;CCCryptorCreate(_:_:_:_:_:_:_:);;;Argument[1];Argument[6];value",
";;false;CCCryptorCreateFromData(_:_:_:_:_:_:_:_:_:_:);;;Argument[1];Argument[8];value",
";;false;CCCryptorCreateWithMode(_:_:_:_:_:_:_:_:_:_:_:_:);;;Argument[1..2];Argument[10];value",
Comment on lines +16 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
";;false;CCCryptorCreate(_:_:_:_:_:_:_:);;;Argument[1];Argument[6];value",
";;false;CCCryptorCreateFromData(_:_:_:_:_:_:_:_:_:_:);;;Argument[1];Argument[8];value",
";;false;CCCryptorCreateWithMode(_:_:_:_:_:_:_:_:_:_:_:_:);;;Argument[1..2];Argument[10];value",
";;false;CCCryptorCreate(_:_:_:_:_:_:_:);;;Argument[1];Argument[6];taint",
";;false;CCCryptorCreateFromData(_:_:_:_:_:_:_:_:_:_:);;;Argument[1];Argument[8];taint",
";;false;CCCryptorCreateWithMode(_:_:_:_:_:_:_:_:_:_:_:_:);;;Argument[1..2];Argument[11];taint",

I believe this should be (at most) taint flow, because someone whoever controls the algorithm choice substantially controls cryptorRef - but cryptorRef is not itself just a copy of the algorithm argument (which would be value flow).

You'll have to make AlgorithmUse a TaintTracking::Global flow however.

]
}
}

/** A data flow node for the output of a CommonCrypto encryption/decryption operation */
abstract private class CommonCryptoOutputNode extends CryptographicOperation::Range {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should just explicitly model CCCrypt and CCCryptorUpdate? It looks like you have everything you need to figure out which algorithm is used (at which point you should begin to get results on the tests), and I guess similar logic for the block mode.

override CryptographicAlgorithm getAlgorithm() { none() }

override DataFlow::Node getAnInput() { none() }

override BlockMode getBlockMode() { none() }
}

/**
* A declaration of a constant representing a value of the
* `CCAlgorithm` C enum, such as `kCCAlgorithmAES128`.
*/
private class AlgorithmConstantDecl extends VarDecl {
AlgorithmConstantDecl() { this.getName().matches("kCCAlgorithm%") }

string getAlgorithmName() { this.getName() = "kCCAlgorithm" + result }

CryptographicAlgorithm asAlgorithm() { result.matchesName(this.getAlgorithmName()) }
}

/**
* Data flow configuration from a `CCAlgorithm` constant (or `CCAlgorithm`-bearing `CCCryptorRef`)
* to a call where it appears as an argument, e.g. `CCCryptorCreate(…)`, `CCCryptorUpdate(…)`, etc.
*/
private module AlgorithmUseConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node n) {
n.asExpr().(DeclRefExpr).getDecl() instanceof AlgorithmConstantDecl
}

predicate isSink(DataFlow::Node n) {
exists(Argument arg | n.asExpr() = arg.getExpr() |
arg.getApplyExpr().getStaticTarget().getName().matches("CC%")
)
}

predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
// Flow through cast expressions like `CCAlgorithm(kCCAlgorithmAES128)`.
// TODO: turn this into a generally available flow step.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it there's a slight risk that NumericalCastType matches something that isn't truly a cast. But I imagine this will be very worthwhile overall. 👍

The trouble is it really should have it's own tests, so it might be better done as a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... do you mind if I grab your implementation of NumericalCastType and do the generally available step myself? It looks like having this might fix an issue elsewhere (in the constant salt query tests).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspired by this and some similar problems we've run into, I've created #13946 . It does not contain your code, just a number of specific models for Numeric and similar classes (which may cover your needs, as really you're just dealing with Int / UInt32). In any case we could add your model to Numeric.qll quite easily now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to future developer working on this query: the isAdditionalFlowStep and associated code should be removed from this PR. We've evolved this code and got these cases by default now.

exists(NumericalCastExpr call |
n1.asExpr() = call.getArgument(0).getExpr() and
n2.asExpr() = call
)
}
}

private module AlgorithmUse = DataFlow::Global<AlgorithmUseConfig>;

Check warning

Code scanning / CodeQL

Dead code

This code is never used, and it's not publicly exported.

/**
* A call expression that uses a `CCAlgorithm` constant through an argument, e.g. `CCryptorCreate(…)`.
*/
private class AlgorithmUser extends CallExpr {

Check warning

Code scanning / CodeQL

Dead code

This code is never used, and it's not publicly exported.
AlgorithmConstantDecl alg;

AlgorithmUser() {
exists(DataFlow::Node src, DataFlow::Node snk | AlgorithmUse::flow(src, snk) |
src.asExpr().(DeclRefExpr).getDecl() = alg and
snk.asExpr() = this.getAnArgument().getExpr()
)
}

CryptographicAlgorithm getAlgorithm() { result = alg.asAlgorithm() }
}

/**
* A call to an auto-generated numerical cast initializer, e.g. `CCAlgorithm(kCCAlgorithmAES128)`.
*/
private class NumericalCastExpr extends CallExpr {
NumericalCastExpr() { this.getStaticTarget() instanceof NumericalCastInitializer }
}

/**
* The declaration of an auto-generated numerical cast initializer.
*/
private class NumericalCastInitializer extends Initializer {
NumericalCastInitializer() {
this.getInterfaceType().getCanonicalType() instanceof NumericalCastType
}
}

/**
* The type of an auto-generated numerical cast initializer, which is a generic
* function type of the form `<Self, T where ...> (Self.Type) -> (T) -> Self`.
*/
private class NumericalCastType extends Type {
NumericalCastType() {
exists(
GenericFunctionType fntySelf, FunctionType fntyT, GenericTypeParamType genericSelf,
GenericTypeParamType genericT, MetatypeType metaSelf
|
this = fntySelf and
fntySelf = fntySelf.getCanonicalType() and
fntySelf.getNumberOfGenericParams() = 2 and
fntySelf.getGenericParam(0) = genericSelf and
fntySelf.getGenericParam(1) = genericT and
fntySelf.getNumberOfParamTypes() = 1 and
fntySelf.getParamType(0) = metaSelf and
fntySelf.getResult() = fntyT and
fntyT.getNumberOfParamTypes() = 1 and
fntyT.getParamType(0) = genericT and
fntyT.getResult() = genericSelf
)
}
}