Merge branch 'main' into redsun82/rust-expand-attr-macros

This commit is contained in:
Paolo Tranquilli
2025-05-13 16:21:29 +02:00
257 changed files with 38826 additions and 1723 deletions

View File

@@ -68,7 +68,7 @@ jobs:
DATABASE=$2
cd codeql-$QL_VARIANT
SHORTNAME=`basename $DATABASE`
python java/ql/src/utils/modelgenerator/GenerateFlowModel.py --with-summaries --with-sinks $DATABASE $SHORTNAME/$QL_VARIANT
python misc/scripts/models-as-data/generate_mad.py --language java --with-summaries --with-sinks $DATABASE $SHORTNAME/$QL_VARIANT
mkdir -p $MODELS/$SHORTNAME
mv java/ql/lib/ext/generated/$SHORTNAME/$QL_VARIANT $MODELS/$SHORTNAME
cd ..

View File

@@ -17,6 +17,7 @@
# Experimental CodeQL cryptography
**/experimental/quantum/ @github/ps-codeql
/shared/quantum/ @github/ps-codeql
# CodeQL tools and associated docs
/docs/codeql/codeql-cli/ @github/codeql-cli-reviewers

View File

@@ -299,6 +299,7 @@ ql/cpp/ql/src/experimental/cryptography/inventory/new_models/SigningAlgorithms.q
ql/cpp/ql/src/experimental/cryptography/inventory/new_models/SymmetricEncryptionAlgorithms.ql
ql/cpp/ql/src/experimental/cryptography/inventory/new_models/SymmetricPaddingAlgorithms.ql
ql/cpp/ql/src/experimental/cryptography/inventory/new_models/UnknownAsymmetricKeyGeneration.ql
ql/cpp/ql/src/experimental/quantum/PrintCBOMGraph.ql
ql/cpp/ql/src/external/examples/filters/BumpMetricBy10.ql
ql/cpp/ql/src/external/examples/filters/EditDefectMessage.ql
ql/cpp/ql/src/external/examples/filters/ExcludeGeneratedCode.ql

View File

@@ -0,0 +1,113 @@
private import cpp as Language
import semmle.code.cpp.dataflow.new.DataFlow
import codeql.quantum.experimental.Model
module CryptoInput implements InputSig<Language::Location> {
class DataFlowNode = DataFlow::Node;
class LocatableElement = Language::Locatable;
class UnknownLocation = Language::UnknownDefaultLocation;
LocatableElement dfn_to_element(DataFlow::Node node) {
result = node.asExpr() or
result = node.asParameter() or
result = node.asVariable()
}
string locationToFileBaseNameAndLineNumberString(Location location) {
result = location.getFile().getBaseName() + ":" + location.getStartLine()
}
predicate artifactOutputFlowsToGenericInput(
DataFlow::Node artifactOutput, DataFlow::Node otherInput
) {
ArtifactFlow::flow(artifactOutput, otherInput)
}
}
module Crypto = CryptographyBase<Language::Location, CryptoInput>;
module ArtifactFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source = any(Crypto::ArtifactInstance artifact).getOutputNode()
}
predicate isSink(DataFlow::Node sink) {
sink = any(Crypto::FlowAwareElement other).getInputNode()
}
predicate isBarrierOut(DataFlow::Node node) {
node = any(Crypto::FlowAwareElement element).getInputNode()
}
predicate isBarrierIn(DataFlow::Node node) {
node = any(Crypto::FlowAwareElement element).getOutputNode()
}
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
node1.(AdditionalFlowInputStep).getOutput() = node2
}
}
module ArtifactFlow = DataFlow::Global<ArtifactFlowConfig>;
/**
* Artifact output to node input configuration
*/
abstract class AdditionalFlowInputStep extends DataFlow::Node {
abstract DataFlow::Node getOutput();
final DataFlow::Node getInput() { result = this }
}
/**
* Generic data source to node input configuration
*/
module GenericDataSourceFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source = any(Crypto::GenericSourceInstance i).getOutputNode()
}
predicate isSink(DataFlow::Node sink) {
sink = any(Crypto::FlowAwareElement other).getInputNode()
}
predicate isBarrierOut(DataFlow::Node node) {
node = any(Crypto::FlowAwareElement element).getInputNode()
}
predicate isBarrierIn(DataFlow::Node node) {
node = any(Crypto::FlowAwareElement element).getOutputNode()
}
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
node1.(AdditionalFlowInputStep).getOutput() = node2
}
}
module ArtifactUniversalFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source = any(Crypto::ArtifactInstance artifact).getOutputNode()
}
predicate isSink(DataFlow::Node sink) {
sink = any(Crypto::FlowAwareElement other).getInputNode()
}
predicate isBarrierOut(DataFlow::Node node) {
node = any(Crypto::FlowAwareElement element).getInputNode()
}
predicate isBarrierIn(DataFlow::Node node) {
node = any(Crypto::FlowAwareElement element).getOutputNode()
}
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
node1.(AdditionalFlowInputStep).getOutput() = node2
}
}
module ArtifactUniversalFlow = DataFlow::Global<ArtifactUniversalFlowConfig>;
import OpenSSL.OpenSSL

View File

@@ -0,0 +1,170 @@
import cpp
import semmle.code.cpp.dataflow.new.DataFlow
import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmConstants
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
/**
* Traces 'known algorithms' to AVCs, specifically
* algorithms that are in the set of known algorithm constants.
* Padding-specific consumers exist that have their own values that
* overlap with the known algorithm constants.
* Padding consumers (specific padding consumers) are excluded from the set of sinks.
*/
module KnownOpenSSLAlgorithmToAlgorithmValueConsumerConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr() instanceof KnownOpenSSLAlgorithmConstant
}
predicate isSink(DataFlow::Node sink) {
exists(OpenSSLAlgorithmValueConsumer c |
c.getInputNode() = sink and
not c instanceof PaddingAlgorithmValueConsumer
)
}
predicate isBarrier(DataFlow::Node node) {
// False positive reducer, don't flow out through argv
exists(VariableAccess va, Variable v |
v.getAnAccess() = va and va = node.asExpr()
or
va = node.asIndirectExpr()
|
v.getName().matches("%argv")
)
}
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
knownPassThroughStep(node1, node2)
}
}
module KnownOpenSSLAlgorithmToAlgorithmValueConsumerFlow =
DataFlow::Global<KnownOpenSSLAlgorithmToAlgorithmValueConsumerConfig>;
module RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr() instanceof KnownOpenSSLAlgorithmConstant
}
predicate isSink(DataFlow::Node sink) {
exists(PaddingAlgorithmValueConsumer c | c.getInputNode() = sink)
}
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
knownPassThroughStep(node1, node2)
}
}
module RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerFlow =
DataFlow::Global<RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerConfig>;
class OpenSSLAlgorithmAdditionalFlowStep extends AdditionalFlowInputStep {
OpenSSLAlgorithmAdditionalFlowStep() { exists(AlgorithmPassthroughCall c | c.getInNode() = this) }
override DataFlow::Node getOutput() {
exists(AlgorithmPassthroughCall c | c.getInNode() = this and c.getOutNode() = result)
}
}
abstract class AlgorithmPassthroughCall extends Call {
abstract DataFlow::Node getInNode();
abstract DataFlow::Node getOutNode();
}
class CopyAndDupAlgorithmPassthroughCall extends AlgorithmPassthroughCall {
DataFlow::Node inNode;
DataFlow::Node outNode;
CopyAndDupAlgorithmPassthroughCall() {
// Flow out through any return or other argument of the same type
// Assume flow in and out is asIndirectExpr or asDefinitingArgument since a pointer is assumed
// to be involved
// NOTE: not attempting to detect openssl specific copy/dup functions, but anything suspected to be copy/dup
this.getTarget().getName().toLowerCase().matches(["%_dup%", "%_copy%"]) and
exists(Expr inArg, Type t |
inArg = this.getAnArgument() and t = inArg.getUnspecifiedType().stripType()
|
inNode.asIndirectExpr() = inArg and
(
// Case 1: flow through another argument as an out arg of the same type
exists(Expr outArg |
outArg = this.getAnArgument() and
outArg != inArg and
outArg.getUnspecifiedType().stripType() = t
|
outNode.asDefiningArgument() = outArg
)
or
// Case 2: flow through the return value if the result is the same as the intput type
exists(Expr outArg | outArg = this and outArg.getUnspecifiedType().stripType() = t |
outNode.asIndirectExpr() = outArg
)
)
)
}
override DataFlow::Node getInNode() { result = inNode }
override DataFlow::Node getOutNode() { result = outNode }
}
class NIDToPointerPassthroughCall extends AlgorithmPassthroughCall {
DataFlow::Node inNode;
DataFlow::Node outNode;
NIDToPointerPassthroughCall() {
this.getTarget().getName() in ["OBJ_nid2obj", "OBJ_nid2ln", "OBJ_nid2sn"] and
inNode.asExpr() = this.getArgument(0) and
outNode.asExpr() = this
//outNode.asIndirectExpr() = this
}
override DataFlow::Node getInNode() { result = inNode }
override DataFlow::Node getOutNode() { result = outNode }
}
class PointerToPointerPassthroughCall extends AlgorithmPassthroughCall {
DataFlow::Node inNode;
DataFlow::Node outNode;
PointerToPointerPassthroughCall() {
this.getTarget().getName() = "OBJ_txt2obj" and
inNode.asIndirectExpr() = this.getArgument(0) and
outNode.asIndirectExpr() = this
or
//outNode.asExpr() = this
this.getTarget().getName() in ["OBJ_obj2txt", "i2t_ASN1_OBJECT"] and
inNode.asIndirectExpr() = this.getArgument(2) and
outNode.asDefiningArgument() = this.getArgument(0)
}
override DataFlow::Node getInNode() { result = inNode }
override DataFlow::Node getOutNode() { result = outNode }
}
class PointerToNIDPassthroughCall extends AlgorithmPassthroughCall {
DataFlow::Node inNode;
DataFlow::Node outNode;
PointerToNIDPassthroughCall() {
this.getTarget().getName() in ["OBJ_obj2nid", "OBJ_ln2nid", "OBJ_sn2nid", "OBJ_txt2nid"] and
(
inNode.asIndirectExpr() = this.getArgument(0)
or
inNode.asExpr() = this.getArgument(0)
) and
outNode.asExpr() = this
}
override DataFlow::Node getInNode() { result = inNode }
override DataFlow::Node getOutNode() { result = outNode }
}
// TODO: pkeys pass through EVP_PKEY_CTX_new and any similar variant
predicate knownPassThroughStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(AlgorithmPassthroughCall c | c.getInNode() = node1 and c.getOutNode() = node2)
}

View File

@@ -0,0 +1,76 @@
import cpp
import experimental.quantum.Language
import OpenSSLAlgorithmInstanceBase
import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmConstants
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.DirectAlgorithmValueConsumer
import AlgToAVCFlow
/**
* Given a `KnownOpenSSLBlockModeAlgorithmConstant`, converts this to a block family type.
* Does not bind if there is know mapping (no mapping to 'unknown' or 'other').
*/
predicate knownOpenSSLConstantToBlockModeFamilyType(
KnownOpenSSLBlockModeAlgorithmConstant e, Crypto::TBlockCipherModeOfOperationType type
) {
exists(string name |
name = e.getNormalizedName() and
(
name.matches("CBC") and type instanceof Crypto::CBC
or
name.matches("CFB%") and type instanceof Crypto::CFB
or
name.matches("CTR") and type instanceof Crypto::CTR
or
name.matches("GCM") and type instanceof Crypto::GCM
or
name.matches("OFB") and type instanceof Crypto::OFB
or
name.matches("XTS") and type instanceof Crypto::XTS
or
name.matches("CCM") and type instanceof Crypto::CCM
or
name.matches("GCM") and type instanceof Crypto::GCM
or
name.matches("CCM") and type instanceof Crypto::CCM
or
name.matches("ECB") and type instanceof Crypto::ECB
)
)
}
class KnownOpenSSLBlockModeConstantAlgorithmInstance extends OpenSSLAlgorithmInstance,
Crypto::ModeOfOperationAlgorithmInstance instanceof KnownOpenSSLBlockModeAlgorithmConstant
{
OpenSSLAlgorithmValueConsumer getterCall;
KnownOpenSSLBlockModeConstantAlgorithmInstance() {
// Two possibilities:
// 1) The source is a literal and flows to a getter, then we know we have an instance
// 2) The source is a KnownOpenSSLAlgorithm is call, and we know we have an instance immediately from that
// Possibility 1:
this instanceof Literal and
exists(DataFlow::Node src, DataFlow::Node sink |
// Sink is an argument to a CipherGetterCall
sink = getterCall.(OpenSSLAlgorithmValueConsumer).getInputNode() and
// Source is `this`
src.asExpr() = this and
// This traces to a getter
KnownOpenSSLAlgorithmToAlgorithmValueConsumerFlow::flow(src, sink)
)
or
// Possibility 2:
this instanceof DirectAlgorithmValueConsumer and getterCall = this
}
override Crypto::TBlockCipherModeOfOperationType getModeType() {
knownOpenSSLConstantToBlockModeFamilyType(this, result)
or
not knownOpenSSLConstantToBlockModeFamilyType(this, _) and result = Crypto::OtherMode()
}
// NOTE: I'm not going to attempt to parse out the mode specific part, so returning
// the same as the raw name for now.
override string getRawModeAlgorithmName() { result = this.(Literal).getValue().toString() }
override OpenSSLAlgorithmValueConsumer getAVC() { result = getterCall }
}

View File

