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,3 @@
|
||||
semmle-extractor-options: /nostdlib /noconfig
|
||||
semmle-extractor-options: --load-sources-from-project:${testdir}/../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
|
||||
semmle-extractor-options: ${testdir}/../../resources/stubs/System.Web.cs
|
||||
semmle-extractor-options: ${testdir}/../../resources/stubs/System.Web.cs
|
||||
|
||||
@@ -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: ${testdir}/../../../resources/stubs/System.Web.cs
|
||||
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
|
||||
semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs
|
||||
|
||||
@@ -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,3 +1,3 @@
|
||||
semmle-extractor-options: /nostdlib /noconfig
|
||||
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
|
||||
semmle-extractor-options: ${testdir}/../../../../../resources/stubs/System.Web.cs
|
||||
semmle-extractor-options: ${testdir}/../../../../../resources/stubs/System.Web.cs
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
semmle-extractor-options: /nostdlib /noconfig
|
||||
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
|
||||
semmle-extractor-options: ${testdir}/../../../../resources/stubs/System.Web.cs
|
||||
semmle-extractor-options: ${testdir}/../../../../resources/stubs/System.Web.cs
|
||||
|
||||
@@ -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
|
||||
@@ -97,7 +97,7 @@ function multiSource() {
|
||||
|
||||
function chaining() {
|
||||
sink(s(source("s1"))
|
||||
.slugify().capitalize().decapitalize().clean().cleanDiacritics()
|
||||
.slugify().capitalize().decapitalize().clean().cleanDiacritics()
|
||||
.swapCase().escapeHTML().unescapeHTML().wrap().dedent()
|
||||
.reverse().pred().succ().titleize().camelize().classify()
|
||||
.underscored().dasherize().humanize().trim().ltrim().rtrim()
|
||||
@@ -119,8 +119,8 @@ function chaining() {
|
||||
.q(source("s17")).ljust(10, source("s18"))
|
||||
.rjust(10, source("s19"))); // $ hasTaintFlow=s16 hasTaintFlow=s17 hasTaintFlow=s18 hasTaintFlow=s19
|
||||
|
||||
sink(s(source("s20")).tap(function(value) {
|
||||
return value + source("s21");
|
||||
sink(s(source("s20")).tap(function(value) {
|
||||
return value + source("s21");
|
||||
}).value()); // $ hasTaintFlow=s20 hasTaintFlow=s21
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user