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

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

View File

@@ -68,7 +68,7 @@ jobs:
DATABASE=$2 DATABASE=$2
cd codeql-$QL_VARIANT cd codeql-$QL_VARIANT
SHORTNAME=`basename $DATABASE` 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 mkdir -p $MODELS/$SHORTNAME
mv java/ql/lib/ext/generated/$SHORTNAME/$QL_VARIANT $MODELS/$SHORTNAME mv java/ql/lib/ext/generated/$SHORTNAME/$QL_VARIANT $MODELS/$SHORTNAME
cd .. cd ..

View File

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

View File

@@ -299,6 +299,7 @@ ql/cpp/ql/src/experimental/cryptography/inventory/new_models/SigningAlgorithms.q
ql/cpp/ql/src/experimental/cryptography/inventory/new_models/SymmetricEncryptionAlgorithms.ql ql/cpp/ql/src/experimental/cryptography/inventory/new_models/SymmetricEncryptionAlgorithms.ql
ql/cpp/ql/src/experimental/cryptography/inventory/new_models/SymmetricPaddingAlgorithms.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/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/BumpMetricBy10.ql
ql/cpp/ql/src/external/examples/filters/EditDefectMessage.ql ql/cpp/ql/src/external/examples/filters/EditDefectMessage.ql
ql/cpp/ql/src/external/examples/filters/ExcludeGeneratedCode.ql ql/cpp/ql/src/external/examples/filters/ExcludeGeneratedCode.ql

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
import cpp
import semmle.code.cpp.dataflow.new.DataFlow
module OpenSSLModel {
import experimental.quantum.Language
import experimental.quantum.OpenSSL.AlgorithmInstances.OpenSSLAlgorithmInstances
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
import experimental.quantum.OpenSSL.Operations.OpenSSLOperations
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,3 @@
semmle-extractor-options: /nostdlib /noconfig semmle-extractor-options: /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.NETCore.App/Microsoft.NETCore.App.csproj
semmle-extractor-options: ${testdir}/../../resources/stubs/System.Web.cs semmle-extractor-options: ${testdir}/../../resources/stubs/System.Web.cs

View File

@@ -1,3 +1,4 @@
semmle-extractor-options: /nostdlib /noconfig semmle-extractor-options: /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.cs
semmle-extractor-options: ${testdir}/../../../../resources/stubs/System.Web.Http.cs

View File

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

View File

@@ -1,4 +1,3 @@
semmle-extractor-options: /nostdlib /noconfig 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: --load-sources-from-project:../../../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.cs

View File

@@ -1,3 +1,3 @@
semmle-extractor-options: /nostdlib /noconfig 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.NETCore.App/Microsoft.NETCore.App.csproj
semmle-extractor-options: ${testdir}/../../../../../resources/stubs/System.Web.cs semmle-extractor-options: ${testdir}/../../../../../resources/stubs/System.Web.cs

View File

@@ -1,4 +1,3 @@
semmle-extractor-options: /nostdlib /noconfig semmle-extractor-options: /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: --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.cs

View File

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

View File

@@ -1,3 +1,3 @@
semmle-extractor-options: /nostdlib /noconfig semmle-extractor-options: /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.NETCore.App/Microsoft.NETCore.App.csproj
semmle-extractor-options: ${testdir}/../../../../resources/stubs/System.Web.cs semmle-extractor-options: ${testdir}/../../../../resources/stubs/System.Web.cs

View File

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

View File

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

View File

@@ -19,6 +19,15 @@ namespace System.Web
public class HttpResponseBase public class HttpResponseBase
{ {
public void Write(object obj) { } 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 public class HttpContextBase
@@ -51,26 +60,40 @@ namespace System.Web
} }
} }
namespace System.Web.Http
{
public class ApiController
{
}
}
namespace System.Web.Mvc namespace System.Web.Mvc
{ {
public class Controller public class Controller
{ {
public ViewResult View() => null; 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 class MvcHtmlString : HtmlString
{ {
public MvcHtmlString(string s) : base(s) { } 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 namespace System.Web.UI
{ {
public class Control public class Control
@@ -81,6 +104,7 @@ namespace System.Web.UI
{ {
public System.Security.Principal.IPrincipal User { get; } public System.Security.Principal.IPrincipal User { get; }
public System.Web.HttpRequest Request { get; } public System.Web.HttpRequest Request { get; }
public HttpResponse Response => null;
} }
interface IPostBackDataHandler interface IPostBackDataHandler
@@ -153,6 +177,7 @@ namespace System.Web
public UnvalidatedRequestValues Unvalidated { get; } public UnvalidatedRequestValues Unvalidated { get; }
public string RawUrl { get; set; } public string RawUrl { get; set; }
public HttpCookieCollection Cookies => null; public HttpCookieCollection Cookies => null;
public bool IsAuthenticated { get; set; }
} }
public class HttpRequestWrapper : System.Web.HttpRequestBase public class HttpRequestWrapper : System.Web.HttpRequestBase
@@ -169,6 +194,13 @@ namespace System.Web
public void AddHeader(string name, string value) { } public void AddHeader(string name, string value) { }
public void Redirect(string url) { } public void Redirect(string url) { }
public void AppendHeader(string name, string value) { } 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 public class HttpContext : IServiceProvider
@@ -177,6 +209,7 @@ namespace System.Web
public HttpResponse Response => null; public HttpResponse Response => null;
public SessionState.HttpSessionState Session => null; public SessionState.HttpSessionState Session => null;
public HttpServerUtility Server => null; public HttpServerUtility Server => null;
public static HttpContext Current => null;
} }
public class HttpCookie public class HttpCookie
@@ -301,6 +334,15 @@ namespace System.Web.Mvc
public UrlHelper(Routing.RequestContext requestContext) { } public UrlHelper(Routing.RequestContext requestContext) { }
public virtual bool IsLocalUrl(string url) => false; 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 namespace System.Web.Routing
@@ -390,7 +432,7 @@ namespace System.Web.Script.Serialization
public JavaScriptSerializer() => throw null; public JavaScriptSerializer() => throw null;
public JavaScriptSerializer(System.Web.Script.Serialization.JavaScriptTypeResolver resolver) => throw null; public JavaScriptSerializer(System.Web.Script.Serialization.JavaScriptTypeResolver resolver) => throw null;
public object DeserializeObject(string input) => 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; public object Deserialize(string input, Type targetType) => throw null;
} }

View File

@@ -1582,18 +1582,10 @@ func isAlias(tp types.Type) bool {
return ok 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; // 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 // 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 { func extractType(tw *trap.Writer, tp types.Type) trap.Label {
tp = resolveTypeAlias(tp) tp = types.Unalias(tp)
lbl, exists := getTypeLabel(tw, tp) lbl, exists := getTypeLabel(tw, tp)
if !exists { if !exists {
var kind int 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 // is constructed from their globally unique ID. This prevents cyclic type keys
// since type recursion in Go always goes through defined types. // since type recursion in Go always goes through defined types.
func getTypeLabel(tw *trap.Writer, tp types.Type) (trap.Label, bool) { func getTypeLabel(tw *trap.Writer, tp types.Type) (trap.Label, bool) {
tp = resolveTypeAlias(tp) tp = types.Unalias(tp)
lbl, exists := tw.Labeler.TypeLabels[tp] lbl, exists := tw.Labeler.TypeLabels[tp]
if !exists { if !exists {
switch tp := tp.(type) { switch tp := tp.(type) {

View File

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

View File

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

View File

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

View File

@@ -253,6 +253,29 @@ ql/java/ql/src/experimental/Security/CWE/CWE-665/InsecureRmiJmxEnvironmentConfig
ql/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql ql/java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql
ql/java/ql/src/experimental/Security/CWE/CWE-759/HashWithoutSalt.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/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/DuplicateAnonymous.ql
ql/java/ql/src/external/DuplicateBlock.ql ql/java/ql/src/external/DuplicateBlock.ql
ql/java/ql/src/external/DuplicateMethod.ql ql/java/ql/src/external/DuplicateMethod.ql

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,8 @@ module ShellJS {
shellJSMember() shellJSMember()
.getMember([ .getMember([
"exec", "cd", "cp", "touch", "chmod", "pushd", "find", "ls", "ln", "mkdir", "mv", "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() .getReturn()
} }
@@ -99,7 +100,8 @@ module ShellJS {
*/ */
private class ShellJSGenericFileAccess extends FileSystemAccess, ShellJSCall { private class ShellJSGenericFileAccess extends FileSystemAccess, ShellJSCall {
ShellJSGenericFileAccess() { 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() } override DataFlow::Node getAPathArgument() { result = this.getAnArgument() }
@@ -111,7 +113,8 @@ module ShellJS {
private class ShellJSFilenameSource extends FileNameSource, ShellJSCall { private class ShellJSFilenameSource extends FileNameSource, ShellJSCall {
ShellJSFilenameSource() { ShellJSFilenameSource() {
name = "find" or 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 { 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 isShellInterpreted(DataFlow::Node arg) { arg = this.getACommandArgument() }
override predicate isSync() { none() } override predicate isSync() { name = "cmd" }
override DataFlow::Node getOptionsArg() { override DataFlow::Node getOptionsArg() {
result = this.getLastArgument() and result = this.getLastArgument() and

View File

@@ -19,6 +19,7 @@
private import javascript private import javascript
private import internal.ApiGraphModels as Shared private import internal.ApiGraphModels as Shared
private import internal.ApiGraphModelsSpecific as Specific private import internal.ApiGraphModelsSpecific as Specific
private import semmle.javascript.dataflow.internal.FlowSummaryPrivate
private import semmle.javascript.endpoints.EndpointNaming as EndpointNaming private import semmle.javascript.endpoints.EndpointNaming as EndpointNaming
import Shared::ModelInput as ModelInput import Shared::ModelInput as ModelInput
import Shared::ModelOutput as ModelOutput 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. * Like `ModelOutput::summaryStep` but with API nodes mapped to data-flow nodes.
*/ */
private predicate summaryStepNodes(DataFlow::Node pred, DataFlow::Node succ, string kind) { private predicate summaryStepNodes(DataFlow::Node pred, DataFlow::Node succ, string kind) {
exists(API::Node predNode, API::Node succNode | exists(API::Node predNode, API::Node succNode |
Specific::summaryStep(predNode, succNode, kind) and summaryStep(predNode, succNode, kind) and
pred = predNode.asSink() and pred = predNode.asSink() and
succ = succNode.asSource() succ = succNode.asSource()
) )

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -55,14 +55,19 @@ test_FileSystemAccess
| tst.js:60:1:60:17 | shelljs.cat(file) | | tst.js:60:1:60:17 | shelljs.cat(file) |
| tst.js:60:1:60:41 | shelljs ... cement) | | tst.js:60:1:60:41 | shelljs ... cement) |
| tst.js:61:1:61:17 | shelljs.cat(file) | | tst.js:61:1:61:17 | shelljs.cat(file) |
| tst.js:65:1:65:19 | shelljs.which(file) |
test_MissingFileSystemAccess test_MissingFileSystemAccess
test_SystemCommandExecution test_SystemCommandExecution
| tst.js:14:1:14:27 | shelljs ... ts, cb) | | tst.js:14:1:14:27 | shelljs ... ts, cb) |
| tst.js:60:1:60:51 | shelljs ... ec(cmd) | | tst.js:60:1:60:51 | shelljs ... ec(cmd) |
| tst.js:61:1:61:27 | 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 test_FileNameSource
| tst.js:15:1:15:26 | shelljs ... file2) | | tst.js:15:1:15:26 | shelljs ... file2) |
| tst.js:24:1:24:16 | shelljs.ls(file) | | tst.js:24:1:24:16 | shelljs.ls(file) |
| tst.js:25:1:25:22 | shelljs ... , file) | | tst.js:25:1:25:22 | shelljs ... , file) |
| tst.js:26:1:26:30 | shelljs ... file2) | | tst.js:26:1:26:30 | shelljs ... file2) |
| tst.js:27:1:27:24 | shelljs ... file2) | | tst.js:27:1:27:24 | shelljs ... file2) |
| tst.js:65:1:65:19 | shelljs.which(file) |

View File

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

View File

@@ -1,4 +1,21 @@
legacyDataFlowDifference 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 consistencyIssue
taintFlow taintFlow
| guardedRouteHandler.js:6:10:6:28 | req.injectedReqData | guardedRouteHandler.js:6:10:6:28 | req.injectedReqData | | guardedRouteHandler.js:6:10:6:28 | req.injectedReqData | guardedRouteHandler.js:6:10:6:28 | req.injectedReqData |

View File

@@ -2,6 +2,9 @@
| actions.js:4:6:4:29 | process ... _DATA'] | actions.js:4:6:4:16 | process.env | actions.js:4:6:4:29 | process ... _DATA'] | This command depends on an unsanitized $@. | actions.js:4:6:4:16 | process.env | environment variable | | actions.js: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: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: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: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: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 | | 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: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: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: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: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: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 | | | 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: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:12:6:12:16 | process.env | semmle.label | process.env |
| actions.js:14:6:14:21 | getInput('data') | semmle.label | getInput('data') | | 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: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: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 | | command-line-parameter-command-injection.js:8:22:8:33 | process.argv | semmle.label | process.argv |

View File

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

View File

@@ -78,7 +78,9 @@ edges
| ReflectedXss.js:22:19:22:26 | req.body | ReflectedXss.js:22:12:22:27 | marked(req.body) | provenance | | | ReflectedXss.js: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: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: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: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: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 | | | 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:28:12:28:19 | req.body | semmle.label | req.body |
| ReflectedXss.js:29:7:32:4 | mytable | semmle.label | mytable | | 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: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:31:14:31:21 | req.body | semmle.label | req.body |
| ReflectedXss.js:33:12:33:18 | mytable | semmle.label | mytable | | ReflectedXss.js:33:12:33:18 | mytable | semmle.label | mytable |
| ReflectedXss.js:40:12:40:19 | req.body | semmle.label | req.body | | ReflectedXss.js:40:12:40:19 | req.body | semmle.label | req.body |

View File

@@ -1,10 +1,8 @@
#!/usr/bin/python3 #!/usr/bin/python3
import helpers import helpers
import json
import os import os
import os.path import os.path
import shlex
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
@@ -27,24 +25,13 @@ def parseData(data):
return rows 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():
def printHelp(self): print(f"""Usage:
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]
python3 GenerateFlowModel.py <library-database> [DIR] [--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. 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: Which models are generated is controlled by the flags:
--with-sinks --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. --dry-run: Only run the queries, but don't write to file.
Example invocations: Example invocations:
$ python3 GenerateFlowModel.py /tmp/dbs/my_library_db $ python3 generate_mad.py /tmp/dbs/my_library_db
$ python3 GenerateFlowModel.py /tmp/dbs/my_library_db --with-sinks $ python3 generate_mad.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 --with-sinks my_directory
Requirements: `codeql` should appear on your path. 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): def setenvironment(self, database, folder):
self.codeQlRoot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip() 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 @staticmethod
def make(language): def make():
generator = Generator(language) # Create a generator instance based on command line arguments.
if any(s == "--help" for s in sys.argv): if any(s == "--help" for s in sys.argv):
generator.printHelp() printHelp()
sys.exit(0) 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: if "--with-sinks" in sys.argv:
sys.argv.remove("--with-sinks") sys.argv.remove("--with-sinks")
generator.generateSinks = True generator.generateSinks = True
@@ -115,7 +123,7 @@ Requirements: `codeql` should appear on your path.
n = len(sys.argv) n = len(sys.argv)
if n < 2: if n < 2:
generator.printHelp() printHelp()
sys.exit(1) sys.exit(1)
elif n == 2: elif n == 2:
generator.setenvironment(sys.argv[1], "") generator.setenvironment(sys.argv[1], "")
@@ -204,3 +212,6 @@ extensions:
if self.generateTypeBasedSummaries: if self.generateTypeBasedSummaries:
self.save(typeBasedContent, ".typebased.model.yml") self.save(typeBasedContent, ".typebased.model.yml")
if __name__ == '__main__':
Generator.make().run()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -182,7 +182,6 @@ named_crates(
| @label | @label
| @let_else | @let_else
| @macro_items | @macro_items
| @macro_stmts
| @match_arm | @match_arm
| @match_arm_list | @match_arm_list
| @match_guard | @match_guard
@@ -438,6 +437,7 @@ closure_binder_generic_param_lists(
| @labelable_expr | @labelable_expr
| @let_expr | @let_expr
| @literal_expr | @literal_expr
| @macro_block_expr
| @macro_expr | @macro_expr
| @match_expr | @match_expr
| @offset_of_expr | @offset_of_expr
@@ -585,23 +585,6 @@ macro_items_items(
int item: @item ref 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( match_arms(
unique int id: @match_arm unique int id: @match_arm
); );
@@ -2104,6 +2087,23 @@ literal_pat_literals(
int literal: @literal_expr ref 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( macro_exprs(
unique int id: @macro_expr unique int id: @macro_expr
); );

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