@@ -0,0 +1,126 @@
import cpp
import experimental.quantum.Language
import KnownAlgorithmConstants
import Crypto::KeyOpAlg as KeyOpAlg
import OpenSSLAlgorithmInstanceBase
import PaddingAlgorithmInstance
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
import AlgToAVCFlow
import BlockAlgorithmInstance
/**
* Given a `KnownOpenSSLCipherAlgorithmConstant`, converts this to a cipher family type.
* Does not bind if there is know mapping (no mapping to 'unknown' or 'other').
*/
predicate knownOpenSSLConstantToCipherFamilyType(
KnownOpenSSLCipherAlgorithmConstant e, Crypto::KeyOpAlg::TAlgorithm type
) {
exists(string name |
name = e.getNormalizedName() and
(
name.matches("AES%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::AES())
or
name.matches("ARIA%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::ARIA())
or
name.matches("BLOWFISH%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::BLOWFISH())
or
name.matches("BF%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::BLOWFISH())
or
name.matches("CAMELLIA%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::CAMELLIA())
or
name.matches("CHACHA20%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::CHACHA20())
or
name.matches("CAST5%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::CAST5())
or
name.matches("2DES%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::DoubleDES())
or
name.matches("3DES%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::TripleDES())
or
name.matches("DES%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::DES())
or
name.matches("DESX%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::DESX())
or
name.matches("GOST%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::GOST())
or
name.matches("IDEA%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::IDEA())
or
name.matches("KUZNYECHIK%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::KUZNYECHIK())
or
name.matches("MAGMA%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::MAGMA())
or
name.matches("RC2%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::RC2())
or
name.matches("RC4%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::RC4())
or
name.matches("RC5%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::RC5())
or
name.matches("RSA%") and type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA())
or
name.matches("SEED%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::SEED())
or
name.matches("SM4%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::SM4())
)
)
}
class KnownOpenSSLCipherConstantAlgorithmInstance extends OpenSSLAlgorithmInstance,
Crypto::KeyOperationAlgorithmInstance instanceof KnownOpenSSLCipherAlgorithmConstant
{
OpenSSLAlgorithmValueConsumer getterCall;
KnownOpenSSLCipherConstantAlgorithmInstance() {
// Two possibilities:
// 1) The source is a literal and flows to a getter, then we know we have an instance
// 2) The source is a KnownOpenSSLAlgorithm is call, and we know we have an instance immediately from that
// Possibility 1:
this instanceof Literal and
exists(DataFlow::Node src, DataFlow::Node sink |
// Sink is an argument to a CipherGetterCall
sink = getterCall.(OpenSSLAlgorithmValueConsumer).getInputNode() and
// Source is `this`
src.asExpr() = this and
// This traces to a getter
KnownOpenSSLAlgorithmToAlgorithmValueConsumerFlow::flow(src, sink)
)
or
// Possibility 2:
this instanceof DirectAlgorithmValueConsumer and getterCall = this
}
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() {
// if there is a block mode associated with the same element, then that's the block mode
// note, if none are associated, we may need to parse if the cipher is a block cipher
// to determine if this is an unknown vs not relevant.
result = this
}
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() {
//TODO: the padding is either self, or it flows through getter ctx to a set padding call
// like EVP_PKEY_CTX_set_rsa_padding
result = this
// TODO or trace through getter ctx to set padding
}
override string getRawAlgorithmName() { result = this.(Literal).getValue().toString() }
override string getKeySizeFixed() {
exists(int keySize |
this.(KnownOpenSSLCipherAlgorithmConstant).getExplicitKeySize() = keySize and
result = keySize.toString()
)
}
override Crypto::KeyOpAlg::Algorithm getAlgorithmType() {
knownOpenSSLConstantToCipherFamilyType(this, result)
or
not knownOpenSSLConstantToCipherFamilyType(this, _) and
result = Crypto::KeyOpAlg::TUnknownKeyOperationAlgorithmType()
}
override OpenSSLAlgorithmValueConsumer getAVC() { result = getterCall }
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
// TODO: trace to any key size initializer, symmetric and asymmetric
none()
}
}

View File

@@ -0,0 +1,83 @@
import cpp
import experimental.quantum.Language
import KnownAlgorithmConstants
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
import AlgToAVCFlow
predicate knownOpenSSLConstantToHashFamilyType(
KnownOpenSSLHashAlgorithmConstant e, Crypto::THashType type
) {
exists(string name |
name = e.getNormalizedName() and
(
name.matches("BLAKE2B") and type instanceof Crypto::BLAKE2B
or
name.matches("BLAKE2S") and type instanceof Crypto::BLAKE2S
or
name.matches("GOST%") and type instanceof Crypto::GOSTHash
or
name.matches("MD2") and type instanceof Crypto::MD2
or
name.matches("MD4") and type instanceof Crypto::MD4
or
name.matches("MD5") and type instanceof Crypto::MD5
or
name.matches("MDC2") and type instanceof Crypto::MDC2
or
name.matches("POLY1305") and type instanceof Crypto::POLY1305
or
name.matches(["SHA", "SHA1"]) and type instanceof Crypto::SHA1
or
name.matches("SHA+%") and not name.matches(["SHA1", "SHA3-"]) and type instanceof Crypto::SHA2
or
name.matches("SHA3-%") and type instanceof Crypto::SHA3
or
name.matches(["SHAKE"]) and type instanceof Crypto::SHAKE
or
name.matches("SM3") and type instanceof Crypto::SM3
or
name.matches("RIPEMD160") and type instanceof Crypto::RIPEMD160
or
name.matches("WHIRLPOOL") and type instanceof Crypto::WHIRLPOOL
)
)
}
class KnownOpenSSLHashConstantAlgorithmInstance extends OpenSSLAlgorithmInstance,
Crypto::HashAlgorithmInstance instanceof KnownOpenSSLHashAlgorithmConstant
{
OpenSSLAlgorithmValueConsumer getterCall;
KnownOpenSSLHashConstantAlgorithmInstance() {
// Two possibilities:
// 1) The source is a literal and flows to a getter, then we know we have an instance
// 2) The source is a KnownOpenSSLAlgorithm is call, and we know we have an instance immediately from that
// Possibility 1:
this instanceof Literal and
exists(DataFlow::Node src, DataFlow::Node sink |
// Sink is an argument to a CipherGetterCall
sink = getterCall.(OpenSSLAlgorithmValueConsumer).getInputNode() and
// Source is `this`
src.asExpr() = this and
// This traces to a getter
KnownOpenSSLAlgorithmToAlgorithmValueConsumerFlow::flow(src, sink)
)
or
// Possibility 2:
this instanceof DirectAlgorithmValueConsumer and getterCall = this
}
override OpenSSLAlgorithmValueConsumer getAVC() { result = getterCall }
override Crypto::THashType getHashFamily() {
knownOpenSSLConstantToHashFamilyType(this, result)
or
not knownOpenSSLConstantToHashFamilyType(this, _) and result = Crypto::OtherHashType()
}
override string getRawHashAlgorithmName() { result = this.(Literal).getValue().toString() }
override int getFixedDigestLength() {
this.(KnownOpenSSLHashAlgorithmConstant).getExplicitDigestLength() = result
}
}

View File

@@ -0,0 +1,6 @@
import experimental.quantum.Language
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumerBase
abstract class OpenSSLAlgorithmInstance extends Crypto::AlgorithmInstance {
abstract OpenSSLAlgorithmValueConsumer getAVC();
}

View File

@@ -0,0 +1,5 @@
import OpenSSLAlgorithmInstanceBase
import CipherAlgorithmInstance
import PaddingAlgorithmInstance
import BlockAlgorithmInstance
import HashAlgorithmInstance

View File

@@ -0,0 +1,167 @@
import cpp
import experimental.quantum.Language
import OpenSSLAlgorithmInstanceBase
import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmConstants
import AlgToAVCFlow
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.DirectAlgorithmValueConsumer
/**
* Given a `KnownOpenSSLPaddingAlgorithmConstant`, converts this to a padding family type.
* Does not bind if there is know mapping (no mapping to 'unknown' or 'other').
*/
predicate knownOpenSSLConstantToPaddingFamilyType(
KnownOpenSSLPaddingAlgorithmConstant e, Crypto::TPaddingType type
) {
exists(string name |
name = e.getNormalizedName() and
(
name.matches("OAEP") and type = Crypto::OAEP()
or
name.matches("PSS") and type = Crypto::PSS()
or
name.matches("PKCS7") and type = Crypto::PKCS7()
or
name.matches("PKCS1V15") and type = Crypto::PKCS1_v1_5()
)
)
}
//abstract class OpenSSLPaddingAlgorithmInstance extends OpenSSLAlgorithmInstance, Crypto::PaddingAlgorithmInstance{}
// TODO: need to alter this to include known padding constants which don't have the
// same mechanics as those with known nids
class KnownOpenSSLPaddingConstantAlgorithmInstance extends OpenSSLAlgorithmInstance,
Crypto::PaddingAlgorithmInstance instanceof Expr
{
OpenSSLAlgorithmValueConsumer getterCall;
boolean isPaddingSpecificConsumer;
KnownOpenSSLPaddingConstantAlgorithmInstance() {
// three possibilities:
// 1) The source is a 'typical' literal and flows to a getter, then we know we have an instance
// 2) The source is a KnownOpenSSLAlgorithm is call, and we know we have an instance immediately from that
// 3) the source is a padding-specific literal flowing to a padding-specific consumer
// Possibility 1:
this instanceof Literal and
this instanceof KnownOpenSSLPaddingAlgorithmConstant and
exists(DataFlow::Node src, DataFlow::Node sink |
// Sink is an argument to a CipherGetterCall
sink = getterCall.(OpenSSLAlgorithmValueConsumer).getInputNode() and
// Source is `this`
src.asExpr() = this and
// This traces to a getter
KnownOpenSSLAlgorithmToAlgorithmValueConsumerFlow::flow(src, sink) and
isPaddingSpecificConsumer = false
)
or
// Possibility 2:
this instanceof DirectAlgorithmValueConsumer and
getterCall = this and
this instanceof KnownOpenSSLPaddingAlgorithmConstant and
isPaddingSpecificConsumer = false
or
// Possibility 3:
// from rsa.h in openssl:
// # define RSA_PKCS1_PADDING 1
// # define RSA_NO_PADDING 3
// # define RSA_PKCS1_OAEP_PADDING 4
// # define RSA_X931_PADDING 5
// /* EVP_PKEY_ only */
// # define RSA_PKCS1_PSS_PADDING 6
// # define RSA_PKCS1_WITH_TLS_PADDING 7
// /* internal RSA_ only */
// # define RSA_PKCS1_NO_IMPLICIT_REJECT_PADDING 8
this instanceof Literal and
this.getValue().toInt() in [0, 1, 3, 4, 5, 6, 7, 8] and
exists(DataFlow::Node src, DataFlow::Node sink |
// Sink is an argument to a CipherGetterCall
sink = getterCall.(OpenSSLAlgorithmValueConsumer).getInputNode() and
// Source is `this`
src.asExpr() = this and
// This traces to a padding-specific consumer
RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerFlow::flow(src, sink)
) and
isPaddingSpecificConsumer = true
}
override string getRawPaddingAlgorithmName() { result = this.(Literal).getValue().toString() }
override OpenSSLAlgorithmValueConsumer getAVC() { result = getterCall }
override Crypto::TPaddingType getPaddingType() {
isPaddingSpecificConsumer = true and
(
if this.(Literal).getValue().toInt() in [1, 7, 8]
then result = Crypto::PKCS1_v1_5()
else
if this.(Literal).getValue().toInt() = 3
then result = Crypto::NoPadding()
else
if this.(Literal).getValue().toInt() = 4
then result = Crypto::OAEP()
else
if this.(Literal).getValue().toInt() = 5
then result = Crypto::ANSI_X9_23()
else
if this.(Literal).getValue().toInt() = 6
then result = Crypto::PSS()
else result = Crypto::OtherPadding()
)
or
isPaddingSpecificConsumer = false and
knownOpenSSLConstantToPaddingFamilyType(this, result)
}
}
// // Values used for EVP_PKEY_CTX_set_rsa_padding, these are
// // not the same as 'typical' constants found in the set of known algorithm constants
// // they do not have an NID
// // TODO: what about setting the padding directly?
// class KnownRSAPaddingConstant extends OpenSSLPaddingAlgorithmInstance, Crypto::PaddingAlgorithmInstance instanceof Literal
// {
// KnownRSAPaddingConstant() {
// // from rsa.h in openssl:
// // # define RSA_PKCS1_PADDING 1
// // # define RSA_NO_PADDING 3
// // # define RSA_PKCS1_OAEP_PADDING 4
// // # define RSA_X931_PADDING 5
// // /* EVP_PKEY_ only */
// // # define RSA_PKCS1_PSS_PADDING 6
// // # define RSA_PKCS1_WITH_TLS_PADDING 7
// // /* internal RSA_ only */
// // # define RSA_PKCS1_NO_IMPLICIT_REJECT_PADDING 8
// this instanceof Literal and
// this.getValue().toInt() in [0, 1, 3, 4, 5, 6, 7, 8]
// // TODO: trace to padding-specific consumers
// RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerFlow
// }
// override string getRawPaddingAlgorithmName() { result = this.(Literal).getValue().toString() }
// override Crypto::TPaddingType getPaddingType() {
// if this.(Literal).getValue().toInt() in [1, 6, 7, 8]
// then result = Crypto::PKCS1_v1_5()
// else
// if this.(Literal).getValue().toInt() = 3
// then result = Crypto::NoPadding()
// else
// if this.(Literal).getValue().toInt() = 4
// then result = Crypto::OAEP()
// else
// if this.(Literal).getValue().toInt() = 5
// then result = Crypto::ANSI_X9_23()
// else result = Crypto::OtherPadding()
// }
// }
class OAEPPaddingAlgorithmInstance extends Crypto::OAEPPaddingAlgorithmInstance,
KnownOpenSSLPaddingConstantAlgorithmInstance
{
OAEPPaddingAlgorithmInstance() {
this.(Crypto::PaddingAlgorithmInstance).getPaddingType() = Crypto::OAEP()
}
override Crypto::HashAlgorithmInstance getOAEPEncodingHashAlgorithm() {
none() //TODO
}
override Crypto::HashAlgorithmInstance getMGF1HashAlgorithm() {
none() //TODO
}
}

View File

@@ -0,0 +1,39 @@
import cpp
import experimental.quantum.Language
import experimental.quantum.OpenSSL.LibraryDetector
import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmConstants
import experimental.quantum.OpenSSL.AlgorithmInstances.OpenSSLAlgorithmInstanceBase
import OpenSSLAlgorithmValueConsumerBase
abstract class CipherAlgorithmValueConsumer extends OpenSSLAlgorithmValueConsumer { }
// https://www.openssl.org/docs/manmaster/man3/EVP_CIPHER_fetch.html
class EVPCipherAlgorithmValueConsumer extends CipherAlgorithmValueConsumer {
DataFlow::Node valueArgNode;
DataFlow::Node resultNode;
EVPCipherAlgorithmValueConsumer() {
resultNode.asExpr() = this and
isPossibleOpenSSLFunction(this.(Call).getTarget()) and
(
this.(Call).getTarget().getName() in [
"EVP_get_cipherbyname", "EVP_get_cipherbyobj", "EVP_get_cipherbynid"
] and
valueArgNode.asExpr() = this.(Call).getArgument(0)
or
this.(Call).getTarget().getName() in ["EVP_CIPHER_fetch", "EVP_ASYM_CIPHER_fetch"] and
valueArgNode.asExpr() = this.(Call).getArgument(1)
)
}
override DataFlow::Node getResultNode() { result = resultNode }
override Crypto::ConsumerInputDataFlowNode getInputNode() { result = valueArgNode }
// override DataFlow::Node getInputNode() { result = valueArgNode }
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
exists(OpenSSLAlgorithmInstance i | i.getAVC() = this and result = i)
//TODO: As a potential alternative, for OpenSSL only, add a generic source node for literals and only create flow (flowsTo) to
// OpenSSL AVCs... the unknown literal sources would have to be any literals not in the known set.
}
}

View File

@@ -0,0 +1,36 @@
import cpp
import experimental.quantum.Language
import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmConstants
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumerBase
// TODO: can self referential to itself, which is also an algorithm (Known algorithm)
/**
* Cases like EVP_MD5(),
* there is no input, rather it directly gets an algorithm
* and returns it.
*/
class DirectAlgorithmValueConsumer extends OpenSSLAlgorithmValueConsumer {
DataFlow::Node resultNode;
Expr resultExpr;
DirectAlgorithmValueConsumer() {
this instanceof KnownOpenSSLAlgorithmConstant and
this instanceof Call and
resultExpr = this and
resultNode.asExpr() = resultExpr
}
/**
* These cases take in no explicit value (the value is implicit)
*/
override Crypto::ConsumerInputDataFlowNode getInputNode() { none() }
override DataFlow::Node getResultNode() { result = resultNode }
// override DataFlow::Node getOutputNode() { result = resultNode }
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
// Note: algorithm source definitions enforces that
// this class will be a known algorithm source
result = this
}
}

View File

@@ -0,0 +1,35 @@
// import EVPHashInitializer
// import EVPHashOperation
// import EVPHashAlgorithmSource
import cpp
import experimental.quantum.Language
import semmle.code.cpp.dataflow.new.DataFlow
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumerBase
import experimental.quantum.OpenSSL.AlgorithmInstances.OpenSSLAlgorithmInstances
import experimental.quantum.OpenSSL.LibraryDetector
abstract class HashAlgorithmValueConsumer extends OpenSSLAlgorithmValueConsumer { }
/**
* EVP_Q_Digest directly consumes algorithm constant values
*/
class EVP_Q_Digest_Algorithm_Consumer extends OpenSSLAlgorithmValueConsumer {
EVP_Q_Digest_Algorithm_Consumer() {
isPossibleOpenSSLFunction(this.(Call).getTarget()) and
this.(Call).getTarget().getName() = "EVP_Q_digest"
}
override Crypto::ConsumerInputDataFlowNode getInputNode() {
result.asExpr() = this.(Call).getArgument(1)
}
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
exists(OpenSSLAlgorithmInstance i | i.getAVC() = this and result = i)
}
override DataFlow::Node getResultNode() {
// EVP_Q_Digest directly consumes the algorithm constant value and performs the operation, there is no
// algorithm result
none()
}
}

View File

@@ -0,0 +1,9 @@
import experimental.quantum.Language
import semmle.code.cpp.dataflow.new.DataFlow
abstract class OpenSSLAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer instanceof Call {
/**
* Returns the node representing the resulting algorithm
*/
abstract DataFlow::Node getResultNode();
}

View File

@@ -0,0 +1,5 @@
import OpenSSLAlgorithmValueConsumerBase
import CipherAlgorithmValueConsumer
import DirectAlgorithmValueConsumer
import PaddingAlgorithmValueConsumer
import HashAlgorithmValueConsumer

View File

@@ -0,0 +1,36 @@
import cpp
import experimental.quantum.Language
import experimental.quantum.OpenSSL.LibraryDetector
import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmConstants
import experimental.quantum.OpenSSL.AlgorithmInstances.OpenSSLAlgorithmInstanceBase
import OpenSSLAlgorithmValueConsumerBase
abstract class PaddingAlgorithmValueConsumer extends OpenSSLAlgorithmValueConsumer { }
// https://docs.openssl.org/master/man7/EVP_ASYM_CIPHER-RSA/#rsa-asymmetric-cipher-parameters
// TODO: need to handle setting padding through EVP_PKEY_CTX_set_params, where modes like "OSSL_PKEY_RSA_PAD_MODE_OAEP"
// are set.
class EVP_PKEY_CTX_set_rsa_padding_AlgorithmValueConsumer extends PaddingAlgorithmValueConsumer {
DataFlow::Node valueArgNode;
DataFlow::Node resultNode;
EVP_PKEY_CTX_set_rsa_padding_AlgorithmValueConsumer() {
resultNode.asExpr() = this and
isPossibleOpenSSLFunction(this.(Call).getTarget()) and
(
this.(Call).getTarget().getName() in ["EVP_PKEY_CTX_set_rsa_padding"] and
valueArgNode.asExpr() = this.(Call).getArgument(1)
)
}
override DataFlow::Node getResultNode() { result = resultNode }
override Crypto::ConsumerInputDataFlowNode getInputNode() { result = valueArgNode }
// override DataFlow::Node getInputNode() { result = valueArgNode }
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
exists(OpenSSLAlgorithmInstance i | i.getAVC() = this and result = i)
//TODO: As a potential alternative, for OpenSSL only, add a generic source node for literals and only create flow (flowsTo) to
// OpenSSL AVCs... the unknown literal sources would have to be any literals not in the known set.
}
}

View File

@@ -0,0 +1,99 @@
//TODO: model as data on open APIs should be able to get common flows, and obviate some of this
// e.g., copy/dup calls, need to ingest those models for openSSL and refactor.
/**
* In OpenSSL, flow between 'context' parameters is often used to
* store state/config of how an operation will eventually be performed.
* Tracing algorithms and configurations to operations therefore
* requires tracing context parameters for many OpenSSL apis.
*
* This library provides a dataflow analysis to track context parameters
* between any two functions accepting openssl context parameters.
* The dataflow takes into consideration flowing through duplication and copy calls
* as well as flow through flow killers (free/reset calls).
*
* TODO: we may need to revisit 'free' as a dataflow killer, depending on how
* we want to model use after frees.
*
* This library also provides classes to represent context Types and relevant
* arguments/expressions.
*/
import semmle.code.cpp.dataflow.new.DataFlow
class CTXType extends Type {
CTXType() {
// TODO: should we limit this to an openssl path?
this.getUnspecifiedType().stripType().getName().matches("evp_%ctx_%st")
}
}
class CTXPointerExpr extends Expr {
CTXPointerExpr() {
this.getType() instanceof CTXType and
this.getType() instanceof PointerType
}
}
class CTXPointerArgument extends CTXPointerExpr {
CTXPointerArgument() { exists(Call c | c.getAnArgument() = this) }
Call getCall() { result.getAnArgument() = this }
}
class CTXClearCall extends Call {
CTXClearCall() {
this.getTarget().getName().toLowerCase().matches(["%free%", "%reset%"]) and
this.getAnArgument() instanceof CTXPointerArgument
}
}
class CTXCopyOutArgCall extends Call {
CTXCopyOutArgCall() {
this.getTarget().getName().toLowerCase().matches(["%copy%"]) and
this.getAnArgument() instanceof CTXPointerArgument
}
}
class CTXCopyReturnCall extends Call {
CTXCopyReturnCall() {
this.getTarget().getName().toLowerCase().matches(["%dup%"]) and
this.getAnArgument() instanceof CTXPointerArgument and
this instanceof CTXPointerExpr
}
}
module OpenSSLCTXArgumentFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source.asExpr() instanceof CTXPointerArgument }
predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof CTXPointerArgument }
predicate isBarrier(DataFlow::Node node) {
exists(CTXClearCall c | c.getAnArgument() = node.asExpr())
}
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(CTXCopyOutArgCall c |
c.getAnArgument() = node1.asExpr() and
c.getAnArgument() = node2.asExpr() and
node1.asExpr() != node2.asExpr() and
node2.asExpr().getType() instanceof CTXType
)
or
exists(CTXCopyReturnCall c |
c.getAnArgument() = node1.asExpr() and
c = node2.asExpr() and
node1.asExpr() != node2.asExpr() and
node2.asExpr().getType() instanceof CTXType
)
}
}
module OpenSSLCTXArgumentFlow = DataFlow::Global<OpenSSLCTXArgumentFlowConfig>;
predicate ctxArgFlowsToCtxArg(CTXPointerArgument source, CTXPointerArgument sink) {
exists(DataFlow::Node a, DataFlow::Node b |
OpenSSLCTXArgumentFlow::flow(a, b) and
a.asExpr() = source and
b.asExpr() = sink
)
}

View File

@@ -0,0 +1,7 @@
import cpp
predicate isPossibleOpenSSLFunction(Function f) {
isPossibleOpenSSLLocation(f.getADeclarationLocation())
}
predicate isPossibleOpenSSLLocation(Location l) { l.toString().toLowerCase().matches("%openssl%") }

View File

@@ -0,0 +1,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
}

View File

@@ -0,0 +1,123 @@
/**
* see: https://docs.openssl.org/master/man3/EVP_EncryptInit/
* Models cipher initialization for EVP cipher operations.
*/
import experimental.quantum.Language
import experimental.quantum.OpenSSL.CtxFlow as CTXFlow
module EncValToInitEncArgConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source.asExpr().getValue().toInt() in [0, 1] }
predicate isSink(DataFlow::Node sink) {
exists(EVP_Cipher_Initializer initCall | sink.asExpr() = initCall.getOperationSubtypeArg())
}
}
module EncValToInitEncArgFlow = DataFlow::Global<EncValToInitEncArgConfig>;
int getEncConfigValue(Expr e) {
exists(EVP_Cipher_Initializer initCall | e = initCall.getOperationSubtypeArg()) and
exists(DataFlow::Node a, DataFlow::Node b |
EncValToInitEncArgFlow::flow(a, b) and b.asExpr() = e and result = a.asExpr().getValue().toInt()
)
}
bindingset[i]
Crypto::KeyOperationSubtype intToCipherOperationSubtype(int i) {
if i = 0
then result instanceof Crypto::TEncryptMode
else
if i = 1
then result instanceof Crypto::TDecryptMode
else result instanceof Crypto::TUnknownKeyOperationMode
}
// TODO: need to add key consumer
abstract class EVP_Cipher_Initializer extends Call {
Expr getContextArg() { result = this.(Call).getArgument(0) }
Expr getAlgorithmArg() { result = this.(Call).getArgument(1) }
abstract Expr getKeyArg();
abstract Expr getIVArg();
// abstract Crypto::CipherOperationSubtype getCipherOperationSubtype();
abstract Expr getOperationSubtypeArg();
Crypto::KeyOperationSubtype getCipherOperationSubtype() {
if this.(Call).getTarget().getName().toLowerCase().matches("%encrypt%")
then result instanceof Crypto::TEncryptMode
else
if this.(Call).getTarget().getName().toLowerCase().matches("%decrypt%")
then result instanceof Crypto::TDecryptMode
else
if exists(getEncConfigValue(this.getOperationSubtypeArg()))
then result = intToCipherOperationSubtype(getEncConfigValue(this.getOperationSubtypeArg()))
else result instanceof Crypto::TUnknownKeyOperationMode
}
}
abstract class EVP_EX_Initializer extends EVP_Cipher_Initializer {
override Expr getKeyArg() { result = this.(Call).getArgument(3) }
override Expr getIVArg() { result = this.(Call).getArgument(4) }
}
abstract class EVP_EX2_Initializer extends EVP_Cipher_Initializer {
override Expr getKeyArg() { result = this.(Call).getArgument(2) }
override Expr getIVArg() { result = this.(Call).getArgument(3) }
}
class EVP_Cipher_EX_Init_Call extends EVP_EX_Initializer {
EVP_Cipher_EX_Init_Call() {
this.(Call).getTarget().getName() in [
"EVP_EncryptInit_ex", "EVP_DecryptInit_ex", "EVP_CipherInit_ex"
]
}
override Expr getOperationSubtypeArg() {
this.(Call).getTarget().getName().toLowerCase().matches("%cipherinit%") and
result = this.(Call).getArgument(5)
}
}
class EVP_Cipher_EX2_or_Simple_Init_Call extends EVP_EX2_Initializer {
EVP_Cipher_EX2_or_Simple_Init_Call() {
this.(Call).getTarget().getName() in [
"EVP_EncryptInit_ex2", "EVP_DecryptInit_ex2", "EVP_CipherInit_ex2", "EVP_EncryptInit",
"EVP_DecryptInit", "EVP_CipherInit"
]
}
override Expr getOperationSubtypeArg() {
this.(Call).getTarget().getName().toLowerCase().matches("%cipherinit%") and
result = this.(Call).getArgument(4)
}
}
class EVP_CipherInit_SKEY_Call extends EVP_EX2_Initializer {
EVP_CipherInit_SKEY_Call() { this.(Call).getTarget().getName() in ["EVP_CipherInit_SKEY"] }
override Expr getOperationSubtypeArg() { result = this.(Call).getArgument(5) }
}
class EVPCipherInitializerAlgorithmArgument extends Expr {
EVPCipherInitializerAlgorithmArgument() {
exists(EVP_Cipher_Initializer initCall | this = initCall.getAlgorithmArg())
}
}
class EVPCipherInitializerKeyArgument extends Expr {
EVPCipherInitializerKeyArgument() {
exists(EVP_Cipher_Initializer initCall | this = initCall.getKeyArg())
}
}
class EVPCipherInitializerIVArgument extends Expr {
EVPCipherInitializerIVArgument() {
exists(EVP_Cipher_Initializer initCall | this = initCall.getIVArg())
}
}

View File

@@ -0,0 +1,116 @@
import experimental.quantum.Language
import experimental.quantum.OpenSSL.CtxFlow as CTXFlow
import EVPCipherInitializer
import OpenSSLOperationBase
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
private module AlgGetterToAlgConsumerConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
exists(OpenSSLAlgorithmValueConsumer c | c.getResultNode() = source)
}
predicate isSink(DataFlow::Node sink) {
exists(EVP_Cipher_Operation c | c.getInitCall().getAlgorithmArg() = sink.asExpr())
}
}
private module AlgGetterToAlgConsumerFlow = DataFlow::Global<AlgGetterToAlgConsumerConfig>;
// import experimental.quantum.OpenSSL.AlgorithmValueConsumers.AlgorithmValueConsumers
// import OpenSSLOperation
// class EVPCipherOutput extends CipherOutputArtifact {
// EVPCipherOutput() { exists(EVP_Cipher_Operation op | op.getOutputArg() = this) }
// override DataFlow::Node getOutputNode() { result.asDefiningArgument() = this }
// }
//
/**
* see: https://docs.openssl.org/master/man3/EVP_EncryptInit/#synopsis
* Base configuration for all EVP cipher operations.
* NOTE: cannot extend instance of OpenSSLOperation, as we need to override
* elements of OpenSSLOperation (i.e., we are creating an instance)
*/
abstract class EVP_Cipher_Operation extends OpenSSLOperation, Crypto::KeyOperationInstance {
Expr getContextArg() { result = this.(Call).getArgument(0) }
override Expr getOutputArg() { result = this.(Call).getArgument(1) }
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
result instanceof Crypto::TEncryptMode and
this.(Call).getTarget().getName().toLowerCase().matches("%encrypt%")
or
result instanceof Crypto::TDecryptMode and
this.(Call).getTarget().getName().toLowerCase().matches("%decrypt%")
or
result = this.getInitCall().getCipherOperationSubtype() and
this.(Call).getTarget().getName().toLowerCase().matches("%cipher%")
}
EVP_Cipher_Initializer getInitCall() {
CTXFlow::ctxArgFlowsToCtxArg(result.getContextArg(), this.getContextArg())
}
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() {
this.getInitCall().getIVArg() = result.asExpr()
}
override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() }
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
this.getInitCall().getKeyArg() = result.asExpr()
}
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result = this.getOutputNode() }
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
AlgGetterToAlgConsumerFlow::flow(result.(OpenSSLAlgorithmValueConsumer).getResultNode(),
DataFlow::exprNode(this.getInitCall().getAlgorithmArg()))
}
}
// abstract class EVP_Update_Call extends EVP_Cipher_Operation { }
abstract class EVP_Final_Call extends EVP_Cipher_Operation {
override Expr getInputArg() { none() }
}
// TODO: only model Final (model final as operation and model update but not as an operation)
// Updates are multiple input consumers (most important)
// TODO: assuming update doesn't ouput, otherwise it outputs artifacts, but is not an operation
class EVP_Cipher_Call extends EVP_Cipher_Operation {
EVP_Cipher_Call() { this.(Call).getTarget().getName() = "EVP_Cipher" }
override Expr getInputArg() { result = this.(Call).getArgument(2) }
}
// ******* TODO: model UPDATE but not as the core operation, rather a step towards final
// see the JCA
// class EVP_Encrypt_Decrypt_or_Cipher_Update_Call extends EVP_Update_Call {
// EVP_Encrypt_Decrypt_or_Cipher_Update_Call() {
// this.(Call).getTarget().getName() in [
// "EVP_EncryptUpdate", "EVP_DecryptUpdate", "EVP_CipherUpdate"
// ]
// }
// override Expr getInputArg() { result = this.(Call).getArgument(3) }
// }
class EVP_Encrypt_Decrypt_or_Cipher_Final_Call extends EVP_Final_Call {
EVP_Encrypt_Decrypt_or_Cipher_Final_Call() {
this.(Call).getTarget().getName() in [
"EVP_EncryptFinal_ex", "EVP_DecryptFinal_ex", "EVP_CipherFinal_ex", "EVP_EncryptFinal",
"EVP_DecryptFinal", "EVP_CipherFinal"
]
}
}
class EVP_PKEY_Operation extends EVP_Cipher_Operation {
EVP_PKEY_Operation() {
this.(Call).getTarget().getName() in ["EVP_PKEY_decrypt", "EVP_PKEY_encrypt"]
}
override Expr getInputArg() { result = this.(Call).getArgument(3) }
// TODO: how PKEY is initialized is different that symmetric cipher
// Consider making an entirely new class for this and specializing
// the get init call
}
class EVPCipherInputArgument extends Expr {
EVPCipherInputArgument() { exists(EVP_Cipher_Operation op | op.getInputArg() = this) }
}

