mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
524 lines
18 KiB
Plaintext
524 lines
18 KiB
Plaintext
private import experimental.quantum.Language
|
|
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
|
import semmle.code.cpp.dataflow.new.DataFlow
|
|
// Importing these intializers here to ensure the are part of any model that is
|
|
// using OpenSslOperationBase. This further ensures that initializers are tied to opeartions
|
|
// even if only importing the operation by itself.
|
|
import EVPPKeyCtxInitializer
|
|
|
|
/**
|
|
* An openSSL CTX type, which is type for which the stripped underlying type
|
|
* matches the pattern 'evp_%ctx_%st'.
|
|
* This includes types like:
|
|
* - EVP_CIPHER_CTX
|
|
* - EVP_MD_CTX
|
|
* - EVP_PKEY_CTX
|
|
*/
|
|
class CtxType extends Type {
|
|
CtxType() {
|
|
// It is possible for users to use the underlying type of the CTX variables
|
|
// these have a name matching 'evp_%ctx_%st
|
|
this.getUnspecifiedType().stripType().getName().matches("evp_%ctx_%st")
|
|
or
|
|
// In principal the above check should be sufficient, but in case of build mode none issues
|
|
// i.e., if a typedef cannot be resolved,
|
|
// or issues with properly stubbing test cases, we also explicitly check for the wrapping type defs
|
|
// i.e., patterns matching 'EVP_%_CTX'
|
|
exists(Type base | base = this or base = this.(DerivedType).getBaseType() |
|
|
base.getName().matches("EVP_%_CTX")
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A pointer to a CtxType
|
|
*/
|
|
class CtxPointerExpr extends Expr {
|
|
CtxPointerExpr() {
|
|
this.getType() instanceof CtxType and
|
|
this.getType() instanceof PointerType
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call argument of type CtxPointerExpr.
|
|
*/
|
|
class CtxPointerArgument extends CtxPointerExpr {
|
|
CtxPointerArgument() { exists(Call c | c.getAnArgument() = this) }
|
|
|
|
Call getCall() { result.getAnArgument() = this }
|
|
}
|
|
|
|
/**
|
|
* The type of inputs and ouputs for an `OperationStep`.
|
|
*/
|
|
newtype TIOType =
|
|
CiphertextIO() or
|
|
// Used for typical CTX types, but not for OSSL_PARAM or OSSL_LIB_CTX
|
|
// For OSSL_PARAM and OSSL_LIB_CTX use of OsslParamIO and OsslLibContextIO
|
|
ContextIO() or
|
|
DigestIO() or
|
|
HashAlgorithmIO() or
|
|
IVorNonceIO() or
|
|
KeyIO() or
|
|
KeyOperationSubtypeIO() or
|
|
KeySizeIO() or
|
|
// Used for OSSL_LIB_CTX
|
|
OsslLibContextIO() or
|
|
// Used for OSSL_PARAM
|
|
OsslParamIO() or
|
|
MacIO() or
|
|
PaddingAlgorithmIO() or
|
|
// Plaintext also includes a message for digest, signature, verification, and mac generation
|
|
PlaintextIO() or
|
|
PrimaryAlgorithmIO() or
|
|
RandomSourceIO() or
|
|
SaltLengthIO() or
|
|
SeedIO() or
|
|
SignatureIO()
|
|
|
|
private string ioTypeToString(TIOType t) {
|
|
t = CiphertextIO() and result = "CiphertextIO"
|
|
or
|
|
t = ContextIO() and result = "ContextIO"
|
|
or
|
|
t = DigestIO() and result = "DigestIO"
|
|
or
|
|
t = HashAlgorithmIO() and result = "HashAlgorithmIO"
|
|
or
|
|
t = IVorNonceIO() and result = "IVorNonceIO"
|
|
or
|
|
t = KeyIO() and result = "KeyIO"
|
|
or
|
|
t = KeyOperationSubtypeIO() and result = "KeyOperationSubtypeIO"
|
|
or
|
|
t = KeySizeIO() and result = "KeySizeIO"
|
|
or
|
|
t = OsslLibContextIO() and result = "OsslLibContextIO"
|
|
or
|
|
t = OsslParamIO() and result = "OsslParamIO"
|
|
or
|
|
t = MacIO() and result = "MacIO"
|
|
or
|
|
t = PaddingAlgorithmIO() and result = "PaddingAlgorithmIO"
|
|
or
|
|
t = PlaintextIO() and result = "PlaintextIO"
|
|
or
|
|
t = PrimaryAlgorithmIO() and result = "PrimaryAlgorithmIO"
|
|
or
|
|
t = RandomSourceIO() and result = "RandomSourceIO"
|
|
or
|
|
t = SaltLengthIO() and result = "SaltLengthIO"
|
|
or
|
|
t = SeedIO() and result = "SeedIO"
|
|
or
|
|
t = SignatureIO() and result = "SignatureIO"
|
|
}
|
|
|
|
class IOType extends TIOType {
|
|
string toString() {
|
|
result = ioTypeToString(this)
|
|
or
|
|
not exists(ioTypeToString(this)) and result = "UnknownIOType"
|
|
}
|
|
}
|
|
|
|
//TODO: add more initializers as needed
|
|
/**
|
|
* The type of step in an `OperationStep`.
|
|
* - `ContextCreationStep`: the creation of a context from an algorithm or key.
|
|
* for example `EVP_MD_CTX_create(EVP_sha256())` or `EVP_PKEY_CTX_new(pkey, NULL)`
|
|
* - `InitializerStep`: the initialization of an operation through some sort of shared/accumulated context
|
|
* for example `EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)`
|
|
* - `UpdateStep`: any operation that has and update/final paradigm, the update represents an intermediate step in an operation,
|
|
* such as `EVP_DigestUpdate(ctx, data, len)`
|
|
* - `FinalStep`: an ultimate operation step. This may be an explicit 'final' in an update/final paradigm, but not necessarily.
|
|
* Any operation that does nto operate through an update/final paradigm is considered a final step.
|
|
*/
|
|
newtype OperationStepType =
|
|
// Context creation captures cases where a context is created from an algorithm or key
|
|
//
|
|
ContextCreationStep() or
|
|
InitializerStep() or
|
|
UpdateStep() or
|
|
FinalStep()
|
|
|
|
/**
|
|
* A step in configuring an operation.
|
|
* Captures creation of contexts from algorithms or keys,
|
|
* initalization of configurations on contexts,
|
|
* update operations (intermediate steps in an operation)
|
|
* and the operation itself.
|
|
*
|
|
* NOTE: if an operation is configured through a means other than a call
|
|
* e.g., a pattern like ctx->alg = EVP_sha256()
|
|
* then this class will need to be modified to account for that paradigm.
|
|
* Currently, this is not a known pattern in OpenSSL.
|
|
*/
|
|
abstract class OperationStep extends Call {
|
|
/**
|
|
* Gets the output nodes from the given operation step.
|
|
* These are the nodes that flow connecting this step
|
|
* to any other step in the operation should follow.
|
|
*/
|
|
abstract DataFlow::Node getOutput(IOType type);
|
|
|
|
/**
|
|
* Gets any output node from the given operation step.
|
|
*/
|
|
final DataFlow::Node getAnOutput() { result = this.getOutput(_) }
|
|
|
|
/**
|
|
* Gets the input nodes for the given operation step.
|
|
*/
|
|
abstract DataFlow::Node getInput(IOType type);
|
|
|
|
/**
|
|
* Gets any input node for the given operation step.
|
|
*/
|
|
final DataFlow::Node getAnInput() { result = this.getInput(_) }
|
|
|
|
/**
|
|
* Gets the type of the step, e.g., ContextCreationStep, InitializerStep, UpdateStep, FinalStep.
|
|
*/
|
|
abstract OperationStepType getStepType();
|
|
|
|
/**
|
|
* Holds if this operation step flows to the given `OperationStep` `sink`.
|
|
* If `sink` is `this`, then this holds true.
|
|
*/
|
|
predicate flowsToOperationStep(OperationStep sink) {
|
|
sink = this or
|
|
OperationStepFlow::flow(this.getAnOutput(), sink.getAnInput())
|
|
}
|
|
|
|
/**
|
|
* Holds if this operation step flows from the given `OperationStep` (`source`).
|
|
* If `source` is `this`, then this holds true.
|
|
*/
|
|
predicate flowsFromOperationStep(OperationStep source) {
|
|
source = this or
|
|
OperationStepFlow::flow(source.getAnOutput(), this.getAnInput())
|
|
}
|
|
|
|
/**
|
|
* Holds if this operation step sets a value of the given `IOType`.
|
|
*/
|
|
predicate setsValue(IOType type) { exists(this.getInput(type)) }
|
|
|
|
/**
|
|
* Gets operation steps that flow to `this` and set the given `IOType`.
|
|
* This checks for the last initializers that flow to the `this`,
|
|
* i.e., if a value is set then re-set, the last set operation step is returned,
|
|
* not both.
|
|
* Note: Any 'update' that sets a value is not considered to be 'resetting' an input.
|
|
* I.e., there is a difference between changing a configuration before use and
|
|
* the operation allows for multiple inputs (like plaintext for cipher update calls before final).
|
|
*/
|
|
OperationStep getDominatingInitializersToStep(IOType type) {
|
|
result.flowsToOperationStep(this) and
|
|
result.setsValue(type) and
|
|
(
|
|
// Do not consider a 'reset' to occur on updates
|
|
result.getStepType() = UpdateStep()
|
|
or
|
|
not exists(OperationStep reset |
|
|
result != reset and
|
|
reset.setsValue(type) and
|
|
reset.flowsToOperationStep(this) and
|
|
result.flowsToOperationStep(reset)
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets all output of `type` that flow to `this`
|
|
* if `this` is a final step and the output is not from
|
|
* a separate final step.
|
|
*/
|
|
OperationStep getOutputStepFlowingToStep(IOType type) {
|
|
this.getStepType() = FinalStep() and
|
|
result.flowsToOperationStep(this) and
|
|
exists(result.getOutput(type)) and
|
|
(result = this or result.getStepType() != FinalStep())
|
|
}
|
|
|
|
/**
|
|
* Gets an AVC for the primary algorithm for this operation.
|
|
* A primary algorithm is an AVC that flows to a ctx input directly or
|
|
* an AVC that flows to a primary algorithm input directly.
|
|
* See `AvcContextCreationStep` for details about resetting scenarios.
|
|
* Gets the first OperationStep an AVC flows to. If a context input,
|
|
* the AVC is considered primary.
|
|
* If a primary algorithm input, then get the last set primary algorithm
|
|
* operation step (dominating operation step, see `getDominatingInitializersToStep`).
|
|
*/
|
|
Crypto::AlgorithmValueConsumer getPrimaryAlgorithmValueConsumer() {
|
|
exists(DataFlow::Node src, DataFlow::Node sink, IOType t, OperationStep avcSucc |
|
|
(t = PrimaryAlgorithmIO() or t = ContextIO()) and
|
|
avcSucc.flowsToOperationStep(this) and
|
|
src.asExpr() = result and
|
|
sink = avcSucc.getInput(t) and
|
|
AvcToOperationStepFlow::flow(src, sink) and
|
|
(
|
|
// Case 1: the avcSucc step is a dominating initialization step
|
|
t = PrimaryAlgorithmIO() and
|
|
avcSucc = this.getDominatingInitializersToStep(PrimaryAlgorithmIO())
|
|
or
|
|
// Case 2: the succ is a context input (any avcSucc is valid)
|
|
t = ContextIO()
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the algorithm value consumer for an input to `this` operation step
|
|
* of the given `type`.
|
|
* TODO: generalize to use this for `getPrimaryAlgorithmValueConsumer`
|
|
*/
|
|
Crypto::AlgorithmValueConsumer getAlgorithmValueConsumerForInput(IOType type) {
|
|
exists(DataFlow::Node src, DataFlow::Node sink |
|
|
AvcToOperationStepFlow::flow(src, sink) and
|
|
src.asExpr() = result and
|
|
sink = this.getInput(type)
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An AVC is considered to output a 'context type', however,
|
|
* each AVC has it's own output types in practice.
|
|
* Some output algorithm containers (`EVP_get_cipherbyname`)
|
|
* some output explicit contexts (`EVP_PKEY_CTX_new_from_name`).
|
|
* The output of an AVC cannot be determined to be a primary algorithm (PrimaryAlgorithmIO), that depends
|
|
* on the use of the AVC output.
|
|
* The use is assumed to be of two forms:
|
|
* - The AVC output flows to a known input that accepts an algorithm
|
|
* e.g., `EVP_DigestInit(ctx, type)` the `type` parameter is known to be the primary algorithm.
|
|
* `EVP_SignInit(ctx, type)` the `type` parameter is known to be a digest algorithm for the signature.
|
|
* - The AVC output flows to a context initialization step
|
|
* e.g., `pkey_ctx = EVP_PKEY_CTX_new_from_name(libctx, name, propquery)` this is an AVC call, but the
|
|
* API says the output is a context. It is consumed typically by something like:
|
|
* `ctx = EVP_PKEY_keygen_init(pkey_ctx)`, but note I cannot consider the `pkey_ctx` parameter to always be a primary algorithm,
|
|
* a key gen can be inited by a prior key as well, e.g., `ctx = EVP_PKEY_CTX_new(pkey, NULL)`.
|
|
* Hence, these initialization steps take in a context that may have come from an AVC or something else,
|
|
* and therefore cannot be considered a primary algorithm.
|
|
* Assumption: The first operation step an AVC flows to will be of the above two forms.
|
|
* Resetting Algorithm Concerns and Assumptions:
|
|
* What if a user resets the algorithm through another AVC call?
|
|
* How would we detect that and only look at the 'dominating' (last set) AVC?
|
|
* From an AVC, always assess the first operation step it flows to.
|
|
* If the first step is to a context input, then we assume that reset is not possible in the same path.
|
|
* I.e., a user cannot reset the algorithm without starting an entirely new operation step chain.
|
|
* See the use patterns for `pkey_ctx = EVP_PKEY_CTX_new_from_name(...)` mentioned above. A user cannot
|
|
* reset the algorithm without calling a new `ctx = EVP_PKEY_keygen_init(pkey_ctx)`,
|
|
* i.e., subsequent flow follows the `ctx` output.
|
|
* If the first step is to any other input, then we use the `getDominatingInitializersToStep`
|
|
* to find the last AVC that set the algorithm for the operation step.
|
|
* Domination checks must occur at an operation step (e.g., at a final operation).
|
|
* This operation step does not find the dominating AVC.
|
|
* If a primary algorithm is explicitly set and and AVC is set through a context input,
|
|
* we will use both cases as primary inputs.
|
|
*/
|
|
class AvcContextCreationStep extends OperationStep instanceof OpenSslAlgorithmValueConsumer {
|
|
override DataFlow::Node getOutput(IOType type) {
|
|
type = ContextIO() and result = super.getResultNode()
|
|
}
|
|
|
|
override DataFlow::Node getInput(IOType type) { none() }
|
|
|
|
override OperationStepType getStepType() { result = ContextCreationStep() }
|
|
}
|
|
|
|
abstract private class CtxPassThroughCall extends Call {
|
|
abstract DataFlow::Node getNode1();
|
|
|
|
abstract DataFlow::Node getNode2();
|
|
}
|
|
|
|
/**
|
|
* A call whose target contains 'free' or 'reset' and has an argument of type
|
|
* CtxPointerArgument.
|
|
*/
|
|
private class CtxClearCall extends Call {
|
|
CtxClearCall() {
|
|
this.getTarget().getName().toLowerCase().matches(["%free%", "%reset%"]) and
|
|
this.getAnArgument() instanceof CtxPointerArgument
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call whose target contains 'copy' and has an argument of type
|
|
* CtxPointerArgument.
|
|
*/
|
|
private class CtxCopyOutArgCall extends CtxPassThroughCall {
|
|
DataFlow::Node n1;
|
|
DataFlow::Node n2;
|
|
|
|
CtxCopyOutArgCall() {
|
|
this.getTarget().getName().toLowerCase().matches("%copy%") and
|
|
n1.asExpr() = this.getAnArgument() and
|
|
n1.getType() instanceof CtxType and
|
|
n2.asDefiningArgument() = this.getAnArgument() and
|
|
n2.getType() instanceof CtxType and
|
|
n1.asDefiningArgument() != n2.asExpr()
|
|
}
|
|
|
|
override DataFlow::Node getNode1() { result = n1 }
|
|
|
|
override DataFlow::Node getNode2() { result = n2 }
|
|
}
|
|
|
|
/**
|
|
* A call whose target contains 'dup' and has an argument of type
|
|
* CtxPointerArgument.
|
|
*/
|
|
private class CtxCopyReturnCall extends CtxPassThroughCall, CtxPointerExpr {
|
|
DataFlow::Node n1;
|
|
|
|
CtxCopyReturnCall() {
|
|
this.getTarget().getName().toLowerCase().matches("%dup%") and
|
|
n1.asExpr() = this.getAnArgument() and
|
|
n1.getType() instanceof CtxType
|
|
}
|
|
|
|
override DataFlow::Node getNode1() { result = n1 }
|
|
|
|
override DataFlow::Node getNode2() { result.asExpr() = this }
|
|
}
|
|
|
|
// TODO: is this still needed?
|
|
/**
|
|
* A call to `EVP_PKEY_paramgen` acts as a kind of pass through.
|
|
* It's output pkey is eventually used in a new operation generating
|
|
* a fresh context pointer (e.g., `EVP_PKEY_CTX_new`).
|
|
* It is easier to model this as a pass through
|
|
* than to model the flow from the paramgen to the new key generation.
|
|
*/
|
|
private class CtxParamGenCall extends CtxPassThroughCall {
|
|
DataFlow::Node n1;
|
|
DataFlow::Node n2;
|
|
|
|
CtxParamGenCall() {
|
|
this.getTarget().getName() = "EVP_PKEY_paramgen" and
|
|
n1.asExpr() = this.getArgument(0) and
|
|
(
|
|
n2.asExpr() = this.getArgument(1)
|
|
or
|
|
n2.asDefiningArgument() = this.getArgument(1)
|
|
)
|
|
}
|
|
|
|
override DataFlow::Node getNode1() { result = n1 }
|
|
|
|
override DataFlow::Node getNode2() { result = n2 }
|
|
}
|
|
|
|
//TODO: I am not sure CallArgToCtxRet is needed anymore
|
|
/**
|
|
* If the current node is an argument to a function
|
|
* that returns a pointer type, immediately flow through.
|
|
* NOTE: this passthrough is required if we allow
|
|
* intermediate steps to go into variables that are not a CTX type.
|
|
* See for example `CtxParamGenCall`.
|
|
*/
|
|
private class CallArgToCtxRet extends CtxPassThroughCall, CtxPointerExpr {
|
|
DataFlow::Node n1;
|
|
DataFlow::Node n2;
|
|
|
|
CallArgToCtxRet() {
|
|
this.getAnArgument() = n1.asExpr() and
|
|
n2.asExpr() = this
|
|
}
|
|
|
|
override DataFlow::Node getNode1() { result = n1 }
|
|
|
|
override DataFlow::Node getNode2() { result = n2 }
|
|
}
|
|
|
|
/**
|
|
* A flow configuration from any non-final `OperationStep` to any other `OperationStep`.
|
|
*/
|
|
module OperationStepFlowConfig implements DataFlow::ConfigSig {
|
|
predicate isSource(DataFlow::Node source) {
|
|
exists(OperationStep s |
|
|
s.getAnOutput() = source or
|
|
s.getAnInput() = source
|
|
)
|
|
}
|
|
|
|
predicate isSink(DataFlow::Node sink) {
|
|
exists(OperationStep s |
|
|
s.getAnInput() = sink or
|
|
s.getAnOutput() = sink
|
|
)
|
|
}
|
|
|
|
predicate isBarrier(DataFlow::Node node) {
|
|
exists(CtxClearCall c | c.getAnArgument() = node.asExpr())
|
|
}
|
|
|
|
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
|
exists(CtxPassThroughCall c | c.getNode1() = node1 and c.getNode2() = node2)
|
|
or
|
|
// Flow out through all outputs from an operation step if more than one output
|
|
// is defined.
|
|
exists(OperationStep s | s.getAnInput() = node1 and s.getAnOutput() = node2)
|
|
// TODO: consideration for additional alises defined as follows:
|
|
// if an output from an operation step itself flows from the output of another operation step
|
|
// then the source of that flow's outputs (all of them) are potential aliases
|
|
}
|
|
}
|
|
|
|
module OperationStepFlow = DataFlow::Global<OperationStepFlowConfig>;
|
|
|
|
/**
|
|
* A flow from AVC to the first `OperationStep` the AVC reaches as an input.
|
|
*/
|
|
module AvcToOperationStepFlowConfig implements DataFlow::ConfigSig {
|
|
predicate isSource(DataFlow::Node source) {
|
|
exists(AvcContextCreationStep s | s.getAnOutput() = source)
|
|
}
|
|
|
|
predicate isSink(DataFlow::Node sink) { exists(OperationStep s | s.getAnInput() = sink) }
|
|
|
|
predicate isBarrier(DataFlow::Node node) {
|
|
exists(CtxClearCall c | c.getAnArgument() = node.asExpr())
|
|
}
|
|
|
|
/**
|
|
* Only get the first operation step encountered.
|
|
*/
|
|
predicate isBarrierOut(DataFlow::Node node) { isSink(node) }
|
|
|
|
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
|
exists(CtxPassThroughCall c | c.getNode1() = node1 and c.getNode2() = node2)
|
|
}
|
|
}
|
|
|
|
module AvcToOperationStepFlow = DataFlow::Global<AvcToOperationStepFlowConfig>;
|
|
|
|
module EncValToInitEncArgConfig implements DataFlow::ConfigSig {
|
|
predicate isSource(DataFlow::Node source) { source.asExpr().getValue().toInt() in [0, 1] }
|
|
|
|
predicate isSink(DataFlow::Node sink) {
|
|
exists(OperationStep s | sink = s.getInput(KeyOperationSubtypeIO()))
|
|
}
|
|
}
|
|
|
|
module EncValToInitEncArgFlow = DataFlow::Global<EncValToInitEncArgConfig>;
|
|
|
|
private Crypto::KeyOperationSubtype intToCipherOperationSubtype(int i) {
|
|
i = 0 and
|
|
result instanceof Crypto::TEncryptMode
|
|
or
|
|
i = 1 and result instanceof Crypto::TDecryptMode
|
|
}
|
|
|
|
Crypto::KeyOperationSubtype resolveKeyOperationSubTypeOperationStep(OperationStep s) {
|
|
exists(DataFlow::Node src |
|
|
EncValToInitEncArgFlow::flow(src, s.getInput(KeyOperationSubtypeIO())) and
|
|
result = intToCipherOperationSubtype(src.asExpr().getValue().toInt())
|
|
)
|
|
}
|