From a36fd2cb31929affc304167d748e9d513ada27ae Mon Sep 17 00:00:00 2001 From: "REDMOND\\brodes" Date: Wed, 21 May 2025 18:15:44 -0400 Subject: [PATCH 1/7] Crypto: Advanced literal filtering for OpenSSL, used for both unknown and known algorithm literals to improve dataflow performance. --- cpp/ql/lib/experimental/quantum/Language.qll | 9 +- .../OpenSSL/AlgorithmCandidateLiteral.qll | 116 ++++++++++++++++++ .../KnownAlgorithmConstants.qll | 34 ++--- .../experimental/quantum/OpenSSL/OpenSSL.qll | 10 +- 4 files changed, 134 insertions(+), 35 deletions(-) create mode 100644 cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmCandidateLiteral.qll diff --git a/cpp/ql/lib/experimental/quantum/Language.qll b/cpp/ql/lib/experimental/quantum/Language.qll index ab3ce039cc5c..c9d3f735322d 100644 --- a/cpp/ql/lib/experimental/quantum/Language.qll +++ b/cpp/ql/lib/experimental/quantum/Language.qll @@ -1,6 +1,7 @@ private import cpp as Language import semmle.code.cpp.dataflow.new.TaintTracking import codeql.quantum.experimental.Model +private import experimental.quantum.OpenSSL.AlgorithmCandidateLiteral module CryptoInput implements InputSig { class DataFlowNode = DataFlow::Node; @@ -89,13 +90,7 @@ module GenericDataSourceFlowConfig implements DataFlow::ConfigSig { module GenericDataSourceFlow = TaintTracking::Global; private class ConstantDataSource extends Crypto::GenericConstantSourceInstance instanceof Literal { - ConstantDataSource() { - // TODO: this is an API specific workaround for OpenSSL, as 'EC' is a constant that may be used - // where typical algorithms are specified, but EC specifically means set up a - // default curve container, that will later be specified explicitly (or if not a default) - // curve is used. - this.getValue() != "EC" - } + ConstantDataSource() { this instanceof OpenSSLAlgorithmCandidateLiteral } override DataFlow::Node getOutputNode() { result.asExpr() = this } diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmCandidateLiteral.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmCandidateLiteral.qll new file mode 100644 index 000000000000..3e2d1d0301bf --- /dev/null +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmCandidateLiteral.qll @@ -0,0 +1,116 @@ +import cpp +private import semmle.code.cpp.models.Models +private import semmle.code.cpp.models.interfaces.FormattingFunction + +/** + * Holds if a StringLiteral could be an algorithm literal. + * Note: this predicate should only consider restrictions with respect to strings only. + * General restrictions are in the OpenSSLAlgorithmCandidateLiteral class. + */ +private predicate isOpenSSLStringLiteralAlgorithmCandidate(StringLiteral s) { + // 'EC' is a constant that may be used where typical algorithms are specified, + // but EC specifically means set up a default curve container, that will later be + //specified explicitly (or if not a default) curve is used. + s.getValue() != "EC" and + // Ignore empty strings + s.getValue() != "" and + // ignore strings that represent integers, it is possible this could be used for actual + // algorithms but assuming this is not the common case + // NOTE: if we were to revert this restriction, we should still consider filtering "0" + // to be consistent with filtering integer 0 + not exists(s.getValue().toInt()) and + // Filter out strings with "%", to filter out format strings + not s.getValue().matches("%\\%%") and + // Filter out strings in brackets or braces + not s.getValue().matches(["[%]", "(%)"]) and + // Filter out all strings of length 1, since these are not algorithm names + s.getValue().length() > 1 and + // Ignore all strings that are in format string calls outputing to a stream (e.g., stdout) + not exists(FormattingFunctionCall f | + exists(f.getOutputArgument(true)) and s = f.(Call).getAnArgument() + ) and + // Ignore all format string calls where there is no known out param (resulting string) + // i.e., ignore printf, since it will just ouput a string and not produce a new string + not exists(FormattingFunctionCall f | + // Note: using two ways of determining if there is an out param, since I'm not sure + // which way is canonical + not exists(f.getOutputArgument(false)) and + not f.getTarget().(FormattingFunction).hasTaintFlow(_, _) and + f.(Call).getAnArgument() = s + ) +} + +/** + * Holds if an IntLiteral could be an algorithm literal. + * Note: this predicate should only consider restrictions with respect to integers only. + * General restrictions are in the OpenSSLAlgorithmCandidateLiteral class. + */ +private predicate isOpenSSLIntLiteralAlgorithmCandidate(Literal l) { + exists(l.getValue().toInt()) and + // Ignore char literals + not l instanceof CharLiteral and + not l instanceof StringLiteral and + // Ignore integer values of 0, commonly referring to NULL only (no known algorithm 0) + // Also ignore integer values greater than 5000 + l.getValue().toInt() != 0 and + // ASSUMPTION, no negative numbers are allowed + // RATIONALE: this is a performance improvement to avoid having to trace every number + not exists(UnaryMinusExpr u | u.getOperand() = l) and + // OPENSSL has a special macro for getting every line, ignore it + not exists(MacroInvocation mi | mi.getExpr() = l and mi.getMacroName() = "OPENSSL_LINE") and + // Filter out cases where an int is returned into a pointer, e.g., return NULL; + not exists(ReturnStmt r | + r.getExpr() = l and + r.getEnclosingFunction().getType().getUnspecifiedType() instanceof DerivedType + ) and + // A literal as an array index should never be an algorithm + not exists(ArrayExpr op | op.getArrayOffset() = l) and + // A literal used in a bitwise operation may be an algorithm, but not a candidate + // for the purposes of finding applied algorithms + not exists(BinaryBitwiseOperation op | op.getAnOperand() = l) and + not exists(AssignBitwiseOperation op | op.getAnOperand() = l) and + //Filter out cases where an int is assigned or initialized into a pointer, e.g., char* x = NULL; + not exists(Assignment a | + a.getRValue() = l and + a.getLValue().getType().getUnspecifiedType() instanceof DerivedType + ) and + not exists(Initializer i | + i.getExpr() = l and + i.getDeclaration().getADeclarationEntry().getUnspecifiedType() instanceof DerivedType + ) and + // Filter out cases where the literal is used in any kind of arithmetic operation + not exists(BinaryArithmeticOperation op | op.getAnOperand() = l) and + not exists(UnaryArithmeticOperation op | op.getOperand() = l) and + not exists(AssignArithmeticOperation op | op.getAnOperand() = l) +} + +/** + * Any literal that may represent an algorithm for use in an operation, even if an invalid or unknown algorithm. + * The set of all literals is restricted by this class to cases where there is higher + * plausibility that the literal is eventually used as an algorithm. + * Literals are filtered, for example if they are used in a way no indicative of an algorithm use + * such as in an array index, bitwise operation, or logical operation. + * Note a case like this: + * if(algVal == "AES") + * + * "AES" may be a legitimate algorithm literal, but the literal will not be used for an operation directly + * since it is in a equality comparison, hence this case would also be filtered. + */ +class OpenSSLAlgorithmCandidateLiteral extends Literal { + OpenSSLAlgorithmCandidateLiteral() { + ( + isOpenSSLIntLiteralAlgorithmCandidate(this) or + isOpenSSLStringLiteralAlgorithmCandidate(this) + ) and + // ********* General filters beyond what is filtered for strings and ints ********* + // An algorithm literal in a switch case will not be directly applied to an operation. + not exists(SwitchCase sc | sc.getExpr() = this) and + // A literal in a logical operation may be an algorithm, but not a candidate + // for the purposes of finding applied algorithms + not exists(BinaryLogicalOperation op | op.getAnOperand() = this) and + not exists(UnaryLogicalOperation op | op.getOperand() = this) and + // A literal in a comparison operation may be an algorithm, but not a candidate + // for the purposes of finding applied algorithms + not exists(ComparisonOperation op | op.getAnOperand() = this) + } +} diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll index 5e7e16b13dc6..e8cb9bdf5391 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll @@ -1,5 +1,5 @@ import cpp -private import experimental.quantum.OpenSSL.LibraryDetector +import experimental.quantum.OpenSSL.AlgorithmCandidateLiteral predicate resolveAlgorithmFromExpr(Expr e, string normalizedName, string algType) { resolveAlgorithmFromCall(e, normalizedName, algType) @@ -89,7 +89,6 @@ class KnownOpenSSLEllipticCurveAlgorithmConstant extends KnownOpenSSLAlgorithmCo * alias = "dss1" and target = "dsaWithSHA1" */ predicate resolveAlgorithmFromCall(Call c, string normalized, string algType) { - isPossibleOpenSSLFunction(c.getTarget()) and exists(string name, string parsedTargetName | parsedTargetName = c.getTarget().getName().replaceAll("EVP_", "").toLowerCase().replaceAll("_", "-") and @@ -103,7 +102,9 @@ predicate resolveAlgorithmFromCall(Call c, string normalized, string algType) { * if `e` resolves to a known algorithm. * If this predicate does not hold, then `e` can be interpreted as being of `UNKNOWN` type. */ -predicate resolveAlgorithmFromLiteral(Literal e, string normalized, string algType) { +predicate resolveAlgorithmFromLiteral( + OpenSSLAlgorithmCandidateLiteral e, string normalized, string algType +) { exists(int nid | nid = getPossibleNidFromLiteral(e) and knownOpenSSLAlgorithmLiteral(_, nid, normalized, algType) ) @@ -125,28 +126,15 @@ string resolveAlgorithmAlias(string name) { ) } -private int getPossibleNidFromLiteral(Literal e) { +/** + * Determines if an int literal (NID) is a candidate for being an algorithm literal. + * Checks for common cases where literals are used that would not be indicative of an algorithm. + * Returns the int literal value if the literal is a candidate for an algorithm. + */ +private int getPossibleNidFromLiteral(OpenSSLAlgorithmCandidateLiteral e) { result = e.getValue().toInt() and not e instanceof CharLiteral and - not e instanceof StringLiteral and - // ASSUMPTION, no negative numbers are allowed - // RATIONALE: this is a performance improvement to avoid having to trace every number - not exists(UnaryMinusExpr u | u.getOperand() = e) and - // OPENSSL has a special macro for getting every line, ignore it - not exists(MacroInvocation mi | mi.getExpr() = e and mi.getMacroName() = "OPENSSL_LINE") and - // Filter out cases where an int is assigned into a pointer, e.g., char* x = NULL; - not exists(Assignment a | - a.getRValue() = e and a.getLValue().getType().getUnspecifiedType() instanceof PointerType - ) and - not exists(Initializer i | - i.getExpr() = e and - i.getDeclaration().getADeclarationEntry().getUnspecifiedType() instanceof PointerType - ) and - // Filter out cases where an int is returned into a pointer, e.g., return NULL; - not exists(ReturnStmt r | - r.getExpr() = e and - r.getEnclosingFunction().getType().getUnspecifiedType() instanceof PointerType - ) + not e instanceof StringLiteral } string getAlgorithmAlias(string alias) { diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/OpenSSL.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/OpenSSL.qll index a232ffa6f3a7..eadf1a3e2236 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/OpenSSL.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/OpenSSL.qll @@ -2,9 +2,9 @@ import cpp import semmle.code.cpp.dataflow.new.DataFlow module OpenSSLModel { - import experimental.quantum.Language - import experimental.quantum.OpenSSL.AlgorithmInstances.OpenSSLAlgorithmInstances - import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers - import experimental.quantum.OpenSSL.Operations.OpenSSLOperations - import experimental.quantum.OpenSSL.Random + import AlgorithmInstances.OpenSSLAlgorithmInstances + import AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers + import Operations.OpenSSLOperations + import Random + import AlgorithmCandidateLiteral } From 100045d4cb3a3c80957e1c62f741a9f5d51387b9 Mon Sep 17 00:00:00 2001 From: "REDMOND\\brodes" Date: Wed, 21 May 2025 18:25:29 -0400 Subject: [PATCH 2/7] Crypto: optimizing out the "getPossibleNidFromLiteral" predicate, and now relying on the charpred of OpenSSLAlgorithmCandidateLiteral. --- .../KnownAlgorithmConstants.qll | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll index e8cb9bdf5391..59f03264a694 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll @@ -105,9 +105,7 @@ predicate resolveAlgorithmFromCall(Call c, string normalized, string algType) { predicate resolveAlgorithmFromLiteral( OpenSSLAlgorithmCandidateLiteral e, string normalized, string algType ) { - exists(int nid | - nid = getPossibleNidFromLiteral(e) and knownOpenSSLAlgorithmLiteral(_, nid, normalized, algType) - ) + knownOpenSSLAlgorithmLiteral(_, e.getValue().toInt(), normalized, algType) or exists(string name | name = resolveAlgorithmAlias(e.getValue()) and @@ -126,17 +124,6 @@ string resolveAlgorithmAlias(string name) { ) } -/** - * Determines if an int literal (NID) is a candidate for being an algorithm literal. - * Checks for common cases where literals are used that would not be indicative of an algorithm. - * Returns the int literal value if the literal is a candidate for an algorithm. - */ -private int getPossibleNidFromLiteral(OpenSSLAlgorithmCandidateLiteral e) { - result = e.getValue().toInt() and - not e instanceof CharLiteral and - not e instanceof StringLiteral -} - string getAlgorithmAlias(string alias) { customAliases(result, alias) or From 09170e598c4c55d779a50e6ea8829f42eaf56cea Mon Sep 17 00:00:00 2001 From: "REDMOND\\brodes" Date: Thu, 22 May 2025 10:31:58 -0400 Subject: [PATCH 3/7] Crypto: Making generic literal filter more explicit that it is for filtering all constants, not just for algorithms. --- cpp/ql/lib/experimental/quantum/Language.qll | 4 +- .../KnownAlgorithmConstants.qll | 9 +--- ....qll => GenericSourceCandidateLiteral.qll} | 41 ++++++++++--------- 3 files changed, 25 insertions(+), 29 deletions(-) rename cpp/ql/lib/experimental/quantum/OpenSSL/{AlgorithmCandidateLiteral.qll => GenericSourceCandidateLiteral.qll} (76%) diff --git a/cpp/ql/lib/experimental/quantum/Language.qll b/cpp/ql/lib/experimental/quantum/Language.qll index c9d3f735322d..bc1a2de10e7a 100644 --- a/cpp/ql/lib/experimental/quantum/Language.qll +++ b/cpp/ql/lib/experimental/quantum/Language.qll @@ -1,7 +1,7 @@ private import cpp as Language import semmle.code.cpp.dataflow.new.TaintTracking import codeql.quantum.experimental.Model -private import experimental.quantum.OpenSSL.AlgorithmCandidateLiteral +private import experimental.quantum.OpenSSL.GericSourceCandidateLiteral module CryptoInput implements InputSig { class DataFlowNode = DataFlow::Node; @@ -90,7 +90,7 @@ module GenericDataSourceFlowConfig implements DataFlow::ConfigSig { module GenericDataSourceFlow = TaintTracking::Global; private class ConstantDataSource extends Crypto::GenericConstantSourceInstance instanceof Literal { - ConstantDataSource() { this instanceof OpenSSLAlgorithmCandidateLiteral } + ConstantDataSource() { this instanceof OpenSSLGenericSourceCandidateLiteral } override DataFlow::Node getOutputNode() { result.asExpr() = this } diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll index 59f03264a694..f49767bd2821 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll @@ -1,5 +1,5 @@ import cpp -import experimental.quantum.OpenSSL.AlgorithmCandidateLiteral +import experimental.quantum.OpenSSL.GericSourceCandidateLiteral predicate resolveAlgorithmFromExpr(Expr e, string normalizedName, string algType) { resolveAlgorithmFromCall(e, normalizedName, algType) @@ -103,7 +103,7 @@ predicate resolveAlgorithmFromCall(Call c, string normalized, string algType) { * If this predicate does not hold, then `e` can be interpreted as being of `UNKNOWN` type. */ predicate resolveAlgorithmFromLiteral( - OpenSSLAlgorithmCandidateLiteral e, string normalized, string algType + OpenSSLGenericSourceCandidateLiteral e, string normalized, string algType ) { knownOpenSSLAlgorithmLiteral(_, e.getValue().toInt(), normalized, algType) or @@ -237,11 +237,6 @@ predicate defaultAliases(string target, string alias) { alias = "ssl3-sha1" and target = "sha1" } -predicate tbd(string normalized, string algType) { - knownOpenSSLAlgorithmLiteral(_, _, normalized, algType) and - algType = "HASH" -} - /** * Enumeration of all known crypto algorithms for openSSL * `name` is all lower case (caller's must ensure they pass in lower case) diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmCandidateLiteral.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/GenericSourceCandidateLiteral.qll similarity index 76% rename from cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmCandidateLiteral.qll rename to cpp/ql/lib/experimental/quantum/OpenSSL/GenericSourceCandidateLiteral.qll index 3e2d1d0301bf..0f6730bed74e 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmCandidateLiteral.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/GenericSourceCandidateLiteral.qll @@ -3,27 +3,24 @@ private import semmle.code.cpp.models.Models private import semmle.code.cpp.models.interfaces.FormattingFunction /** - * Holds if a StringLiteral could be an algorithm literal. + * Holds if a StringLiteral could conceivably be used in some way for cryptography. * Note: this predicate should only consider restrictions with respect to strings only. - * General restrictions are in the OpenSSLAlgorithmCandidateLiteral class. + * General restrictions are in the OpenSSLGenericSourceCandidateLiteral class. */ -private predicate isOpenSSLStringLiteralAlgorithmCandidate(StringLiteral s) { +private predicate isOpenSSLStringLiteralGenericSourceCandidate(StringLiteral s) { // 'EC' is a constant that may be used where typical algorithms are specified, // but EC specifically means set up a default curve container, that will later be //specified explicitly (or if not a default) curve is used. s.getValue() != "EC" and // Ignore empty strings s.getValue() != "" and - // ignore strings that represent integers, it is possible this could be used for actual - // algorithms but assuming this is not the common case - // NOTE: if we were to revert this restriction, we should still consider filtering "0" - // to be consistent with filtering integer 0 - not exists(s.getValue().toInt()) and // Filter out strings with "%", to filter out format strings not s.getValue().matches("%\\%%") and - // Filter out strings in brackets or braces + // Filter out strings in brackets or braces (commonly seen strings not relevant for crypto) not s.getValue().matches(["[%]", "(%)"]) and // Filter out all strings of length 1, since these are not algorithm names + // NOTE/ASSUMPTION: If a user legitimately passes a string of length 1 to some configuration + // we will assume this is generally unknown. We may need to reassess this in the future. s.getValue().length() > 1 and // Ignore all strings that are in format string calls outputing to a stream (e.g., stdout) not exists(FormattingFunctionCall f | @@ -43,15 +40,14 @@ private predicate isOpenSSLStringLiteralAlgorithmCandidate(StringLiteral s) { /** * Holds if an IntLiteral could be an algorithm literal. * Note: this predicate should only consider restrictions with respect to integers only. - * General restrictions are in the OpenSSLAlgorithmCandidateLiteral class. + * General restrictions are in the OpenSSLGenericSourceCandidateLiteral class. */ -private predicate isOpenSSLIntLiteralAlgorithmCandidate(Literal l) { +private predicate isOpenSSLIntLiteralGenericSourceCandidate(Literal l) { exists(l.getValue().toInt()) and // Ignore char literals not l instanceof CharLiteral and not l instanceof StringLiteral and // Ignore integer values of 0, commonly referring to NULL only (no known algorithm 0) - // Also ignore integer values greater than 5000 l.getValue().toInt() != 0 and // ASSUMPTION, no negative numbers are allowed // RATIONALE: this is a performance improvement to avoid having to trace every number @@ -63,10 +59,9 @@ private predicate isOpenSSLIntLiteralAlgorithmCandidate(Literal l) { r.getExpr() = l and r.getEnclosingFunction().getType().getUnspecifiedType() instanceof DerivedType ) and - // A literal as an array index should never be an algorithm + // A literal as an array index should not be relevant for crypo not exists(ArrayExpr op | op.getArrayOffset() = l) and - // A literal used in a bitwise operation may be an algorithm, but not a candidate - // for the purposes of finding applied algorithms + // A literal used in a bitwise should not be relevant for crypto not exists(BinaryBitwiseOperation op | op.getAnOperand() = l) and not exists(AssignBitwiseOperation op | op.getAnOperand() = l) and //Filter out cases where an int is assigned or initialized into a pointer, e.g., char* x = NULL; @@ -81,7 +76,13 @@ private predicate isOpenSSLIntLiteralAlgorithmCandidate(Literal l) { // Filter out cases where the literal is used in any kind of arithmetic operation not exists(BinaryArithmeticOperation op | op.getAnOperand() = l) and not exists(UnaryArithmeticOperation op | op.getOperand() = l) and - not exists(AssignArithmeticOperation op | op.getAnOperand() = l) + not exists(AssignArithmeticOperation op | op.getAnOperand() = l) and + // If a literal has no parent ignore it, this is a workaround for the current inability + // to find a literal in an array declaration: int x[100]; + // NOTE/ASSUMPTION: this value might actually be relevant for finding hard coded sizes + // consider size as inferred through the allocation of a buffer. + // In these cases, we advise that the source is not generic and must be traced explicitly. + exists(l.getParent()) } /** @@ -96,11 +97,11 @@ private predicate isOpenSSLIntLiteralAlgorithmCandidate(Literal l) { * "AES" may be a legitimate algorithm literal, but the literal will not be used for an operation directly * since it is in a equality comparison, hence this case would also be filtered. */ -class OpenSSLAlgorithmCandidateLiteral extends Literal { - OpenSSLAlgorithmCandidateLiteral() { +class OpenSSLGenericSourceCandidateLiteral extends Literal { + OpenSSLGenericSourceCandidateLiteral() { ( - isOpenSSLIntLiteralAlgorithmCandidate(this) or - isOpenSSLStringLiteralAlgorithmCandidate(this) + isOpenSSLIntLiteralGenericSourceCandidate(this) or + isOpenSSLStringLiteralGenericSourceCandidate(this) ) and // ********* General filters beyond what is filtered for strings and ints ********* // An algorithm literal in a switch case will not be directly applied to an operation. From 570fdeb25474fd60a32c1da2d1850714eea181a0 Mon Sep 17 00:00:00 2001 From: "REDMOND\\brodes" Date: Thu, 22 May 2025 10:39:27 -0400 Subject: [PATCH 4/7] Crypto: Code Cleanup (+1 squashed commits) Squashed commits: [417734cc3c] Crypto: Fixing typo (+1 squashed commits) Squashed commits: [1ac3d5c7d4] Crypto: Fixing typo caused by AI auto complete. --- cpp/ql/lib/experimental/quantum/Language.qll | 2 +- .../OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll | 2 +- .../quantum/OpenSSL/GenericSourceCandidateLiteral.qll | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp/ql/lib/experimental/quantum/Language.qll b/cpp/ql/lib/experimental/quantum/Language.qll index bc1a2de10e7a..ebc246291a38 100644 --- a/cpp/ql/lib/experimental/quantum/Language.qll +++ b/cpp/ql/lib/experimental/quantum/Language.qll @@ -1,7 +1,7 @@ private import cpp as Language import semmle.code.cpp.dataflow.new.TaintTracking import codeql.quantum.experimental.Model -private import experimental.quantum.OpenSSL.GericSourceCandidateLiteral +private import OpenSSL.GenericSourceCandidateLiteral module CryptoInput implements InputSig { class DataFlowNode = DataFlow::Node; diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll index e1d14e689e0c..f7d186e4ea93 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll @@ -1,5 +1,5 @@ import cpp -import experimental.quantum.OpenSSL.GericSourceCandidateLiteral +import experimental.quantum.OpenSSL.GenericSourceCandidateLiteral predicate resolveAlgorithmFromExpr(Expr e, string normalizedName, string algType) { resolveAlgorithmFromCall(e, normalizedName, algType) diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/GenericSourceCandidateLiteral.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/GenericSourceCandidateLiteral.qll index 0f6730bed74e..214093aae36d 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/GenericSourceCandidateLiteral.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/GenericSourceCandidateLiteral.qll @@ -27,12 +27,12 @@ private predicate isOpenSSLStringLiteralGenericSourceCandidate(StringLiteral s) exists(f.getOutputArgument(true)) and s = f.(Call).getAnArgument() ) and // Ignore all format string calls where there is no known out param (resulting string) - // i.e., ignore printf, since it will just ouput a string and not produce a new string + // i.e., ignore printf, since it will just output a string and not produce a new string not exists(FormattingFunctionCall f | // Note: using two ways of determining if there is an out param, since I'm not sure // which way is canonical not exists(f.getOutputArgument(false)) and - not f.getTarget().(FormattingFunction).hasTaintFlow(_, _) and + not f.getTarget().hasTaintFlow(_, _) and f.(Call).getAnArgument() = s ) } From ca1d4e270a5fb5eb37e381452c902c2c3357b952 Mon Sep 17 00:00:00 2001 From: "REDMOND\\brodes" Date: Thu, 22 May 2025 12:53:11 -0400 Subject: [PATCH 5/7] Crypto: Separating out an IntLiteral class so it is clearer that some constraints for generic input sources are heuristics to filter sources, and other constraints narrow the literals to a general type (ints). Also adding fixes in KnownAlgorithmConstants to classify some algorithms as key exchange and signature correctly, and added support for a signature constant wrapper. --- .../OpenSSL/GenericSourceCandidateLiteral.qll | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/GenericSourceCandidateLiteral.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/GenericSourceCandidateLiteral.qll index 214093aae36d..8841adc17b6e 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/GenericSourceCandidateLiteral.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/GenericSourceCandidateLiteral.qll @@ -2,6 +2,15 @@ import cpp private import semmle.code.cpp.models.Models private import semmle.code.cpp.models.interfaces.FormattingFunction +private class IntLiteral extends Literal { + IntLiteral() { + //Heuristics for distinguishing int literals from other literals + exists(this.getValue().toInt()) and + not this instanceof CharLiteral and + not this instanceof StringLiteral + } +} + /** * Holds if a StringLiteral could conceivably be used in some way for cryptography. * Note: this predicate should only consider restrictions with respect to strings only. @@ -38,15 +47,11 @@ private predicate isOpenSSLStringLiteralGenericSourceCandidate(StringLiteral s) } /** - * Holds if an IntLiteral could be an algorithm literal. + * Holds if a StringLiteral could conceivably be used in some way for cryptography. * Note: this predicate should only consider restrictions with respect to integers only. * General restrictions are in the OpenSSLGenericSourceCandidateLiteral class. */ -private predicate isOpenSSLIntLiteralGenericSourceCandidate(Literal l) { - exists(l.getValue().toInt()) and - // Ignore char literals - not l instanceof CharLiteral and - not l instanceof StringLiteral and +private predicate isOpenSSLIntLiteralGenericSourceCandidate(IntLiteral l) { // Ignore integer values of 0, commonly referring to NULL only (no known algorithm 0) l.getValue().toInt() != 0 and // ASSUMPTION, no negative numbers are allowed @@ -86,10 +91,10 @@ private predicate isOpenSSLIntLiteralGenericSourceCandidate(Literal l) { } /** - * Any literal that may represent an algorithm for use in an operation, even if an invalid or unknown algorithm. + * Any literal that may be conceivably be used in some way for cryptography. * The set of all literals is restricted by this class to cases where there is higher - * plausibility that the literal is eventually used as an algorithm. - * Literals are filtered, for example if they are used in a way no indicative of an algorithm use + * plausibility that the literal could be used as a source of configuration. + * Literals are filtered, for example, if they are used in a way no indicative of an algorithm use * such as in an array index, bitwise operation, or logical operation. * Note a case like this: * if(algVal == "AES") From 28f48246fc87d42efb6e40039a0f33f3dc38e998 Mon Sep 17 00:00:00 2001 From: "REDMOND\\brodes" Date: Thu, 22 May 2025 13:13:35 -0400 Subject: [PATCH 6/7] Crypto: Adding signature constant support, and fixing key exchange and signature mapping for ED and X elliptic curve variants. --- .../KnownAlgorithmConstants.qll | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll index f7d186e4ea93..e56f70700b0c 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll @@ -76,6 +76,15 @@ class KnownOpenSSLEllipticCurveAlgorithmConstant extends KnownOpenSSLAlgorithmCo } } +class KnownOpenSSLSignatureAlgorithmConstant extends KnownOpenSSLAlgorithmConstant { + string algType; + + KnownOpenSSLSignatureAlgorithmConstant() { + resolveAlgorithmFromExpr(this, _, algType) and + algType.matches("SIGNATURE") + } +} + /** * Resolves a call to a 'direct algorithm getter', e.g., EVP_MD5() * This approach to fetching algorithms was used in OpenSSL 1.0.2. @@ -263,8 +272,12 @@ predicate knownOpenSSLAlgorithmLiteral(string name, int nid, string normalized, or name = "ed25519" and nid = 1087 and normalized = "ED25519" and algType = "ELLIPTIC_CURVE" or + name = "ed25519" and nid = 1087 and normalized = "ED25519" and algType = "SIGNATURE" + or name = "ed448" and nid = 1088 and normalized = "ED448" and algType = "ELLIPTIC_CURVE" or + name = "ed448" and nid = 1088 and normalized = "ED448" and algType = "SIGNATURE" + or name = "md2" and nid = 3 and normalized = "MD2" and algType = "HASH" or name = "sha" and nid = 41 and normalized = "SHA" and algType = "HASH" @@ -1684,8 +1697,12 @@ predicate knownOpenSSLAlgorithmLiteral(string name, int nid, string normalized, or name = "x448" and nid = 1035 and normalized = "X448" and algType = "ELLIPTIC_CURVE" or + name = "x448" and nid = 1035 and normalized = "X448" and algType = "KEY_EXCHANGE" + or name = "x25519" and nid = 1034 and normalized = "X25519" and algType = "ELLIPTIC_CURVE" or + name = "x25519" and nid = 1034 and normalized = "X25519" and algType = "KEY_EXCHANGE" + or name = "authecdsa" and nid = 1047 and normalized = "ECDSA" and algType = "SIGNATURE" or name = "authgost01" and nid = 1050 and normalized = "GOST" and algType = "SYMMETRIC_ENCRYPTION" From 007683f06ae0175eec7114d56f8b3eb6c09899ab Mon Sep 17 00:00:00 2001 From: "REDMOND\\brodes" Date: Thu, 22 May 2025 14:06:13 -0400 Subject: [PATCH 7/7] Crypto: Simplifying constant comparisons. --- .../KnownAlgorithmConstants.qll | 34 +++++-------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll index e56f70700b0c..402fbac02ecb 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/KnownAlgorithmConstants.qll @@ -33,30 +33,20 @@ class KnownOpenSSLCipherAlgorithmConstant extends KnownOpenSSLAlgorithmConstant } class KnownOpenSSLPaddingAlgorithmConstant extends KnownOpenSSLAlgorithmConstant { - string algType; - KnownOpenSSLPaddingAlgorithmConstant() { - resolveAlgorithmFromExpr(this, _, algType) and - algType.matches("%PADDING") + exists(string algType | + resolveAlgorithmFromExpr(this, _, algType) and + algType.matches("%PADDING") + ) } } class KnownOpenSSLBlockModeAlgorithmConstant extends KnownOpenSSLAlgorithmConstant { - string algType; - - KnownOpenSSLBlockModeAlgorithmConstant() { - resolveAlgorithmFromExpr(this, _, algType) and - algType.matches("%BLOCK_MODE") - } + KnownOpenSSLBlockModeAlgorithmConstant() { resolveAlgorithmFromExpr(this, _, "BLOCK_MODE") } } class KnownOpenSSLHashAlgorithmConstant extends KnownOpenSSLAlgorithmConstant { - string algType; - - KnownOpenSSLHashAlgorithmConstant() { - resolveAlgorithmFromExpr(this, _, algType) and - algType.matches("%HASH") - } + KnownOpenSSLHashAlgorithmConstant() { resolveAlgorithmFromExpr(this, _, "HASH") } int getExplicitDigestLength() { exists(string name | @@ -69,20 +59,12 @@ class KnownOpenSSLHashAlgorithmConstant extends KnownOpenSSLAlgorithmConstant { class KnownOpenSSLEllipticCurveAlgorithmConstant extends KnownOpenSSLAlgorithmConstant { KnownOpenSSLEllipticCurveAlgorithmConstant() { - exists(string algType | - resolveAlgorithmFromExpr(this, _, algType) and - algType.matches("ELLIPTIC_CURVE") - ) + resolveAlgorithmFromExpr(this, _, "ELLIPTIC_CURVE") } } class KnownOpenSSLSignatureAlgorithmConstant extends KnownOpenSSLAlgorithmConstant { - string algType; - - KnownOpenSSLSignatureAlgorithmConstant() { - resolveAlgorithmFromExpr(this, _, algType) and - algType.matches("SIGNATURE") - } + KnownOpenSSLSignatureAlgorithmConstant() { resolveAlgorithmFromExpr(this, _, "SIGNATURE") } } /**