View File

@@ -0,0 +1,17 @@
import cpp
abstract class EVP_Hash_Initializer extends Call {
Expr getContextArg() { result = this.(Call).getArgument(0) }
abstract Expr getAlgorithmArg();
}
class EVP_DigestInit_Variant_Calls extends EVP_Hash_Initializer {
EVP_DigestInit_Variant_Calls() {
this.(Call).getTarget().getName() in [
"EVP_DigestInit", "EVP_DigestInit_ex", "EVP_DigestInit_ex2"
]
}
override Expr getAlgorithmArg() { result = this.(Call).getArgument(1) }
}

View File

@@ -0,0 +1,117 @@
/**
* https://docs.openssl.org/3.0/man3/EVP_DigestInit/#synopsis
*/
import experimental.quantum.Language
import experimental.quantum.OpenSSL.CtxFlow as CTXFlow
import experimental.quantum.OpenSSL.LibraryDetector
import OpenSSLOperationBase
import EVPHashInitializer
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
// import EVPHashConsumers
abstract class EVP_Hash_Operation extends OpenSSLOperation, Crypto::HashOperationInstance {
Expr getContextArg() { result = this.(Call).getArgument(0) }
EVP_Hash_Initializer getInitCall() {
CTXFlow::ctxArgFlowsToCtxArg(result.getContextArg(), this.getContextArg())
}
}
private module AlgGetterToAlgConsumerConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
exists(OpenSSLAlgorithmValueConsumer c | c.getResultNode() = source)
}
predicate isSink(DataFlow::Node sink) {
exists(EVP_Hash_Operation c | c.getInitCall().getAlgorithmArg() = sink.asExpr())
}
}
private module AlgGetterToAlgConsumerFlow = DataFlow::Global<AlgGetterToAlgConsumerConfig>;
//https://docs.openssl.org/3.0/man3/EVP_DigestInit/#synopsis
class EVP_Q_Digest_Operation extends EVP_Hash_Operation {
EVP_Q_Digest_Operation() {
this.(Call).getTarget().getName() = "EVP_Q_digest" and
isPossibleOpenSSLFunction(this.(Call).getTarget())
}
//override Crypto::AlgorithmConsumer getAlgorithmConsumer() { }
override EVP_Hash_Initializer getInitCall() {
// This variant of digest does not use an init
// and even if it were used, the init would be ignored/undefined
none()
}
override Expr getOutputArg() { result = this.(Call).getArgument(5) }
override Expr getInputArg() { result = this.(Call).getArgument(3) }
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result = this.getOutputNode() }
override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() }
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
// The operation is a direct algorithm consumer
// NOTE: the operation itself is already modeld as a value consumer, so we can
// simply return 'this', see modeled hash algorithm consuers for EVP_Q_Digest
this = result
}
}
class EVP_Digest_Operation extends EVP_Hash_Operation {
EVP_Digest_Operation() {
this.(Call).getTarget().getName() = "EVP_Digest" and
isPossibleOpenSSLFunction(this.(Call).getTarget())
}
// There is no context argument for this function
override Expr getContextArg() { none() }
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
AlgGetterToAlgConsumerFlow::flow(result.(OpenSSLAlgorithmValueConsumer).getResultNode(),
DataFlow::exprNode(this.(Call).getArgument(4)))
}
override EVP_Hash_Initializer getInitCall() {
// This variant of digest does not use an init
// and even if it were used, the init would be ignored/undefined
none()
}
override Expr getOutputArg() { result = this.(Call).getArgument(2) }
override Expr getInputArg() { result = this.(Call).getArgument(0) }
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result = this.getOutputNode() }
override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() }
}
// // override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
// // AlgGetterToAlgConsumerFlow::flow(result.(OpenSSLAlgorithmValueConsumer).getResultNode(),
// // DataFlow::exprNode(this.getInitCall().getAlgorithmArg()))
// // }
// // ***** TODO *** complete modelinlg for hash operations, but have consideration for terminal and non-terminal (non intermedaite) steps
// // see the JCA. May need to update the cipher operations similarly
// // ALSO SEE cipher for how we currently model initialization of the algorithm through an init call
// class EVP_DigestUpdate_Operation extends EVP_Hash_Operation {
// EVP_DigestUpdate_Operation() {
// this.(Call).getTarget().getName() = "EVP_DigestUpdate" and
// isPossibleOpenSSLFunction(this.(Call).getTarget())
// }
// override Crypto::AlgorithmConsumer getAlgorithmConsumer() {
// this.getInitCall().getAlgorithmArg() = result
// }
// }
// class EVP_DigestFinal_Variants_Operation extends EVP_Hash_Operation {
// EVP_DigestFinal_Variants_Operation() {
// this.(Call).getTarget().getName() in [
// "EVP_DigestFinal", "EVP_DigestFinal_ex", "EVP_DigestFinalXOF"
// ] and
// isPossibleOpenSSLFunction(this.(Call).getTarget())
// }
// override Crypto::AlgorithmConsumer getAlgorithmConsumer() {
// this.getInitCall().getAlgorithmArg() = result
// }
// }

