mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Merge branch 'main' into redsun82/rust-expand-attr-macros
This commit is contained in:
2
.github/workflows/mad_modelDiff.yml
vendored
2
.github/workflows/mad_modelDiff.yml
vendored
@@ -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 ..
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
113
cpp/ql/lib/experimental/quantum/Language.qll
Normal file
113
cpp/ql/lib/experimental/quantum/Language.qll
Normal 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
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
import experimental.quantum.Language
|
||||
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumerBase
|
||||
|
||||
abstract class OpenSSLAlgorithmInstance extends Crypto::AlgorithmInstance {
|
||||
abstract OpenSSLAlgorithmValueConsumer getAVC();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import OpenSSLAlgorithmInstanceBase
|
||||
import CipherAlgorithmInstance
|
||||
import PaddingAlgorithmInstance
|
||||
import BlockAlgorithmInstance
|
||||
import HashAlgorithmInstance
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import OpenSSLAlgorithmValueConsumerBase
|
||||
import CipherAlgorithmValueConsumer
|
||||
import DirectAlgorithmValueConsumer
|
||||
import PaddingAlgorithmValueConsumer
|
||||
import HashAlgorithmValueConsumer
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
99
cpp/ql/lib/experimental/quantum/OpenSSL/CtxFlow.qll
Normal file
99
cpp/ql/lib/experimental/quantum/OpenSSL/CtxFlow.qll
Normal 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
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import cpp
|
||||
|
||||
predicate isPossibleOpenSSLFunction(Function f) {
|
||||
isPossibleOpenSSLLocation(f.getADeclarationLocation())
|
||||
}
|
||||
|
||||
predicate isPossibleOpenSSLLocation(Location l) { l.toString().toLowerCase().matches("%openssl%") }
|
||||
9
cpp/ql/lib/experimental/quantum/OpenSSL/OpenSSL.qll
Normal file
9
cpp/ql/lib/experimental/quantum/OpenSSL/OpenSSL.qll
Normal 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
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
@@ -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
|
||||
// }
|
||||
// }
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import OpenSSLOperationBase
|
||||
import EVPCipherOperation
|
||||
import EVPHashOperation
|
||||
18
cpp/ql/lib/experimental/quantum/OpenSSL/Random.qll
Normal file
18
cpp/ql/lib/experimental/quantum/OpenSSL/Random.qll
Normal 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() }
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
23
cpp/ql/src/experimental/quantum/PrintCBOMGraph.ql
Normal file
23
cpp/ql/src/experimental/quantum/PrintCBOMGraph.ql
Normal 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"
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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: --load-sources-from-project:${testdir}/../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
|
||||
semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace System.Net.Http
|
||||
{
|
||||
public StringContent(string s) { }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace System.Net.Mail
|
||||
|
||||
14
csharp/ql/test/resources/stubs/System.Web.Http.cs
Normal file
14
csharp/ql/test/resources/stubs/System.Web.Http.cs
Normal 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 { }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
1618
java/ql/lib/experimental/quantum/JCA.qll
Normal file
1618
java/ql/lib/experimental/quantum/JCA.qll
Normal file
File diff suppressed because it is too large
Load Diff
218
java/ql/lib/experimental/quantum/Language.qll
Normal file
218
java/ql/lib/experimental/quantum/Language.qll
Normal 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
|
||||
@@ -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}
|
||||
|
||||
70
java/ql/src/experimental/quantum/Analysis/ArtifactReuse.qll
Normal file
70
java/ql/src/experimental/quantum/Analysis/ArtifactReuse.qll
Normal 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)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
17
java/ql/src/experimental/quantum/Analysis/ReusedNonce.ql
Normal file
17
java/ql/src/experimental/quantum/Analysis/ReusedNonce.ql
Normal 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()
|
||||
@@ -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()
|
||||
75
java/ql/src/experimental/quantum/Examples/BrokenCrypto.ql
Normal file
75
java/ql/src/experimental/quantum/Examples/BrokenCrypto.ql
Normal 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()
|
||||
16
java/ql/src/experimental/quantum/Examples/TestAESGCMNonce.ql
Normal file
16
java/ql/src/experimental/quantum/Examples/TestAESGCMNonce.ql
Normal 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()
|
||||
18
java/ql/src/experimental/quantum/Examples/TestCipher.ql
Normal file
18
java/ql/src/experimental/quantum/Examples/TestCipher.ql
Normal 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
|
||||
9
java/ql/src/experimental/quantum/Examples/TestHash.ql
Normal file
9
java/ql/src/experimental/quantum/Examples/TestHash.ql
Normal 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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
23
java/ql/src/experimental/quantum/PrintCBOMGraph.ql
Normal file
23
java/ql/src/experimental/quantum/PrintCBOMGraph.ql
Normal 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"
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
4
javascript/ql/lib/change-notes/2025-04-30-shelljs.md
Normal file
4
javascript/ql/lib/change-notes/2025-04-30-shelljs.md
Normal 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`.
|
||||
6
javascript/ql/lib/ext/shelljs.model.yml
Normal file
6
javascript/ql/lib/ext/shelljs.model.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: sourceModel
|
||||
data:
|
||||
- ["shelljs", "Member[env]", "environment"]
|
||||
@@ -264,3 +264,7 @@ module Stage {
|
||||
cached
|
||||
predicate backref() { optionalStep(_, _, _) }
|
||||
}
|
||||
|
||||
predicate unsupportedCallable = Private::unsupportedCallable/1;
|
||||
|
||||
predicate unsupportedCallable = Private::unsupportedCallable/4;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
@@ -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`. */
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: queryMetadata
|
||||
---
|
||||
|
||||
* The precision of `rb/useless-assignment-to-local` has been adjusted from `medium` to `high`.
|
||||
@@ -8,7 +8,7 @@
|
||||
* @tags maintainability
|
||||
* quality
|
||||
* external/cwe/cwe-563
|
||||
* @precision medium
|
||||
* @precision high
|
||||
*/
|
||||
|
||||
import codeql.ruby.AST
|
||||
|
||||
@@ -36,6 +36,7 @@ pkg_filegroup(
|
||||
srcs = [
|
||||
":tools-arch",
|
||||
"//rust/tools",
|
||||
"//rust/tools/builtins",
|
||||
],
|
||||
prefix = "tools",
|
||||
)
|
||||
|
||||
@@ -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_",
|
||||
|
||||
@@ -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
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user