Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Crypto: Improve literal filtering for OpenSSL for algorithms and generic sources #19553

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

Merged
merged 8 commits into from
May 22, 2025
12 changes: 2 additions & 10 deletions 12 cpp/ql/lib/experimental/quantum/Language.qll
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
private import cpp as Language
import semmle.code.cpp.dataflow.new.TaintTracking
import codeql.quantum.experimental.Model
private import OpenSSL.GenericSourceCandidateLiteral

module CryptoInput implements InputSig<Language::Location> {
class DataFlowNode = DataFlow::Node;
Expand Down Expand Up @@ -88,17 +89,8 @@

module GenericDataSourceFlow = TaintTracking::Global<GenericDataSourceFlowConfig>;

private class ConstantDataSource extends Crypto::GenericConstantSourceInstance instanceof Literal {

Check warning

Code scanning / CodeQL

Suggest using non-extending subtype relationships. Warning

Consider defining this class as non-extending subtype of
OpenSSLGenericSourceCandidateLiteral
.
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" and
// Exclude all 0's as algorithms. Currently we know of no algorithm defined as 0, and
// the typical case is 0 is assigned to represent null.
this.getValue().toInt() != 0
}
ConstantDataSource() { this instanceof OpenSSLGenericSourceCandidateLiteral }

override DataFlow::Node getOutputNode() { result.asExpr() = this }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import cpp
import experimental.quantum.OpenSSL.GenericSourceCandidateLiteral

predicate resolveAlgorithmFromExpr(Expr e, string normalizedName, string algType) {
resolveAlgorithmFromCall(e, normalizedName, algType)
Expand Down Expand Up @@ -101,10 +102,10 @@ 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) {
exists(int nid |
nid = getPossibleNidFromLiteral(e) and knownOpenSSLAlgorithmLiteral(_, nid, normalized, algType)
)
predicate resolveAlgorithmFromLiteral(
OpenSSLGenericSourceCandidateLiteral e, string normalized, string algType
) {
knownOpenSSLAlgorithmLiteral(_, e.getValue().toInt(), normalized, algType)
or
exists(string name |
name = resolveAlgorithmAlias(e.getValue()) and
Expand All @@ -123,30 +124,6 @@ string resolveAlgorithmAlias(string name) {
)
}

private int getPossibleNidFromLiteral(Literal 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
)
}

string getAlgorithmAlias(string alias) {
customAliases(result, alias)
or
Expand Down Expand Up @@ -260,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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import cpp
private import semmle.code.cpp.models.Models
private import semmle.code.cpp.models.interfaces.FormattingFunction

/**
* 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 OpenSSLGenericSourceCandidateLiteral class.
*/
private predicate isOpenSSLStringLiteralGenericSourceCandidate(StringLiteral s) {

Check warning

Code scanning / CodeQL

Acronyms should be PascalCase/camelCase. Warning

Acronyms in isOpenSSLStringLiteralGenericSourceCandidate should be PascalCase/camelCase.
// '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
// Filter out strings with "%", to filter out format strings
not s.getValue().matches("%\\%%") and
// 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 |
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 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().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 OpenSSLGenericSourceCandidateLiteral class.
*/
private predicate isOpenSSLIntLiteralGenericSourceCandidate(Literal l) {
Fixed Show fixed Hide fixed
exists(l.getValue().toInt()) and
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
// 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)
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 not be relevant for crypo
not exists(ArrayExpr op | op.getArrayOffset() = l) and
// 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;
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) 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())
}

/**
* 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 OpenSSLGenericSourceCandidateLiteral extends Literal {

Check warning

Code scanning / CodeQL

Acronyms should be PascalCase/camelCase. Warning

Acronyms in OpenSSLGenericSourceCandidateLiteral should be PascalCase/camelCase.
OpenSSLGenericSourceCandidateLiteral() {
(
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.
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)
}
}
1 change: 1 addition & 0 deletions 1 cpp/ql/lib/experimental/quantum/OpenSSL/OpenSSL.qll
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ module OpenSSLModel {
import AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
import Operations.OpenSSLOperations
import Random
import GenericSourceCandidateLiteral
}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.