View File

@@ -0,0 +1,21 @@
import experimental.quantum.Language
abstract class OpenSSLOperation extends Crypto::OperationInstance instanceof Call {
abstract Expr getInputArg();
/**
* Can be an argument of a call or a return value of a function.
*/
abstract Expr getOutputArg();
DataFlow::Node getInputNode() {
// Assumed to be default to asExpr
result.asExpr() = this.getInputArg()
}
DataFlow::Node getOutputNode() {
if exists(Call c | c.getAnArgument() = this)
then result.asDefiningArgument() = this
else result.asExpr() = this
}
}

View File

@@ -0,0 +1,3 @@
import OpenSSLOperationBase
import EVPCipherOperation
import EVPHashOperation

View File

@@ -0,0 +1,18 @@
import cpp
private import experimental.quantum.Language
private import LibraryDetector
private import semmle.code.cpp.dataflow.new.DataFlow
class OpenSSLRandomNumberGeneratorInstance extends Crypto::RandomNumberGenerationInstance instanceof Call
{
OpenSSLRandomNumberGeneratorInstance() {
this.(Call).getTarget().getName() in ["RAND_bytes", "RAND_pseudo_bytes"] and
isPossibleOpenSSLFunction(this.(Call).getTarget())
}
override Crypto::DataFlowNode getOutputNode() {
result.asDefiningArgument() = this.(Call).getArgument(0)
}
override string getGeneratorName() { result = this.(Call).getTarget().getName() }
}

View File

@@ -8,6 +8,7 @@ upgrades: upgrades
dependencies:
codeql/dataflow: ${workspace}
codeql/mad: ${workspace}
codeql/quantum: ${workspace}
codeql/rangeanalysis: ${workspace}
codeql/ssa: ${workspace}
codeql/typeflow: ${workspace}

View File

@@ -0,0 +1,23 @@
/**
* @name Print CBOM Graph
* @description Outputs a graph representation of the cryptographic bill of materials.
* This query only supports DGML output, as CodeQL DOT output omits properties.
* @kind graph
* @id cpp/print-cbom-graph
* @tags quantum
* experimental
*/
import experimental.quantum.Language
query predicate nodes(Crypto::NodeBase node, string key, string value) {
Crypto::nodes_graph_impl(node, key, value)
}
query predicate edges(Crypto::NodeBase source, Crypto::NodeBase target, string key, string value) {
Crypto::edges_graph_impl(source, target, key, value)
}
query predicate graphProperties(string key, string value) {
key = "semmle.graphKind" and value = "graph"
}

View File

@@ -1,15 +0,0 @@
#!/usr/bin/python3
import sys
import os.path
import subprocess
# Add Model as Data script directory to sys.path.
gitroot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip()
madpath = os.path.join(gitroot, "misc/scripts/models-as-data/")
sys.path.append(madpath)
import generate_flow_model as model
language = "cpp"
model.Generator.make(language).run()

View File

@@ -1,15 +0,0 @@
#!/usr/bin/python3
import sys
import os.path
import subprocess
# Add Model as Data script directory to sys.path.
gitroot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip()
madpath = os.path.join(gitroot, "misc/scripts/models-as-data/")
sys.path.append(madpath)
import generate_flow_model as model
language = "csharp"
model.Generator.make(language).run()

View File

@@ -1,3 +1,3 @@
semmle-extractor-options: /nostdlib /noconfig
semmle-extractor-options: --load-sources-from-project:${testdir}/../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
semmle-extractor-options: ${testdir}/../../resources/stubs/System.Web.cs
semmle-extractor-options: ${testdir}/../../resources/stubs/System.Web.cs

View File

@@ -1,3 +1,4 @@
semmle-extractor-options: /nostdlib /noconfig
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../resources/stubs/_frameworks/Microsoft.AspNetCore.App/Microsoft.AspNetCore.App.csproj
semmle-extractor-options: ${testdir}/../../../../resources/stubs/System.Web.cs
semmle-extractor-options: ${testdir}/../../../../resources/stubs/System.Web.Http.cs

View File

@@ -1,3 +1,3 @@
semmle-extractor-options: /nostdlib /noconfig
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/Newtonsoft.Json/13.0.3/Newtonsoft.Json.csproj
semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs

View File

@@ -1,4 +1,3 @@
semmle-extractor-options: /nostdlib /noconfig
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
semmle-extractor-options: --load-sources-from-project:../../../resources/stubs/_frameworks/Microsoft.AspNetCore.App/Microsoft.AspNetCore.App.csproj
semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs

View File

@@ -1,3 +1,3 @@
semmle-extractor-options: /nostdlib /noconfig
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
semmle-extractor-options: ${testdir}/../../../../../resources/stubs/System.Web.cs
semmle-extractor-options: ${testdir}/../../../../../resources/stubs/System.Web.cs

View File

@@ -1,4 +1,3 @@
semmle-extractor-options: /nostdlib /noconfig
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../resources/stubs/_frameworks/Microsoft.AspNetCore.App/Microsoft.AspNetCore.App.csproj
semmle-extractor-options: ${testdir}/../../../../resources/stubs/System.Web.cs

View File

@@ -1 +1,2 @@
semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs /r:System.Collections.Specialized.dll /r:System.Xml.ReaderWriter.dll /r:System.Private.Xml.dll /r:System.Runtime.Extensions.dll
semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs
semmle-extractor-options: /r:System.Collections.Specialized.dll /r:System.Xml.ReaderWriter.dll /r:System.Private.Xml.dll /r:System.Runtime.Extensions.dll

View File

@@ -1,3 +1,3 @@
semmle-extractor-options: /nostdlib /noconfig
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
semmle-extractor-options: ${testdir}/../../../../resources/stubs/System.Web.cs
semmle-extractor-options: ${testdir}/../../../../resources/stubs/System.Web.cs

View File

@@ -49,6 +49,7 @@ namespace System.Net.Http
{
public StringContent(string s) { }
}
}
namespace System.Net.Mail

View File

@@ -0,0 +1,14 @@
namespace System.Web.Http
{
public class ApiController
{
public Microsoft.AspNetCore.Http.HttpContext Context => null;
public virtual Microsoft.AspNetCore.Mvc.RedirectResult Redirect(Uri location) => null;
public virtual Microsoft.AspNetCore.Mvc.RedirectResult Redirect(string location) => null;
public virtual ResponseMessageResult ResponseMessage(System.Net.Http.HttpResponseMessage response) => null;
public virtual Microsoft.AspNetCore.Mvc.RedirectToRouteResult RedirectToRoute(string routeName, object routeValues) => null;
public Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; set; }
}
public class ResponseMessageResult { }
}

View File

@@ -19,6 +19,15 @@ namespace System.Web
public class HttpResponseBase
{
public void Write(object obj) { }
public virtual void AppendHeader(string name, string value) { }
public virtual void Redirect(string url) { }
public virtual void RedirectPermanent(string url) { }
public virtual int StatusCode { get; set; }
public virtual void AddHeader(string name, string value) { }
public virtual void End() { }
public virtual string RedirectLocation { get; set; }
public virtual NameValueCollection Headers => null;
}
public class HttpContextBase
@@ -51,26 +60,40 @@ namespace System.Web
}
}
namespace System.Web.Http
{
public class ApiController
{
}
}
namespace System.Web.Mvc
{
public class Controller
{
public ViewResult View() => null;
public HttpRequestBase Request => null;
public HttpResponseBase Response => null;
protected internal virtual RedirectResult RedirectPermanent(string url) => null;
protected internal RedirectToRouteResult RedirectToRoute(string routeName) => null;
public UrlHelper Url { get; set; }
protected internal virtual RedirectResult Redirect(string url) => null;
}
public class MvcHtmlString : HtmlString
{
public MvcHtmlString(string s) : base(s) { }
}
public class RoutePrefixAttribute : Attribute
{
public virtual string Prefix { get; private set; }
public RoutePrefixAttribute(string prefix) { }
}
public sealed class RouteAttribute : Attribute
{
public RouteAttribute(string template) { }
}
public class RedirectToRouteResult : ActionResult { }
}
namespace System.Web.UI
{
public class Control
@@ -81,6 +104,7 @@ namespace System.Web.UI
{
public System.Security.Principal.IPrincipal User { get; }
public System.Web.HttpRequest Request { get; }
public HttpResponse Response => null;
}
interface IPostBackDataHandler
@@ -153,6 +177,7 @@ namespace System.Web
public UnvalidatedRequestValues Unvalidated { get; }
public string RawUrl { get; set; }
public HttpCookieCollection Cookies => null;
public bool IsAuthenticated { get; set; }
}
public class HttpRequestWrapper : System.Web.HttpRequestBase
@@ -169,6 +194,13 @@ namespace System.Web
public void AddHeader(string name, string value) { }
public void Redirect(string url) { }
public void AppendHeader(string name, string value) { }
public void End() { }
public string RedirectLocation { get; set; }
public int StatusCode { get; set; }
public void RedirectPermanent(string url) { }
public virtual NameValueCollection Headers { get; set; }
}
public class HttpContext : IServiceProvider
@@ -177,6 +209,7 @@ namespace System.Web
public HttpResponse Response => null;
public SessionState.HttpSessionState Session => null;
public HttpServerUtility Server => null;
public static HttpContext Current => null;
}
public class HttpCookie
@@ -301,6 +334,15 @@ namespace System.Web.Mvc
public UrlHelper(Routing.RequestContext requestContext) { }
public virtual bool IsLocalUrl(string url) => false;
}
public class RedirectResult : ActionResult
{
public bool Permanent { get; set; }
public string Url => null;
public RedirectResult(string url) : this(url, permanent: false) { }
public RedirectResult(string url, bool permanent) { }
}
}
namespace System.Web.Routing
@@ -390,7 +432,7 @@ namespace System.Web.Script.Serialization
public JavaScriptSerializer() => throw null;
public JavaScriptSerializer(System.Web.Script.Serialization.JavaScriptTypeResolver resolver) => throw null;
public object DeserializeObject(string input) => throw null;
public T Deserialize<T> (string input) => throw null;
public T Deserialize<T>(string input) => throw null;
public object Deserialize(string input, Type targetType) => throw null;
}

