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
Show file tree
Hide file tree
Changes from all 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
9 changes: 6 additions & 3 deletions config/identical-files.json
Original file line number Diff line number Diff line change
Expand Up @@ -501,12 +501,14 @@
"CryptoAlgorithms Python/JS/Ruby": [
"javascript/ql/lib/semmle/javascript/security/CryptoAlgorithms.qll",
"python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll",
"ruby/ql/lib/codeql/ruby/security/CryptoAlgorithms.qll"
"ruby/ql/lib/codeql/ruby/security/CryptoAlgorithms.qll",
"swift/ql/lib/codeql/swift/security/CryptoAlgorithms.qll"
],
"CryptoAlgorithmNames Python/JS/Ruby": [
"javascript/ql/lib/semmle/javascript/security/internal/CryptoAlgorithmNames.qll",
"python/ql/lib/semmle/python/concepts/internal/CryptoAlgorithmNames.qll",
"ruby/ql/lib/codeql/ruby/security/internal/CryptoAlgorithmNames.qll"
"ruby/ql/lib/codeql/ruby/security/internal/CryptoAlgorithmNames.qll",
"swift/ql/lib/codeql/swift/security/internal/CryptoAlgorithmNames.qll"
],
"SensitiveDataHeuristics Python/JS": [
"javascript/ql/lib/semmle/javascript/security/internal/SensitiveDataHeuristics.qll",
Expand Down Expand Up @@ -543,7 +545,8 @@
"Concepts Python/Ruby/JS": [
"python/ql/lib/semmle/python/internal/ConceptsShared.qll",
"ruby/ql/lib/codeql/ruby/internal/ConceptsShared.qll",
"javascript/ql/lib/semmle/javascript/internal/ConceptsShared.qll"
"javascript/ql/lib/semmle/javascript/internal/ConceptsShared.qll",
"swift/ql/lib/codeql/swift/internal/ConceptsShared.qll"
],
"ApiGraphModels": [
"javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll",
Expand Down
7 changes: 7 additions & 0 deletions swift/ql/lib/codeql/swift/Concepts.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Re-exports abstract classes representing generic concepts such as file system
* access or system command execution, for which individual framework libraries
* provide concrete subclasses.
*/

import codeql.swift.security.Cryptography as Cryptography
126 changes: 126 additions & 0 deletions swift/ql/lib/codeql/swift/frameworks/Cryptography/CommonCrypto.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/** 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

/**
* 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
)
}
}
15 changes: 15 additions & 0 deletions swift/ql/lib/codeql/swift/frameworks/Cryptography/CryptoKit.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/** Provides models and concepts from the CryptoKit library */

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

private class CryptoKitCallNode extends CryptographicOperation::Range {
CryptoKitCallNode() { none() }

override CryptographicAlgorithm getAlgorithm() { none() }

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

override BlockMode getBlockMode() { none() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/** Provides models and concepts from the CryptoSwift library */

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

private class CryptoSwiftCallNode extends CryptographicOperation::Range {
CryptoSwiftCallNode() { none() }

override CryptographicAlgorithm getAlgorithm() { none() }

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

override BlockMode getBlockMode() { none() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* This file imports all models of Cryptography-related frameworks and libraries.
*/

import CryptoKit
import CryptoSwift
import CommonCrypto
1 change: 1 addition & 0 deletions swift/ql/lib/codeql/swift/frameworks/Frameworks.qll
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ private import Alamofire.Alamofire
private import StandardLibrary.StandardLibrary
private import UIKit.UIKit
private import Xml.Xml
private import Cryptography.Cryptography
7 changes: 7 additions & 0 deletions swift/ql/lib/codeql/swift/internal/ConceptsImports.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* This file contains imports required for the Swift version of `ConceptsShared.qll`.
* Since they are language-specific, they can't be placed directly in that file, as it is shared between languages.
*/

import codeql.swift.dataflow.DataFlow
import codeql.swift.security.CryptoAlgorithms as CryptoAlgorithms
175 changes: 175 additions & 0 deletions swift/ql/lib/codeql/swift/internal/ConceptsShared.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/**
* Provides Concepts which are shared across languages.
*
* Each language has a language specific `Concepts.qll` file that can import the
* shared concepts from this file. A language can either re-export the concept directly,
* or can add additional member-predicates that are needed for that language.
*
* Moving forward, `Concepts.qll` will be the staging ground for brand new concepts from
* each language, but we will maintain a discipline of moving those concepts to
* `ConceptsShared.qll` ASAP.
*/

private import ConceptsImports

/**
* Provides models for cryptographic concepts.
*
* Note: The `CryptographicAlgorithm` class currently doesn't take weak keys into
* consideration for the `isWeak` member predicate. So RSA is always considered
* secure, although using a low number of bits will actually make it insecure. We plan
* to improve our libraries in the future to more precisely capture this aspect.
*/
module Cryptography {
class CryptographicAlgorithm = CryptoAlgorithms::CryptographicAlgorithm;

class EncryptionAlgorithm = CryptoAlgorithms::EncryptionAlgorithm;

class HashingAlgorithm = CryptoAlgorithms::HashingAlgorithm;

class PasswordHashingAlgorithm = CryptoAlgorithms::PasswordHashingAlgorithm;

/**
* A data-flow node that is an application of a cryptographic algorithm. For example,
* encryption, decryption, signature-validation.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `CryptographicOperation::Range` instead.
*/
class CryptographicOperation extends DataFlow::Node instanceof CryptographicOperation::Range {
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
CryptographicAlgorithm getAlgorithm() { result = super.getAlgorithm() }

/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
DataFlow::Node getAnInput() { result = super.getAnInput() }

/**
* Gets the block mode used to perform this cryptographic operation.
*
* This predicate is only expected to have a result if two conditions hold:
* 1. The operation is an encryption operation, i.e. the algorithm used is an `EncryptionAlgorithm`, and
* 2. The algorithm used is a block cipher (not a stream cipher).
*
* If either of these conditions do not hold, then this predicate should have no result.
*/
BlockMode getBlockMode() { result = super.getBlockMode() }
}

/** Provides classes for modeling new applications of a cryptographic algorithms. */
module CryptographicOperation {
/**
* A data-flow node that is an application of a cryptographic algorithm. For example,
* encryption, decryption, signature-validation.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `CryptographicOperation` instead.
*/
abstract class Range extends DataFlow::Node {
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we missing one or more implementations extending CryptographicOperation::Range?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's the extension point.

Btw, do you know if it's possible to specify them as Models-as-Data CSVs? I'm a bit unfamiliar with the API for those.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess you could define them as sources or sinks (by extending SourceModelCsv / SinkModelCsv) and then use the sourceNode / sinkNode predicate to select the corresponding DataFlow::Nodes. Morally I'm not sure they're quite the same thing as sources or sinks though.

/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
abstract CryptographicAlgorithm getAlgorithm();

/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
abstract DataFlow::Node getAnInput();

/**
* Gets the block mode used to perform this cryptographic operation.
*
* This predicate is only expected to have a result if two conditions hold:
* 1. The operation is an encryption operation, i.e. the algorithm used is an `EncryptionAlgorithm`, and
* 2. The algorithm used is a block cipher (not a stream cipher).
*
* If either of these conditions do not hold, then this predicate should have no result.
*/
abstract BlockMode getBlockMode();
}
}

/**
* A cryptographic block cipher mode of operation. This can be used to encrypt
* data of arbitrary length using a block encryption algorithm.
*/
class BlockMode extends string {
BlockMode() {
this =
[
"ECB", "CBC", "GCM", "CCM", "CFB", "OFB", "CTR", "OPENPGP",
"XTS", // https://csrc.nist.gov/publications/detail/sp/800-38e/final
"EAX" // https://en.wikipedia.org/wiki/EAX_mode
]
}

/** Holds if this block mode is considered to be insecure. */
predicate isWeak() { this = "ECB" }

/** Holds if the given string appears to match this block mode. */
bindingset[s]
predicate matchesString(string s) { s.toUpperCase().matches("%" + this + "%") }
}
}

/** Provides classes for modeling HTTP-related APIs. */
module Http {
/** Provides classes for modeling HTTP clients. */
module Client {
/**
* A data-flow node that makes an outgoing HTTP request.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `Http::Client::Request::Range` instead.
*/
class Request extends DataFlow::Node instanceof Request::Range {
/**
* Gets a data-flow node that contributes to the URL of the request.
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
*/
DataFlow::Node getAUrlPart() { result = super.getAUrlPart() }

/** Gets a string that identifies the framework used for this request. */
string getFramework() { result = super.getFramework() }

/**
* Holds if this request is made using a mode that disables SSL/TLS
* certificate validation, where `disablingNode` represents the point at
* which the validation was disabled, and `argumentOrigin` represents the origin
* of the argument that disabled the validation (which could be the same node as
* `disablingNode`).
*/
predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
super.disablesCertificateValidation(disablingNode, argumentOrigin)
}
}

/** Provides a class for modeling new HTTP requests. */
module Request {
/**
* A data-flow node that makes an outgoing HTTP request.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `Http::Client::Request` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets a data-flow node that contributes to the URL of the request.
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
*/
abstract DataFlow::Node getAUrlPart();

/** Gets a string that identifies the framework used for this request. */
abstract string getFramework();

/**
* Holds if this request is made using a mode that disables SSL/TLS
* certificate validation, where `disablingNode` represents the point at
* which the validation was disabled, and `argumentOrigin` represents the origin
* of the argument that disabled the validation (which could be the same node as
* `disablingNode`).
*/
abstract predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
);
}
}
}
}
Loading