View File

@@ -1582,18 +1582,10 @@ func isAlias(tp types.Type) bool {
return ok
}
// If the given type is a type alias, this function resolves it to its underlying type.
func resolveTypeAlias(tp types.Type) types.Type {
if isAlias(tp) {
return types.Unalias(tp) // tp.Underlying()
}
return tp
}
// extractType extracts type information for `tp` and returns its associated label;
// types are only extracted once, so the second time `extractType` is invoked it simply returns the label
func extractType(tw *trap.Writer, tp types.Type) trap.Label {
tp = resolveTypeAlias(tp)
tp = types.Unalias(tp)
lbl, exists := getTypeLabel(tw, tp)
if !exists {
var kind int
@@ -1771,7 +1763,7 @@ func extractType(tw *trap.Writer, tp types.Type) trap.Label {
// is constructed from their globally unique ID. This prevents cyclic type keys
// since type recursion in Go always goes through defined types.
func getTypeLabel(tw *trap.Writer, tp types.Type) (trap.Label, bool) {
tp = resolveTypeAlias(tp)
tp = types.Unalias(tp)
lbl, exists := tw.Labeler.TypeLabels[tp]
if !exists {
switch tp := tp.(type) {

View File

@@ -10,7 +10,7 @@ toolchain go1.24.0
// bazel mod tidy
require (
golang.org/x/mod v0.24.0
golang.org/x/tools v0.32.0
golang.org/x/tools v0.33.0
)
require golang.org/x/sync v0.13.0 // indirect
require golang.org/x/sync v0.14.0 // indirect

View File

@@ -2,7 +2,7 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=

View File

@@ -169,11 +169,12 @@ func (l *Labeler) ScopedObjectID(object types.Object, getTypeLabel func() Label)
// findMethodWithGivenReceiver finds a method with `object` as its receiver, if one exists
func findMethodWithGivenReceiver(object types.Object) *types.Func {
meth := findMethodOnTypeWithGivenReceiver(object.Type(), object)
unaliasedType := types.Unalias(object.Type())
meth := findMethodOnTypeWithGivenReceiver(unaliasedType, object)
if meth != nil {
return meth
}
if pointerType, ok := object.Type().(*types.Pointer); ok {
if pointerType, ok := unaliasedType.(*types.Pointer); ok {
meth = findMethodOnTypeWithGivenReceiver(pointerType.Elem(), object)
}
return meth

View File

@@ -253,6 +253,29 @@ ql/java/ql/src/experimental/Security/CWE/CWE-665/InsecureRmiJmxEnvironmentConfig
ql/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql
ql/java/ql/src/experimental/Security/CWE/CWE-759/HashWithoutSalt.ql
ql/java/ql/src/experimental/Security/CWE/CWE-939/IncorrectURLVerification.ql
ql/java/ql/src/experimental/quantum/Analysis/InsecureNonceSource.ql
ql/java/ql/src/experimental/quantum/Analysis/KnownWeakKDFIterationCount.ql
ql/java/ql/src/experimental/quantum/Analysis/ReusedNonce.ql
ql/java/ql/src/experimental/quantum/Analysis/UnknownKDFIterationCount.ql
ql/java/ql/src/experimental/quantum/Examples/BrokenCrypto.ql
ql/java/ql/src/experimental/quantum/Examples/TestAESGCMNonce.ql
ql/java/ql/src/experimental/quantum/Examples/TestCipher.ql
ql/java/ql/src/experimental/quantum/Examples/TestHash.ql
ql/java/ql/src/experimental/quantum/InventorySlices/KnownAsymmetricAlgorithm.ql
ql/java/ql/src/experimental/quantum/InventorySlices/KnownAsymmetricCipherAlgorithm.ql
ql/java/ql/src/experimental/quantum/InventorySlices/KnownAsymmetricOperationAlgorithm.ql
ql/java/ql/src/experimental/quantum/InventorySlices/KnownCipherAlgorithm.ql
ql/java/ql/src/experimental/quantum/InventorySlices/KnownEllipticCurveAlgorithm.ql
ql/java/ql/src/experimental/quantum/InventorySlices/KnownHashingAlgorithm.ql
ql/java/ql/src/experimental/quantum/InventorySlices/KnownHashingOperation.ql
ql/java/ql/src/experimental/quantum/InventorySlices/KnownHashingOperationAlgorithm.ql
ql/java/ql/src/experimental/quantum/InventorySlices/KnownKeyDerivationAlgorithm.ql
ql/java/ql/src/experimental/quantum/InventorySlices/KnownKeyDerivationOperation.ql
ql/java/ql/src/experimental/quantum/InventorySlices/KnownKeyDerivationOperationAlgorithm.ql
ql/java/ql/src/experimental/quantum/InventorySlices/KnownSymmetricCipherAlgorithm.ql
ql/java/ql/src/experimental/quantum/InventorySlices/LikelyCryptoAPIFunction.ql
ql/java/ql/src/experimental/quantum/InventorySlices/UnknownOperationAlgorithm.ql
ql/java/ql/src/experimental/quantum/PrintCBOMGraph.ql
ql/java/ql/src/external/DuplicateAnonymous.ql
ql/java/ql/src/external/DuplicateBlock.ql
ql/java/ql/src/external/DuplicateMethod.ql

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,218 @@
private import java as Language
private import semmle.code.java.security.InsecureRandomnessQuery
private import semmle.code.java.security.RandomQuery
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.FlowSources
private import codeql.quantum.experimental.Model
private class UnknownLocation extends Language::Location {
UnknownLocation() { this.getFile().getAbsolutePath() = "" }
}
/**
* A dummy location which is used when something doesn't have a location in
* the source code but needs to have a `Location` associated with it. There
* may be several distinct kinds of unknown locations. For example: one for
* expressions, one for statements and one for other program elements.
*/
private class UnknownDefaultLocation extends UnknownLocation {
UnknownDefaultLocation() { locations_default(this, _, 0, 0, 0, 0) }
}
module CryptoInput implements InputSig<Language::Location> {
class DataFlowNode = DataFlow::Node;
class LocatableElement = Language::Element;
class UnknownLocation = UnknownDefaultLocation;
string locationToFileBaseNameAndLineNumberString(Location location) {
result = location.getFile().getBaseName() + ":" + location.getStartLine()
}
LocatableElement dfn_to_element(DataFlow::Node node) {
result = node.asExpr() or
result = node.asParameter()
}
predicate artifactOutputFlowsToGenericInput(
DataFlow::Node artifactOutput, DataFlow::Node otherInput
) {
ArtifactFlow::flow(artifactOutput, otherInput)
}
}
// Instantiate the `CryptographyBase` module
module Crypto = CryptographyBase<Language::Location, CryptoInput>;
// Definitions of various generic sources
final class DefaultFlowSource = SourceNode;
final class DefaultRemoteFlowSource = RemoteFlowSource;
private class GenericUnreferencedParameterSource extends Crypto::GenericUnreferencedParameterSource {
GenericUnreferencedParameterSource() {
exists(Parameter p | this = p and not exists(p.getAnArgument()))
}
override predicate flowsTo(Crypto::FlowAwareElement other) {
GenericDataSourceFlow::flow(this.getOutputNode(), other.getInputNode())
}
override DataFlow::Node getOutputNode() { result.asParameter() = this }
override string getAdditionalDescription() { result = this.toString() }
}
private class GenericLocalDataSource extends Crypto::GenericLocalDataSource {
GenericLocalDataSource() {
any(DefaultFlowSource src | not src instanceof DefaultRemoteFlowSource).asExpr() = this
}
override DataFlow::Node getOutputNode() { result.asExpr() = this }
override predicate flowsTo(Crypto::FlowAwareElement other) {
GenericDataSourceFlow::flow(this.getOutputNode(), other.getInputNode())
}
override string getAdditionalDescription() { result = this.toString() }
}
private class GenericRemoteDataSource extends Crypto::GenericRemoteDataSource {
GenericRemoteDataSource() { any(DefaultRemoteFlowSource src).asExpr() = this }
override DataFlow::Node getOutputNode() { result.asExpr() = this }
override predicate flowsTo(Crypto::FlowAwareElement other) {
GenericDataSourceFlow::flow(this.getOutputNode(), other.getInputNode())
}
override string getAdditionalDescription() { result = this.toString() }
}
private class ConstantDataSource extends Crypto::GenericConstantSourceInstance instanceof Literal {
ConstantDataSource() {
// TODO: this is an API specific workaround for JCA, 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"
}
override DataFlow::Node getOutputNode() { result.asExpr() = this }
override predicate flowsTo(Crypto::FlowAwareElement other) {
// TODO: separate config to avoid blowing up data-flow analysis
GenericDataSourceFlow::flow(this.getOutputNode(), other.getInputNode())
}
override string getAdditionalDescription() { result = this.toString() }
}
/**
* An instance of random number generation, modelled as the expression
* tied to an output node (i.e., the result of the source of randomness)
*/
abstract class RandomnessInstance extends Crypto::RandomNumberGenerationInstance {
override DataFlow::Node getOutputNode() { result.asExpr() = this }
}
class SecureRandomnessInstance extends RandomnessInstance {
RandomDataSource source;
SecureRandomnessInstance() {
this = source.getOutput() and
source.getSourceOfRandomness() instanceof SecureRandomNumberGenerator
}
override string getGeneratorName() { result = source.getSourceOfRandomness().getQualifiedName() }
}
class InsecureRandomnessInstance extends RandomnessInstance {
RandomDataSource source;
InsecureRandomnessInstance() {
any(InsecureRandomnessSource src).asExpr() = this and source.getOutput() = this
}
override string getGeneratorName() { result = source.getSourceOfRandomness().getQualifiedName() }
}
/**
* An additional flow step in generic data-flow configurations.
* Where a step is an edge between nodes `n1` and `n2`,
* `this` = `n1` and `getOutput()` = `n2`.
*
* FOR INTERNAL MODELING USE ONLY.
*/
abstract class AdditionalFlowInputStep extends DataFlow::Node {
abstract DataFlow::Node getOutput();
final DataFlow::Node getInput() { result = this }
}
/**
* Generic data source to node input configuration
*/
module GenericDataSourceFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source = any(Crypto::GenericSourceInstance i).getOutputNode()
}
predicate isSink(DataFlow::Node sink) {
sink = any(Crypto::FlowAwareElement other).getInputNode()
}
predicate isBarrierOut(DataFlow::Node node) {
node = any(Crypto::FlowAwareElement element).getInputNode()
}
predicate isBarrierIn(DataFlow::Node node) {
node = any(Crypto::FlowAwareElement element).getOutputNode()
}
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
node1.(AdditionalFlowInputStep).getOutput() = node2
or
exists(MethodCall m |
m.getMethod().hasQualifiedName("java.lang", "String", "getBytes") and
node1.asExpr() = m.getQualifier() and
node2.asExpr() = m
)
}
}
module ArtifactFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source = any(Crypto::ArtifactInstance artifact).getOutputNode()
}
predicate isSink(DataFlow::Node sink) {
sink = any(Crypto::FlowAwareElement other).getInputNode()
}
predicate isBarrierOut(DataFlow::Node node) {
node = any(Crypto::FlowAwareElement element).getInputNode()
}
predicate isBarrierIn(DataFlow::Node node) {
node = any(Crypto::FlowAwareElement element).getOutputNode()
}
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
node1.(AdditionalFlowInputStep).getOutput() = node2
or
exists(MethodCall m |
m.getMethod().hasQualifiedName("java.lang", "String", "getBytes") and
node1.asExpr() = m.getQualifier() and
node2.asExpr() = m
)
}
}
module GenericDataSourceFlow = TaintTracking::Global<GenericDataSourceFlowConfig>;
module ArtifactFlow = DataFlow::Global<ArtifactFlowConfig>;
// Import library-specific modeling
import JCA

View File

@@ -8,6 +8,7 @@ upgrades: upgrades
dependencies:
codeql/dataflow: ${workspace}
codeql/mad: ${workspace}
codeql/quantum: ${workspace}
codeql/rangeanalysis: ${workspace}
codeql/regex: ${workspace}
codeql/threat-models: ${workspace}

View File

@@ -0,0 +1,70 @@
import java
import semmle.code.java.dataflow.DataFlow
import experimental.quantum.Language
/**
* Flow from any function that appears to return a value
* to an artifact node.
* NOTE: TODO: need to handle call by refernece for now. Need to re-evaluate (see notes below)
* Such functions may be 'wrappers' for some derived value.
*/
private module WrapperConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
exists(Call c |
c = source.asExpr()
// not handling references yet, I think we want to flat say references are only ok
// if I know the source, otherwise, it has to be through an additional flow step, which
// we filter as a source, i.e., references are only allowed as sources only,
// no inferrece? Not sure if that would work
//or
// source.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr() = c.getAnArgument()
) and
// Filter out sources that are known additional flow steps, as these are likely not the
// kind of wrapper source we are looking for.
not exists(AdditionalFlowInputStep s | s.getOutput() = source)
}
// Flow through additional flow steps
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
node1.(AdditionalFlowInputStep).getOutput() = node2
}
predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(Crypto::ArtifactNode i).asElement() }
}
module WrapperFlow = DataFlow::Global<WrapperConfig>;
/**
* Using a set approach to determine if reuse of an artifact exists.
* This predicate produces a set of 'wrappers' that flow to the artifact node.
* This set can be compared with the set to another artifact node to determine if they are the same.
*/
private DataFlow::Node getWrapperSet(Crypto::NonceArtifactNode a) {
WrapperFlow::flow(result, DataFlow::exprNode(a.asElement()))
or
result.asExpr() = a.getSourceElement()
}
/**
* Two different artifact nodes are considered reuse if any of the following conditions are met:
* 1. The source for artifact `a` and artifact `b` are the same and the source is a literal.
* 2. The source for artifact `a` and artifact `b` are not the same and the source is a literal of the same value.
* 3. For all 'wrappers' that return the source of artifact `a`, and that wrapper also exists for artifact `b`.
* 4. For all 'wrappers' that return the source of artifact `b`, and that wrapper also exists for artifact `a`.
*/
predicate isArtifactReuse(Crypto::ArtifactNode a, Crypto::ArtifactNode b) {
a != b and
(
a.getSourceElement() = b.getSourceElement() and a.getSourceElement() instanceof Literal
or
a.getSourceElement().(Literal).getValue() = b.getSourceElement().(Literal).getValue()
or
forex(DataFlow::Node e | e = getWrapperSet(a) |
exists(DataFlow::Node e2 | e2 = getWrapperSet(b) | e = e2)
)
or
forex(DataFlow::Node e | e = getWrapperSet(b) |
exists(DataFlow::Node e2 | e2 = getWrapperSet(a) | e = e2)
)
)
}

View File

@@ -0,0 +1,22 @@
/**
* @name Insecure nonce at a cipher operation
* @id java/quantum/insecure-nonce
* @description A nonce is generated from a source that is not secure. This can lead to
* vulnerabilities such as replay attacks or key recovery.
* @kind problem
* @problem.severity error
* @precision high
* @tags quantum
* experimental
*/
import experimental.quantum.Language
predicate isInsecureNonceSource(Crypto::NonceArtifactNode n, Crypto::NodeBase src) {
src = n.getSourceNode() and
not src.asElement() instanceof SecureRandomnessInstance
}
from Crypto::KeyOperationNode op, Crypto::NodeBase src
where isInsecureNonceSource(op.getANonce(), src)
select op, "Operation uses insecure nonce source $@", src, src.toString()

View File

@@ -0,0 +1,20 @@
/**
* @name Weak known key derivation function iteration count
* @description Detects key derivation operations with a known weak iteration count.
* @id java/quantum/weak-kdf-iteration-count
* @kind problem
* @problem.severity error
* @precision high
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
from Crypto::KeyDerivationOperationNode op, Literal l
where
op.getIterationCount().asElement() = l and
l.getValue().toInt() < 100000
select op, "Key derivation operation configures iteration count below 100k: $@", l,
l.getValue().toString()

View File

@@ -0,0 +1,17 @@
/**
* @name Reuse of cryptographic nonce
* @description Reuse of nonce in cryptographic operations can lead to vulnerabilities.
* @id java/quantum/reused-nonce
* @kind problem
* @problem.severity error
* @precision medium
* @tags quantum
* experimental
*/
import java
import ArtifactReuse
from Crypto::NonceArtifactNode nonce1, Crypto::NonceArtifactNode nonce2
where isArtifactReuse(nonce1, nonce2)
select nonce1, "Reuse with nonce $@", nonce2, nonce2.toString()

View File

@@ -0,0 +1,24 @@
/**
* @name Unknown key derivation function iteration count
* @description Detects key derivation operations with an unknown iteration count.
* @id java/quantum/unknown-kdf-iteration-count
* @kind problem
* @precision medium
* @severity warning
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
from Crypto::KeyDerivationOperationNode op, Element e, string msg
where
e = op.getIterationCount().asElement() and
not e instanceof Literal and
msg = "Key derivation operation with unknown iteration: $@"
or
not exists(op.getIterationCount()) and
e = op.asElement() and
msg = "Key derivation operation with no iteration configuration."
select op, msg, e, e.toString()

View File

@@ -0,0 +1,75 @@
/**
* @name Use of a broken or risky cryptographic algorithm
* @description Using broken or weak cryptographic algorithms can allow an attacker to compromise security.
* @kind problem
* @problem.severity warning
* @security-severity 7.5
* @precision high
* @id java/weak-cryptographic-algorithm-new-model
* @tags security
* external/cwe/cwe-327
* external/cwe/cwe-328
*/
//THIS QUERY IS A REPLICA OF: https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.ql
//but uses the **NEW MODELLING**
import experimental.quantum.Language
/**
* Gets the name of an algorithm that is known to be insecure.
*/
string getAnInsecureAlgorithmName() {
result =
[
"DES", "RC2", "RC4", "RC5",
// ARCFOUR is a variant of RC4
"ARCFOUR",
// Encryption mode ECB like AES/ECB/NoPadding is vulnerable to replay and other attacks
"ECB",
// CBC mode of operation with PKCS#5 or PKCS#7 padding is vulnerable to padding oracle attacks
"AES/CBC/PKCS[57]Padding"
]
}
private string rankedInsecureAlgorithm(int i) {
result = rank[i](string s | s = getAnInsecureAlgorithmName())
}
private string insecureAlgorithmString(int i) {
i = 1 and result = rankedInsecureAlgorithm(i)
or
result = rankedInsecureAlgorithm(i) + "|" + insecureAlgorithmString(i - 1)
}
/**
* Gets the regular expression used for matching strings that look like they
* contain an algorithm that is known to be insecure.
*/
string getInsecureAlgorithmRegex() {
result = algorithmRegex(insecureAlgorithmString(max(int i | exists(rankedInsecureAlgorithm(i)))))
}
bindingset[algorithmString]
private string algorithmRegex(string algorithmString) {
// Algorithms usually appear in names surrounded by characters that are not
// alphabetical characters in the same case. This handles the upper and lower
// case cases.
result =
"((^|.*[^A-Z])(" + algorithmString + ")([^A-Z].*|$))" +
// or...
"|" +
// For lowercase, we want to be careful to avoid being confused by camelCase
// hence we require two preceding uppercase letters to be sure of a case switch,
// or a preceding non-alphabetic character
"((^|.*[A-Z]{2}|.*[^a-zA-Z])(" + algorithmString.toLowerCase() + ")([^a-z].*|$))"
}
from Crypto::AlgorithmNode alg
where
alg.getAlgorithmName().regexpMatch(getInsecureAlgorithmRegex()) and
// Exclude RSA/ECB/.* ciphers.
not alg.getAlgorithmName().regexpMatch("RSA/ECB.*") and
// Exclude German and French sentences.
not alg.getAlgorithmName().regexpMatch(".*\\p{IsLowercase} des \\p{IsLetter}.*")
select alg, "Cryptographic algorithm $@ is weak and should not be used.", alg,
alg.getAlgorithmName()

View File

@@ -0,0 +1,16 @@
/**
* @name "PQC Test"
*/
import experimental.quantum.Language
class AESGCMAlgorithmNode extends Crypto::KeyOperationAlgorithmNode {
AESGCMAlgorithmNode() {
this.getAlgorithmType() = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::AES()) and
this.getModeOfOperation().getModeType() = Crypto::GCM()
}
}
from Crypto::KeyOperationNode op, Crypto::NonceArtifactNode nonce
where op.getAKnownAlgorithm() instanceof AESGCMAlgorithmNode and nonce = op.getANonce()
select op, nonce.getSourceNode()

View File

@@ -0,0 +1,18 @@
/**
* @name "Key operation slice table demo query"
*/
import experimental.quantum.Language
from
Crypto::KeyOperationNode op, Crypto::KeyOperationAlgorithmNode a,
Crypto::ModeOfOperationAlgorithmNode m, Crypto::PaddingAlgorithmNode p,
Crypto::NonceArtifactNode nonce, Crypto::KeyArtifactNode k
where
a = op.getAKnownAlgorithm() and
m = a.getModeOfOperation() and
p = a.getPaddingAlgorithm() and
nonce = op.getANonce() and
k = op.getAKey()
select op, op.getKeyOperationSubtype(), a, a.getRawAlgorithmName(), m, m.getRawAlgorithmName(), p,
p.getRawAlgorithmName(), nonce, k

View File

@@ -0,0 +1,9 @@
/**
* @name "Hash operation slice table demo query"
*/
import experimental.quantum.Language
from Crypto::HashOperationNode op, Crypto::HashAlgorithmNode alg
where alg = op.getAKnownAlgorithm()
select op, op.getDigest(), alg, alg.getRawAlgorithmName()

View File

@@ -0,0 +1,14 @@
/**
* @name Operations using known asymmetric cipher algorithms (slice)
* @description Outputs operations where the algorithm used is a known asymmetric cipher algorithm.
* @id java/quantum/slices/known-asymmetric-algorithm
* @kind table
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
from Crypto::AsymmetricAlgorithmNode a
select a, a.asAlgorithmNode().getAlgorithmName()

View File

@@ -0,0 +1,15 @@
/**
* @name Known asymmetric cipher algorithms (slice)
* @description Outputs known asymmetric cipher algorithms.
* @id java/quantum/slices/known-asymmetric-cipher-algorithm
* @kind table
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
from Crypto::KeyOperationAlgorithmNode a
where a.getAlgorithmType() instanceof Crypto::KeyOpAlg::AsymmetricCipherAlgorithm
select a, a.getAlgorithmName()

View File

@@ -0,0 +1,15 @@
/**
* @name Operations using known asymmetric algorithms (slice)
* @description Outputs operations where the algorithm used is a known asymmetric algorithm.
* @id java/quantum/slices/known-asymmetric-operation-algorithm
* @kind table
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
from Crypto::OperationNode op, Crypto::AsymmetricAlgorithmNode a
where a = op.getAKnownAlgorithm()
select op, a.asAlgorithmNode().getAlgorithmName()

View File

@@ -0,0 +1,18 @@
/**
* @name Known cipher algorithms (slice)
* @description Outputs known cipher algorithms.
* @id java/quantum/slices/known-cipher-algorithm
* @kind table
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
// TODO: should there be a cipher algorithm node?
from Crypto::KeyOperationAlgorithmNode a
where
a.getAlgorithmType() instanceof Crypto::KeyOpAlg::AsymmetricCipherAlgorithm or
a.getAlgorithmType() instanceof Crypto::KeyOpAlg::SymmetricCipherAlgorithm
select a, a.getAlgorithmName()

View File

@@ -0,0 +1,14 @@
/**
* @name Known elliptic curve algorithms (slice)
* @description Outputs known elliptic curve algorithms.
* @id java/quantum/slices/known-elliptic-curve-algorithm
* @kind table
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
from Crypto::EllipticCurveNode a
select a, a.getAlgorithmName()

View File

@@ -0,0 +1,14 @@
/**
* @name Known hashing algorithms (slice)
* @description Outputs known hashing algorithms.
* @id java/quantum/slices/known-hashing-algorithm
* @kind table
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
from Crypto::HashAlgorithmNode a
select a, a.getAlgorithmName()

View File

@@ -0,0 +1,14 @@
/**
* @name Known hashing operations (slice)
* @description Outputs known hashing operations.
* @id java/quantum/slices/known-hashing-operation
* @kind table
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
from Crypto::HashOperationNode op
select op

View File

@@ -0,0 +1,15 @@
/**
* @name Operations using known hashing algorithms (slice)
* @description Outputs operations where the algorithm used is a known hashing algorithm.
* @id java/quantum/slices/operation-with-known-hashing-algorithm
* @kind table
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
from Crypto::OperationNode op, Crypto::HashAlgorithmNode a
where a = op.getAKnownAlgorithm()
select op, a.getAlgorithmName()

View File

@@ -0,0 +1,14 @@
/**
* @name Known key derivation algorithms (slice)
* @description Outputs known key derivation algorithms.
* @id java/quantum/slices/known-key-derivation-algorithm
* @kind table
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
from Crypto::KeyDerivationAlgorithmNode alg
select alg, alg.getAlgorithmName()

View File

@@ -0,0 +1,14 @@
/**
* @name Known key derivation operations (slice)
* @description Outputs known key derivation operations.
* @id java/quantum/slices/known-key-derivation-operation
* @kind table
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
from Crypto::KeyDerivationOperationNode op
select op

View File

@@ -0,0 +1,15 @@
/**
* @name Operations using known key derivation algorithms (slice)
* @description Outputs operations where the algorithm used is a known key derivation algorithm.
* @id java/quantum/slices/operation-with-known-kdf-algorithm
* @kind table
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
from Crypto::OperationNode op, Crypto::KeyDerivationAlgorithmNode a
where a = op.getAKnownAlgorithm()
select op, a.getAlgorithmName()

View File

@@ -0,0 +1,15 @@
/**
* @name Known symmetric cipher algorithms (slice)
* @description Outputs known symmetric cipher algorithms.
* @id java/quantum/slices/known-symmetric-cipher-algorithm
* @kind table
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
from Crypto::KeyOperationAlgorithmNode a
where a.getAlgorithmType() instanceof Crypto::KeyOpAlg::SymmetricCipherAlgorithm
select a, a.getAlgorithmName()

View File

@@ -0,0 +1,20 @@
/**
* @name Likely crypto API function
* @description Outputs functions that take in crypto configuration parameters but calls are not detected in source.
* @id java/quantum/slices/likely-crypto-api-function
* @kind problem
* @severity info
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
from Callable f, Parameter p, Crypto::OperationNode op
where
op.asElement().(Expr).getEnclosingCallable() = f and
op.getAnAlgorithmOrGenericSource().asElement() = p
select f,
"Likely crypto API function: Operation $@ configured by parameter $@ with no known configuring call",
op, op.toString(), p, p.toString()

View File

@@ -0,0 +1,26 @@
/**
* @name Operations with unknown algorithm
* @description Outputs operations where the algorithm applied is unknown
* @id java/quantum/slices/operation-with-unknown-algorithm
* @kind problem
* @severity info
* @tags quantum
* experimental
*/
import java
import experimental.quantum.Language
//TODO: can we have an unknown node concept?
from Crypto::OperationNode op, Element e, string msg
where
not exists(op.getAnAlgorithmOrGenericSource()) and
e = op.asElement() and
msg = "Operation with unconfigured algorithm (no known sources)."
or
exists(Crypto::GenericSourceNode n |
n = op.getAnAlgorithmOrGenericSource() and
e = n.asElement()
) and
msg = "Operation with unknown algorithm source: $@"
select op, msg, e, e.toString()

View File

@@ -0,0 +1,23 @@
/**
* @name Print CBOM Graph
* @description Outputs a graph representation of the cryptographic bill of materials.
* This query only supports DGML output, as CodeQL DOT output omits properties.
* @kind graph
* @id java/print-cbom-graph
* @tags quantum
* experimental
*/
import experimental.quantum.Language
query predicate nodes(Crypto::NodeBase node, string key, string value) {
Crypto::nodes_graph_impl(node, key, value)
}
query predicate edges(Crypto::NodeBase source, Crypto::NodeBase target, string key, string value) {
Crypto::edges_graph_impl(source, target, key, value)
}
query predicate graphProperties(string key, string value) {
key = "semmle.graphKind" and value = "graph"
}

View File

@@ -1,15 +0,0 @@
#!/usr/bin/python3
import sys
import os.path
import subprocess
# Add Model as Data script directory to sys.path.
gitroot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip()
madpath = os.path.join(gitroot, "misc/scripts/models-as-data/")
sys.path.append(madpath)
import generate_flow_model as model
language = "java"
model.Generator.make(language).run()

View File

@@ -35,8 +35,8 @@ def regenerateModel(lgtmSlug, extractedDb):
sys.exit(1)
modelFile = lgtmSlugToModelFile[lgtmSlug]
codeQlRoot = findGitRoot()
subprocess.check_call([codeQlRoot + "/java/ql/src/utils/modelgenerator/GenerateFlowModel.py",
"--with-summaries", "--with-sinks", "--with-neutrals",
subprocess.check_call([codeQlRoot + "/misc/scripts/models-as-data/generate_mad.py",
"--language", "java", "--with-summaries", "--with-sinks", "--with-neutrals",
extractedDb, modelFile])
print("Regenerated " + modelFile)
shutil.rmtree(tmpDir)

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Improved modeling of the [`shelljs`](https://www.npmjs.com/package/shelljs) and [`async-shelljs`](https://www.npmjs.com/package/async-shelljs) libraries by adding support for the `which`, `cmd`, `asyncExec` and `env`.

View File

@@ -0,0 +1,6 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: sourceModel
data:
- ["shelljs", "Member[env]", "environment"]

View File

@@ -264,3 +264,7 @@ module Stage {
cached
predicate backref() { optionalStep(_, _, _) }
}
predicate unsupportedCallable = Private::unsupportedCallable/1;
predicate unsupportedCallable = Private::unsupportedCallable/4;

View File

@@ -14,7 +14,8 @@ module ShellJS {
shellJSMember()
.getMember([
"exec", "cd", "cp", "touch", "chmod", "pushd", "find", "ls", "ln", "mkdir", "mv",
"rm", "cat", "head", "sort", "tail", "uniq", "grep", "sed", "to", "toEnd", "echo"
"rm", "cat", "head", "sort", "tail", "uniq", "grep", "sed", "to", "toEnd", "echo",
"which", "cmd", "asyncExec"
])
.getReturn()
}
@@ -99,7 +100,8 @@ module ShellJS {
*/
private class ShellJSGenericFileAccess extends FileSystemAccess, ShellJSCall {
ShellJSGenericFileAccess() {
name = ["cd", "cp", "touch", "chmod", "pushd", "find", "ls", "ln", "mkdir", "mv", "rm"]
name =
["cd", "cp", "touch", "chmod", "pushd", "find", "ls", "ln", "mkdir", "mv", "rm", "which"]
}
override DataFlow::Node getAPathArgument() { result = this.getAnArgument() }
@@ -111,7 +113,8 @@ module ShellJS {
private class ShellJSFilenameSource extends FileNameSource, ShellJSCall {
ShellJSFilenameSource() {
name = "find" or
name = "ls"
name = "ls" or
name = "which"
}
}
@@ -151,16 +154,24 @@ module ShellJS {
}
/**
* A call to `shelljs.exec()` modeled as command execution.
* A call to `shelljs.exec()`, `shelljs.cmd()`, or `async-shelljs.asyncExec()` modeled as command execution.
*/
private class ShellJSExec extends SystemCommandExecution, ShellJSCall {
ShellJSExec() { name = "exec" }
ShellJSExec() { name = ["exec", "cmd", "asyncExec"] }
override DataFlow::Node getACommandArgument() { result = this.getArgument(0) }
override DataFlow::Node getACommandArgument() {
if name = "cmd"
then
result = this.getArgument(_) and
not result = this.getOptionsArg()
else
// For exec/asyncExec: only first argument is command
result = this.getArgument(0)
}
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getACommandArgument() }
override predicate isSync() { none() }
override predicate isSync() { name = "cmd" }
override DataFlow::Node getOptionsArg() {
result = this.getLastArgument() and

View File

@@ -19,6 +19,7 @@
private import javascript
private import internal.ApiGraphModels as Shared
private import internal.ApiGraphModelsSpecific as Specific
private import semmle.javascript.dataflow.internal.FlowSummaryPrivate
private import semmle.javascript.endpoints.EndpointNaming as EndpointNaming
import Shared::ModelInput as ModelInput
import Shared::ModelOutput as ModelOutput
@@ -45,12 +46,94 @@ private class ThreatModelSourceFromDataExtension extends ThreatModelSource::Rang
}
}
private class SummarizedCallableFromModel extends DataFlow::SummarizedCallable {
string type;
string path;
SummarizedCallableFromModel() {
ModelOutput::relevantSummaryModel(type, path, _, _, _, _) and
this = type + ";" + path
}
override DataFlow::InvokeNode getACall() { ModelOutput::resolvedSummaryBase(type, path, result) }
override predicate propagatesFlow(
string input, string output, boolean preservesValue, string model
) {
exists(string kind | ModelOutput::relevantSummaryModel(type, path, input, output, kind, model) |
kind = "value" and
preservesValue = true
or
kind = "taint" and
preservesValue = false
)
}
predicate hasTypeAndPath(string type_, string path_) { type = type_ and path = path_ }
predicate isUnsupportedByFlowSummaries() { unsupportedCallable(this) }
}
private predicate shouldInduceStepsFromSummary(string type, string path) {
exists(SummarizedCallableFromModel callable |
callable.isUnsupportedByFlowSummaries() and
callable.hasTypeAndPath(type, path)
)
}
/**
* Holds if `path` is an input or output spec for a summary with the given `base` node.
*/
pragma[nomagic]
private predicate relevantInputOutputPath(API::InvokeNode base, AccessPath inputOrOutput) {
exists(string type, string input, string output, string path |
// If the summary for 'callable' could not be handled as a flow summary, we need to evaluate
// its inputs and outputs to a set of nodes, so we can generate steps instead.
shouldInduceStepsFromSummary(type, path) and
ModelOutput::resolvedSummaryBase(type, path, base) and
ModelOutput::relevantSummaryModel(type, path, input, output, _, _) and
inputOrOutput = [input, output]
)
}
/**
* Gets the API node for the first `n` tokens of the given input/output path, evaluated relative to `baseNode`.
*/
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPath path, int n) {
relevantInputOutputPath(baseNode, path) and
(
n = 1 and
result = Shared::getSuccessorFromInvoke(baseNode, path.getToken(0))
or
result =
Shared::getSuccessorFromNode(getNodeFromInputOutputPath(baseNode, path, n - 1),
path.getToken(n - 1))
)
}
/**
* Gets the API node for the given input/output path, evaluated relative to `baseNode`.
*/
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPath path) {
result = getNodeFromInputOutputPath(baseNode, path, path.getNumToken())
}
private predicate summaryStep(API::Node pred, API::Node succ, string kind) {
exists(string type, string path, API::InvokeNode base, AccessPath input, AccessPath output |
shouldInduceStepsFromSummary(type, path) and
ModelOutput::relevantSummaryModel(type, path, input, output, kind, _) and
ModelOutput::resolvedSummaryBase(type, path, base) and
pred = getNodeFromInputOutputPath(base, input) and
succ = getNodeFromInputOutputPath(base, output)
)
}
/**
* Like `ModelOutput::summaryStep` but with API nodes mapped to data-flow nodes.
*/
private predicate summaryStepNodes(DataFlow::Node pred, DataFlow::Node succ, string kind) {
exists(API::Node predNode, API::Node succNode |
Specific::summaryStep(predNode, succNode, kind) and
summaryStep(predNode, succNode, kind) and
pred = predNode.asSink() and
succ = succNode.asSource()
)

View File

@@ -272,51 +272,6 @@ predicate invocationMatchesExtraCallSiteFilter(API::InvokeNode invoke, AccessPat
)
}
/**
* Holds if `path` is an input or output spec for a summary with the given `base` node.
*/
pragma[nomagic]
private predicate relevantInputOutputPath(API::InvokeNode base, AccessPath inputOrOutput) {
exists(string type, string input, string output, string path |
ModelOutput::relevantSummaryModel(type, path, input, output, _, _) and
ModelOutput::resolvedSummaryBase(type, path, base) and
inputOrOutput = [input, output]
)
}
/**
* Gets the API node for the first `n` tokens of the given input/output path, evaluated relative to `baseNode`.
*/
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPath path, int n) {
relevantInputOutputPath(baseNode, path) and
(
n = 1 and
result = getSuccessorFromInvoke(baseNode, path.getToken(0))
or
result =
getSuccessorFromNode(getNodeFromInputOutputPath(baseNode, path, n - 1), path.getToken(n - 1))
)
}
/**
* Gets the API node for the given input/output path, evaluated relative to `baseNode`.
*/
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPath path) {
result = getNodeFromInputOutputPath(baseNode, path, path.getNumToken())
}
/**
* Holds if a CSV summary contributed the step `pred -> succ` of the given `kind`.
*/
predicate summaryStep(API::Node pred, API::Node succ, string kind) {
exists(string type, string path, API::InvokeNode base, AccessPath input, AccessPath output |
ModelOutput::relevantSummaryModel(type, path, input, output, kind, _) and
ModelOutput::resolvedSummaryBase(type, path, base) and
pred = getNodeFromInputOutputPath(base, input) and
succ = getNodeFromInputOutputPath(base, output)
)
}
class InvokeNode = API::InvokeNode;
/** Gets an `InvokeNode` corresponding to an invocation of `node`. */

View File

@@ -171,7 +171,7 @@ module CleartextLogging {
/** An access to the sensitive object `process.env`. */
class ProcessEnvSource extends Source {
ProcessEnvSource() { this = NodeJSLib::process().getAPropertyRead("env") }
ProcessEnvSource() { this.(ThreatModelSource).getThreatModel() = "environment" }
override string describe() { result = "process environment" }
}

View File

@@ -29,7 +29,7 @@ module IndirectCommandInjection {
* A read of `process.env`, considered as a flow source for command injection.
*/
private class ProcessEnvAsSource extends Source {
ProcessEnvAsSource() { this = NodeJSLib::process().getAPropertyRead("env") }
ProcessEnvAsSource() { this.(ThreatModelSource).getThreatModel() = "environment" }
override string describe() { result = "environment variable" }
}
@@ -37,7 +37,7 @@ module IndirectCommandInjection {
/** Gets a data flow node referring to `process.env`. */
private DataFlow::SourceNode envObject(DataFlow::TypeTracker t) {
t.start() and
result = NodeJSLib::process().getAPropertyRead("env")
result.(ThreatModelSource).getThreatModel() = "environment"
or
exists(DataFlow::TypeTracker t2 | result = envObject(t2).track(t2, t))
}

View File

@@ -37,7 +37,7 @@ DataFlow::ObjectLiteralNode tlsOptions() { result.flowsTo(tlsInvocation().getAnA
from DataFlow::PropWrite disable
where
exists(DataFlow::SourceNode env |
env = NodeJSLib::process().getAPropertyRead("env") and
env.(ThreatModelSource).getThreatModel() = "environment" and
disable = env.getAPropertyWrite("NODE_TLS_REJECT_UNAUTHORIZED") and
disable.getRhs().mayHaveStringValue("0")
)

View File

@@ -39,7 +39,7 @@ function strToStr() {
}
function strToArray() {
sink(s.chop(source("s1"), 3)); // $ MISSING: hasTaintFlow=s1
sink(s.chop(source("s1"), 3)); // $ hasTaintFlow=s1
sink(s.chars(source("s2"))[0]); // $ hasTaintFlow=s2
sink(s.words(source("s3"))[0]); // $ hasTaintFlow=s3
sink(s.lines(source("s7"))[0]); // $ hasTaintFlow=s7
@@ -97,7 +97,7 @@ function multiSource() {
function chaining() {
sink(s(source("s1"))
.slugify().capitalize().decapitalize().clean().cleanDiacritics()
.slugify().capitalize().decapitalize().clean().cleanDiacritics()
.swapCase().escapeHTML().unescapeHTML().wrap().dedent()
.reverse().pred().succ().titleize().camelize().classify()
.underscored().dasherize().humanize().trim().ltrim().rtrim()
@@ -119,8 +119,8 @@ function chaining() {
.q(source("s17")).ljust(10, source("s18"))
.rjust(10, source("s19"))); // $ hasTaintFlow=s16 hasTaintFlow=s17 hasTaintFlow=s18 hasTaintFlow=s19
sink(s(source("s20")).tap(function(value) {
return value + source("s21");
sink(s(source("s20")).tap(function(value) {
return value + source("s21");
}).value()); // $ hasTaintFlow=s20 hasTaintFlow=s21
}

View File

@@ -55,14 +55,19 @@ test_FileSystemAccess
| tst.js:60:1:60:17 | shelljs.cat(file) |
| tst.js:60:1:60:41 | shelljs ... cement) |
| tst.js:61:1:61:17 | shelljs.cat(file) |
| tst.js:65:1:65:19 | shelljs.which(file) |
test_MissingFileSystemAccess
test_SystemCommandExecution
| tst.js:14:1:14:27 | shelljs ... ts, cb) |
| tst.js:60:1:60:51 | shelljs ... ec(cmd) |
| tst.js:61:1:61:27 | shelljs ... ec(cmd) |
| tst.js:63:1:63:37 | shelljs ... ptions) |
| tst.js:64:1:64:16 | shelljs.cmd(cmd) |
| tst.js:68:1:68:36 | shelljs ... ts, cb) |
test_FileNameSource
| tst.js:15:1:15:26 | shelljs ... file2) |
| tst.js:24:1:24:16 | shelljs.ls(file) |
| tst.js:25:1:25:22 | shelljs ... , file) |
| tst.js:26:1:26:30 | shelljs ... file2) |
| tst.js:27:1:27:24 | shelljs ... file2) |
| tst.js:65:1:65:19 | shelljs.which(file) |

View File

@@ -59,3 +59,10 @@ shelljs.uniq(opts, file1, file2);
shelljs.cat(file).sed(regex, replacement).exec(cmd);
shelljs.cat(file).exec(cmd);
shelljs.cmd(cmd, arg1, arg2, options);
shelljs.cmd(cmd);
shelljs.which(file);
const shelljssync = require("async-shelljs");
shelljssync.asyncExec(cmd, opts, cb);

View File

@@ -1,4 +1,21 @@
legacyDataFlowDifference
| test.js:5:30:5:37 | source() | test.js:5:8:5:38 | testlib ... urce()) | only flow with NEW data flow library |
| test.js:6:22:6:29 | source() | test.js:6:8:6:30 | preserv ... urce()) | only flow with NEW data flow library |
| test.js:7:41:7:48 | source() | test.js:7:8:7:49 | require ... urce()) | only flow with NEW data flow library |
| test.js:11:38:11:45 | source() | test.js:11:8:11:55 | testlib ... , 1, 1) | only flow with NEW data flow library |
| test.js:13:44:13:51 | source() | test.js:13:8:13:55 | testlib ... e(), 1) | only flow with NEW data flow library |
| test.js:17:47:17:54 | source() | test.js:17:8:17:61 | testlib ... , 1, 1) | only flow with NEW data flow library |
| test.js:18:50:18:57 | source() | test.js:18:8:18:61 | testlib ... e(), 1) | only flow with NEW data flow library |
| test.js:19:53:19:60 | source() | test.js:19:8:19:61 | testlib ... urce()) | only flow with NEW data flow library |
| test.js:21:34:21:41 | source() | test.js:21:8:21:51 | testlib ... , 1, 1) | only flow with NEW data flow library |
| test.js:22:37:22:44 | source() | test.js:22:8:22:51 | testlib ... , 1, 1) | only flow with NEW data flow library |
| test.js:23:40:23:47 | source() | test.js:23:8:23:51 | testlib ... e(), 1) | only flow with NEW data flow library |
| test.js:24:43:24:50 | source() | test.js:24:8:24:51 | testlib ... urce()) | only flow with NEW data flow library |
| test.js:31:29:31:36 | source() | test.js:32:10:32:10 | y | only flow with NEW data flow library |
| test.js:37:29:37:36 | source() | test.js:38:10:38:10 | y | only flow with NEW data flow library |
| test.js:43:29:43:36 | source() | test.js:44:10:44:10 | y | only flow with NEW data flow library |
| test.js:47:33:47:40 | source() | test.js:49:10:49:13 | this | only flow with NEW data flow library |
| test.js:102:16:102:34 | testlib.getSource() | test.js:104:8:104:24 | source.continue() | only flow with NEW data flow library |
consistencyIssue
taintFlow
| guardedRouteHandler.js:6:10:6:28 | req.injectedReqData | guardedRouteHandler.js:6:10:6:28 | req.injectedReqData |

View File

@@ -2,6 +2,9 @@
| actions.js:4:6:4:29 | process ... _DATA'] | actions.js:4:6:4:16 | process.env | actions.js:4:6:4:29 | process ... _DATA'] | This command depends on an unsanitized $@. | actions.js:4:6:4:16 | process.env | environment variable |
| actions.js:8:10:8:23 | e['TEST_DATA'] | actions.js:12:6:12:16 | process.env | actions.js:8:10:8:23 | e['TEST_DATA'] | This command depends on an unsanitized $@. | actions.js:12:6:12:16 | process.env | environment variable |
| actions.js:14:6:14:21 | getInput('data') | actions.js:14:6:14:21 | getInput('data') | actions.js:14:6:14:21 | getInput('data') | This command depends on an unsanitized $@. | actions.js:14:6:14:21 | getInput('data') | GitHub Actions user input |
| actions.js:18:10:18:40 | 'rm -rf ... 'SOME'] | actions.js:18:22:18:32 | shelljs.env | actions.js:18:10:18:40 | 'rm -rf ... 'SOME'] | This command depends on an unsanitized $@. | actions.js:18:22:18:32 | shelljs.env | environment variable |
| actions.js:19:10:19:37 | 'rm -rf ... nv.SOME | actions.js:19:22:19:32 | shelljs.env | actions.js:19:10:19:37 | 'rm -rf ... nv.SOME | This command depends on an unsanitized $@. | actions.js:19:22:19:32 | shelljs.env | environment variable |
| actions.js:20:10:20:32 | 'rm -rf ... ljs.env | actions.js:20:22:20:32 | shelljs.env | actions.js:20:10:20:32 | 'rm -rf ... ljs.env | This command depends on an unsanitized $@. | actions.js:20:22:20:32 | shelljs.env | environment variable |
| command-line-parameter-command-injection.js:4:10:4:21 | process.argv | command-line-parameter-command-injection.js:4:10:4:21 | process.argv | command-line-parameter-command-injection.js:4:10:4:21 | process.argv | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:4:10:4:21 | process.argv | command-line argument |
| command-line-parameter-command-injection.js:8:10:8:36 | "cmd.sh ... argv[2] | command-line-parameter-command-injection.js:8:22:8:33 | process.argv | command-line-parameter-command-injection.js:8:10:8:36 | "cmd.sh ... argv[2] | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:8:22:8:33 | process.argv | command-line argument |
| command-line-parameter-command-injection.js:11:14:11:20 | args[0] | command-line-parameter-command-injection.js:10:13:10:24 | process.argv | command-line-parameter-command-injection.js:11:14:11:20 | args[0] | This command depends on an unsanitized $@. | command-line-parameter-command-injection.js:10:13:10:24 | process.argv | command-line argument |
@@ -44,6 +47,9 @@ edges
| actions.js:7:15:7:15 | e | actions.js:8:10:8:10 | e | provenance | |
| actions.js:8:10:8:10 | e | actions.js:8:10:8:23 | e['TEST_DATA'] | provenance | |
| actions.js:12:6:12:16 | process.env | actions.js:7:15:7:15 | e | provenance | |
| actions.js:18:22:18:32 | shelljs.env | actions.js:18:10:18:40 | 'rm -rf ... 'SOME'] | provenance | |
| actions.js:19:22:19:32 | shelljs.env | actions.js:19:10:19:37 | 'rm -rf ... nv.SOME | provenance | |
| actions.js:20:22:20:32 | shelljs.env | actions.js:20:10:20:32 | 'rm -rf ... ljs.env | provenance | |
| command-line-parameter-command-injection.js:8:22:8:33 | process.argv | command-line-parameter-command-injection.js:8:10:8:36 | "cmd.sh ... argv[2] | provenance | |
| command-line-parameter-command-injection.js:10:6:10:33 | args | command-line-parameter-command-injection.js:11:14:11:17 | args | provenance | |
| command-line-parameter-command-injection.js:10:6:10:33 | args | command-line-parameter-command-injection.js:12:26:12:29 | args | provenance | |
@@ -181,6 +187,12 @@ nodes
| actions.js:8:10:8:23 | e['TEST_DATA'] | semmle.label | e['TEST_DATA'] |
| actions.js:12:6:12:16 | process.env | semmle.label | process.env |
| actions.js:14:6:14:21 | getInput('data') | semmle.label | getInput('data') |
| actions.js:18:10:18:40 | 'rm -rf ... 'SOME'] | semmle.label | 'rm -rf ... 'SOME'] |
| actions.js:18:22:18:32 | shelljs.env | semmle.label | shelljs.env |
| actions.js:19:10:19:37 | 'rm -rf ... nv.SOME | semmle.label | 'rm -rf ... nv.SOME |
| actions.js:19:22:19:32 | shelljs.env | semmle.label | shelljs.env |
| actions.js:20:10:20:32 | 'rm -rf ... ljs.env | semmle.label | 'rm -rf ... ljs.env |
| actions.js:20:22:20:32 | shelljs.env | semmle.label | shelljs.env |
| command-line-parameter-command-injection.js:4:10:4:21 | process.argv | semmle.label | process.argv |
| command-line-parameter-command-injection.js:8:10:8:36 | "cmd.sh ... argv[2] | semmle.label | "cmd.sh ... argv[2] |
| command-line-parameter-command-injection.js:8:22:8:33 | process.argv | semmle.label | process.argv |

View File

@@ -12,3 +12,10 @@ function test(e) {
test(process.env); // $ Source
exec(getInput('data')); // $ Alert
function test2(e) {
const shelljs = require('shelljs');
exec('rm -rf ' + shelljs.env['SOME']); // $ Alert
exec('rm -rf ' + shelljs.env.SOME); // $ Alert
exec('rm -rf ' + shelljs.env); // $ Alert
}

View File

@@ -78,7 +78,9 @@ edges
| ReflectedXss.js:22:19:22:26 | req.body | ReflectedXss.js:22:12:22:27 | marked(req.body) | provenance | |
| ReflectedXss.js:29:7:32:4 | mytable | ReflectedXss.js:33:12:33:18 | mytable | provenance | |
| ReflectedXss.js:29:17:32:4 | table([ ... ce\\n ]) | ReflectedXss.js:29:7:32:4 | mytable | provenance | |
| ReflectedXss.js:31:14:31:21 | req.body | ReflectedXss.js:29:17:32:4 | table([ ... ce\\n ]) | provenance | |
| ReflectedXss.js:29:23:32:3 | [\\n [ ... rce\\n ] [1, 1] | ReflectedXss.js:29:17:32:4 | table([ ... ce\\n ]) | provenance | |
| ReflectedXss.js:31:5:31:22 | ['body', req.body] [1] | ReflectedXss.js:29:23:32:3 | [\\n [ ... rce\\n ] [1, 1] | provenance | |
| ReflectedXss.js:31:14:31:21 | req.body | ReflectedXss.js:31:5:31:22 | ['body', req.body] [1] | provenance | |
| ReflectedXss.js:41:31:41:38 | req.body | ReflectedXss.js:41:12:41:39 | convert ... q.body) | provenance | |
| ReflectedXss.js:63:14:63:21 | req.body | ReflectedXss.js:63:39:63:42 | file | provenance | |
| ReflectedXss.js:63:39:63:42 | file | ReflectedXss.js:64:16:64:19 | file | provenance | |
@@ -253,6 +255,8 @@ nodes
| ReflectedXss.js:28:12:28:19 | req.body | semmle.label | req.body |
| ReflectedXss.js:29:7:32:4 | mytable | semmle.label | mytable |
| ReflectedXss.js:29:17:32:4 | table([ ... ce\\n ]) | semmle.label | table([ ... ce\\n ]) |
| ReflectedXss.js:29:23:32:3 | [\\n [ ... rce\\n ] [1, 1] | semmle.label | [\\n [ ... rce\\n ] [1, 1] |
| ReflectedXss.js:31:5:31:22 | ['body', req.body] [1] | semmle.label | ['body', req.body] [1] |
| ReflectedXss.js:31:14:31:21 | req.body | semmle.label | req.body |
| ReflectedXss.js:33:12:33:18 | mytable | semmle.label | mytable |
| ReflectedXss.js:40:12:40:19 | req.body | semmle.label | req.body |

View File

@@ -1,10 +1,8 @@
#!/usr/bin/python3
import helpers
import json
import os
import os.path
import shlex
import subprocess
import sys
import tempfile
@@ -27,24 +25,13 @@ def parseData(data):
return rows
class Generator:
def __init__ (self, language):
self.language = language
self.generateSinks = False
self.generateSources = False
self.generateSummaries = False
self.generateNeutrals = False
self.generateTypeBasedSummaries = False
self.dryRun = False
self.dirname = "modelgenerator"
def printHelp(self):
print(f"""Usage:
python3 GenerateFlowModel.py <library-database> [DIR] [--with-sinks] [--with-sources] [--with-summaries] [--with-neutrals] [--with-typebased-summaries] [--dry-run]
def printHelp():
print(f"""Usage:
python3 generate_mad.py <library-database> [DIR] --language LANGUAGE [--with-sinks] [--with-sources] [--with-summaries] [--with-neutrals] [--with-typebased-summaries] [--dry-run]
This generates summary, source, sink and neutral models for the code in the database.
The files will be placed in `{self.language}/ql/lib/ext/generated/DIR`
The files will be placed in `LANGUAGE/ql/lib/ext/generated/DIR`
Which models are generated is controlled by the flags:
--with-sinks
@@ -57,14 +44,25 @@ If none of these flags are specified, all models are generated except for the ty
--dry-run: Only run the queries, but don't write to file.
Example invocations:
$ python3 GenerateFlowModel.py /tmp/dbs/my_library_db
$ python3 GenerateFlowModel.py /tmp/dbs/my_library_db --with-sinks
$ python3 GenerateFlowModel.py /tmp/dbs/my_library_db --with-sinks my_directory
$ python3 generate_mad.py /tmp/dbs/my_library_db
$ python3 generate_mad.py /tmp/dbs/my_library_db --with-sinks
$ python3 generate_mad.py /tmp/dbs/my_library_db --with-sinks my_directory
Requirements: `codeql` should appear on your path.
""")
class Generator:
def __init__(self, language):
self.language = language
self.generateSinks = False
self.generateSources = False
self.generateSummaries = False
self.generateNeutrals = False
self.generateTypeBasedSummaries = False
self.dryRun = False
self.dirname = "modelgenerator"
def setenvironment(self, database, folder):
self.codeQlRoot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip()
@@ -76,12 +74,22 @@ Requirements: `codeql` should appear on your path.
@staticmethod
def make(language):
generator = Generator(language)
def make():
# Create a generator instance based on command line arguments.
if any(s == "--help" for s in sys.argv):
generator.printHelp()
printHelp()
sys.exit(0)
if "--language" in sys.argv:
language = sys.argv[sys.argv.index("--language") + 1]
sys.argv.remove("--language")
sys.argv.remove(language)
else:
printHelp()
sys.exit(0)
generator = Generator(language=language)
if "--with-sinks" in sys.argv:
sys.argv.remove("--with-sinks")
generator.generateSinks = True
@@ -115,7 +123,7 @@ Requirements: `codeql` should appear on your path.
n = len(sys.argv)
if n < 2:
generator.printHelp()
printHelp()
sys.exit(1)
elif n == 2:
generator.setenvironment(sys.argv[1], "")
@@ -204,3 +212,6 @@ extensions:
if self.generateTypeBasedSummaries:
self.save(typeBasedContent, ".typebased.model.yml")
if __name__ == '__main__':
Generator.make().run()

View File

@@ -1,2 +1,3 @@
ql/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql
ql/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql
ql/ruby/ql/src/queries/variables/UninitializedLocal.ql

View File

@@ -0,0 +1,5 @@
---
category: queryMetadata
---
* The precision of `rb/useless-assignment-to-local` has been adjusted from `medium` to `high`.

View File

@@ -8,7 +8,7 @@
* @tags maintainability
* quality
* external/cwe/cwe-563
* @precision medium
* @precision high
*/
import codeql.ruby.AST

View File

@@ -36,6 +36,7 @@ pkg_filegroup(
srcs = [
":tools-arch",
"//rust/tools",
"//rust/tools/builtins",
],
prefix = "tools",
)

View File

@@ -23,6 +23,7 @@ fn class_name(type_name: &str) -> String {
"Literal" => "LiteralExpr".to_owned(),
"ArrayExpr" => "ArrayExprInternal".to_owned(),
"AsmOptions" => "AsmOptionsList".to_owned(),
"MacroStmts" => "MacroBlockExpr".to_owned(),
_ if type_name.starts_with("Record") => type_name.replacen("Record", "Struct", 1),
_ if type_name.ends_with("Type") => format!("{}Repr", type_name),
_ => type_name.to_owned(),
@@ -36,6 +37,7 @@ fn property_name(type_name: &str, field_name: &str) -> String {
("MatchExpr", "expr") => "scrutinee",
("Variant", "expr") => "discriminant",
("FieldExpr", "expr") => "container",
("MacroBlockExpr", "expr") => "tail_expr",
(_, "name_ref") => "identifier",
(_, "then_branch") => "then",
(_, "else_branch") => "else_",

View File

@@ -182,7 +182,6 @@ named_crates(
| @label
| @let_else
| @macro_items
| @macro_stmts
| @match_arm
| @match_arm_list
| @match_guard
@@ -438,6 +437,7 @@ closure_binder_generic_param_lists(
| @labelable_expr
| @let_expr
| @literal_expr
| @macro_block_expr
| @macro_expr
| @match_expr
| @offset_of_expr
@@ -585,23 +585,6 @@ macro_items_items(
int item: @item ref
);
macro_stmts(
unique int id: @macro_stmts
);
#keyset[id]
macro_stmts_exprs(
int id: @macro_stmts ref,
int expr: @expr ref
);
#keyset[id, index]
macro_stmts_statements(
int id: @macro_stmts ref,
int index: int ref,
int statement: @stmt ref
);
match_arms(
unique int id: @match_arm
);
@@ -2104,6 +2087,23 @@ literal_pat_literals(
int literal: @literal_expr ref
);
macro_block_exprs(
unique int id: @macro_block_expr
);
#keyset[id]
macro_block_expr_tail_exprs(
int id: @macro_block_expr ref,
int tail_expr: @expr ref
);
#keyset[id, index]
macro_block_expr_statements(
int id: @macro_block_expr ref,
int index: int ref,
int statement: @stmt ref
);
macro_exprs(
unique int id: @macro_expr
);

Some files were not shown because too many files have changed in this diff Show More