mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Merge branch 'main' into redsun82/rust-expand-attr-macros
This commit is contained in:
2
.github/workflows/mad_modelDiff.yml
vendored
2
.github/workflows/mad_modelDiff.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
|||||||
DATABASE=$2
|
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 ..
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
113
cpp/ql/lib/experimental/quantum/Language.qll
Normal file
113
cpp/ql/lib/experimental/quantum/Language.qll
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
private import cpp as Language
|
||||||
|
import semmle.code.cpp.dataflow.new.DataFlow
|
||||||
|
import codeql.quantum.experimental.Model
|
||||||
|
|
||||||
|
module CryptoInput implements InputSig<Language::Location> {
|
||||||
|
class DataFlowNode = DataFlow::Node;
|
||||||
|
|
||||||
|
class LocatableElement = Language::Locatable;
|
||||||
|
|
||||||
|
class UnknownLocation = Language::UnknownDefaultLocation;
|
||||||
|
|
||||||
|
LocatableElement dfn_to_element(DataFlow::Node node) {
|
||||||
|
result = node.asExpr() or
|
||||||
|
result = node.asParameter() or
|
||||||
|
result = node.asVariable()
|
||||||
|
}
|
||||||
|
|
||||||
|
string locationToFileBaseNameAndLineNumberString(Location location) {
|
||||||
|
result = location.getFile().getBaseName() + ":" + location.getStartLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate artifactOutputFlowsToGenericInput(
|
||||||
|
DataFlow::Node artifactOutput, DataFlow::Node otherInput
|
||||||
|
) {
|
||||||
|
ArtifactFlow::flow(artifactOutput, otherInput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module Crypto = CryptographyBase<Language::Location, CryptoInput>;
|
||||||
|
|
||||||
|
module ArtifactFlowConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) {
|
||||||
|
source = any(Crypto::ArtifactInstance artifact).getOutputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) {
|
||||||
|
sink = any(Crypto::FlowAwareElement other).getInputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isBarrierOut(DataFlow::Node node) {
|
||||||
|
node = any(Crypto::FlowAwareElement element).getInputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isBarrierIn(DataFlow::Node node) {
|
||||||
|
node = any(Crypto::FlowAwareElement element).getOutputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||||
|
node1.(AdditionalFlowInputStep).getOutput() = node2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module ArtifactFlow = DataFlow::Global<ArtifactFlowConfig>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Artifact output to node input configuration
|
||||||
|
*/
|
||||||
|
abstract class AdditionalFlowInputStep extends DataFlow::Node {
|
||||||
|
abstract DataFlow::Node getOutput();
|
||||||
|
|
||||||
|
final DataFlow::Node getInput() { result = this }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic data source to node input configuration
|
||||||
|
*/
|
||||||
|
module GenericDataSourceFlowConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) {
|
||||||
|
source = any(Crypto::GenericSourceInstance i).getOutputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) {
|
||||||
|
sink = any(Crypto::FlowAwareElement other).getInputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isBarrierOut(DataFlow::Node node) {
|
||||||
|
node = any(Crypto::FlowAwareElement element).getInputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isBarrierIn(DataFlow::Node node) {
|
||||||
|
node = any(Crypto::FlowAwareElement element).getOutputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||||
|
node1.(AdditionalFlowInputStep).getOutput() = node2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module ArtifactUniversalFlowConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) {
|
||||||
|
source = any(Crypto::ArtifactInstance artifact).getOutputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) {
|
||||||
|
sink = any(Crypto::FlowAwareElement other).getInputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isBarrierOut(DataFlow::Node node) {
|
||||||
|
node = any(Crypto::FlowAwareElement element).getInputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isBarrierIn(DataFlow::Node node) {
|
||||||
|
node = any(Crypto::FlowAwareElement element).getOutputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||||
|
node1.(AdditionalFlowInputStep).getOutput() = node2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module ArtifactUniversalFlow = DataFlow::Global<ArtifactUniversalFlowConfig>;
|
||||||
|
|
||||||
|
import OpenSSL.OpenSSL
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import cpp
|
||||||
|
import semmle.code.cpp.dataflow.new.DataFlow
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmConstants
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traces 'known algorithms' to AVCs, specifically
|
||||||
|
* algorithms that are in the set of known algorithm constants.
|
||||||
|
* Padding-specific consumers exist that have their own values that
|
||||||
|
* overlap with the known algorithm constants.
|
||||||
|
* Padding consumers (specific padding consumers) are excluded from the set of sinks.
|
||||||
|
*/
|
||||||
|
module KnownOpenSSLAlgorithmToAlgorithmValueConsumerConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) {
|
||||||
|
source.asExpr() instanceof KnownOpenSSLAlgorithmConstant
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) {
|
||||||
|
exists(OpenSSLAlgorithmValueConsumer c |
|
||||||
|
c.getInputNode() = sink and
|
||||||
|
not c instanceof PaddingAlgorithmValueConsumer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isBarrier(DataFlow::Node node) {
|
||||||
|
// False positive reducer, don't flow out through argv
|
||||||
|
exists(VariableAccess va, Variable v |
|
||||||
|
v.getAnAccess() = va and va = node.asExpr()
|
||||||
|
or
|
||||||
|
va = node.asIndirectExpr()
|
||||||
|
|
|
||||||
|
v.getName().matches("%argv")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||||
|
knownPassThroughStep(node1, node2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module KnownOpenSSLAlgorithmToAlgorithmValueConsumerFlow =
|
||||||
|
DataFlow::Global<KnownOpenSSLAlgorithmToAlgorithmValueConsumerConfig>;
|
||||||
|
|
||||||
|
module RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) {
|
||||||
|
source.asExpr() instanceof KnownOpenSSLAlgorithmConstant
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) {
|
||||||
|
exists(PaddingAlgorithmValueConsumer c | c.getInputNode() = sink)
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||||
|
knownPassThroughStep(node1, node2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerFlow =
|
||||||
|
DataFlow::Global<RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerConfig>;
|
||||||
|
|
||||||
|
class OpenSSLAlgorithmAdditionalFlowStep extends AdditionalFlowInputStep {
|
||||||
|
OpenSSLAlgorithmAdditionalFlowStep() { exists(AlgorithmPassthroughCall c | c.getInNode() = this) }
|
||||||
|
|
||||||
|
override DataFlow::Node getOutput() {
|
||||||
|
exists(AlgorithmPassthroughCall c | c.getInNode() = this and c.getOutNode() = result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class AlgorithmPassthroughCall extends Call {
|
||||||
|
abstract DataFlow::Node getInNode();
|
||||||
|
|
||||||
|
abstract DataFlow::Node getOutNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
class CopyAndDupAlgorithmPassthroughCall extends AlgorithmPassthroughCall {
|
||||||
|
DataFlow::Node inNode;
|
||||||
|
DataFlow::Node outNode;
|
||||||
|
|
||||||
|
CopyAndDupAlgorithmPassthroughCall() {
|
||||||
|
// Flow out through any return or other argument of the same type
|
||||||
|
// Assume flow in and out is asIndirectExpr or asDefinitingArgument since a pointer is assumed
|
||||||
|
// to be involved
|
||||||
|
// NOTE: not attempting to detect openssl specific copy/dup functions, but anything suspected to be copy/dup
|
||||||
|
this.getTarget().getName().toLowerCase().matches(["%_dup%", "%_copy%"]) and
|
||||||
|
exists(Expr inArg, Type t |
|
||||||
|
inArg = this.getAnArgument() and t = inArg.getUnspecifiedType().stripType()
|
||||||
|
|
|
||||||
|
inNode.asIndirectExpr() = inArg and
|
||||||
|
(
|
||||||
|
// Case 1: flow through another argument as an out arg of the same type
|
||||||
|
exists(Expr outArg |
|
||||||
|
outArg = this.getAnArgument() and
|
||||||
|
outArg != inArg and
|
||||||
|
outArg.getUnspecifiedType().stripType() = t
|
||||||
|
|
|
||||||
|
outNode.asDefiningArgument() = outArg
|
||||||
|
)
|
||||||
|
or
|
||||||
|
// Case 2: flow through the return value if the result is the same as the intput type
|
||||||
|
exists(Expr outArg | outArg = this and outArg.getUnspecifiedType().stripType() = t |
|
||||||
|
outNode.asIndirectExpr() = outArg
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getInNode() { result = inNode }
|
||||||
|
|
||||||
|
override DataFlow::Node getOutNode() { result = outNode }
|
||||||
|
}
|
||||||
|
|
||||||
|
class NIDToPointerPassthroughCall extends AlgorithmPassthroughCall {
|
||||||
|
DataFlow::Node inNode;
|
||||||
|
DataFlow::Node outNode;
|
||||||
|
|
||||||
|
NIDToPointerPassthroughCall() {
|
||||||
|
this.getTarget().getName() in ["OBJ_nid2obj", "OBJ_nid2ln", "OBJ_nid2sn"] and
|
||||||
|
inNode.asExpr() = this.getArgument(0) and
|
||||||
|
outNode.asExpr() = this
|
||||||
|
//outNode.asIndirectExpr() = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getInNode() { result = inNode }
|
||||||
|
|
||||||
|
override DataFlow::Node getOutNode() { result = outNode }
|
||||||
|
}
|
||||||
|
|
||||||
|
class PointerToPointerPassthroughCall extends AlgorithmPassthroughCall {
|
||||||
|
DataFlow::Node inNode;
|
||||||
|
DataFlow::Node outNode;
|
||||||
|
|
||||||
|
PointerToPointerPassthroughCall() {
|
||||||
|
this.getTarget().getName() = "OBJ_txt2obj" and
|
||||||
|
inNode.asIndirectExpr() = this.getArgument(0) and
|
||||||
|
outNode.asIndirectExpr() = this
|
||||||
|
or
|
||||||
|
//outNode.asExpr() = this
|
||||||
|
this.getTarget().getName() in ["OBJ_obj2txt", "i2t_ASN1_OBJECT"] and
|
||||||
|
inNode.asIndirectExpr() = this.getArgument(2) and
|
||||||
|
outNode.asDefiningArgument() = this.getArgument(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getInNode() { result = inNode }
|
||||||
|
|
||||||
|
override DataFlow::Node getOutNode() { result = outNode }
|
||||||
|
}
|
||||||
|
|
||||||
|
class PointerToNIDPassthroughCall extends AlgorithmPassthroughCall {
|
||||||
|
DataFlow::Node inNode;
|
||||||
|
DataFlow::Node outNode;
|
||||||
|
|
||||||
|
PointerToNIDPassthroughCall() {
|
||||||
|
this.getTarget().getName() in ["OBJ_obj2nid", "OBJ_ln2nid", "OBJ_sn2nid", "OBJ_txt2nid"] and
|
||||||
|
(
|
||||||
|
inNode.asIndirectExpr() = this.getArgument(0)
|
||||||
|
or
|
||||||
|
inNode.asExpr() = this.getArgument(0)
|
||||||
|
) and
|
||||||
|
outNode.asExpr() = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getInNode() { result = inNode }
|
||||||
|
|
||||||
|
override DataFlow::Node getOutNode() { result = outNode }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: pkeys pass through EVP_PKEY_CTX_new and any similar variant
|
||||||
|
predicate knownPassThroughStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||||
|
exists(AlgorithmPassthroughCall c | c.getInNode() = node1 and c.getOutNode() = node2)
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import cpp
|
||||||
|
import experimental.quantum.Language
|
||||||
|
import OpenSSLAlgorithmInstanceBase
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmConstants
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.DirectAlgorithmValueConsumer
|
||||||
|
import AlgToAVCFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a `KnownOpenSSLBlockModeAlgorithmConstant`, converts this to a block family type.
|
||||||
|
* Does not bind if there is know mapping (no mapping to 'unknown' or 'other').
|
||||||
|
*/
|
||||||
|
predicate knownOpenSSLConstantToBlockModeFamilyType(
|
||||||
|
KnownOpenSSLBlockModeAlgorithmConstant e, Crypto::TBlockCipherModeOfOperationType type
|
||||||
|
) {
|
||||||
|
exists(string name |
|
||||||
|
name = e.getNormalizedName() and
|
||||||
|
(
|
||||||
|
name.matches("CBC") and type instanceof Crypto::CBC
|
||||||
|
or
|
||||||
|
name.matches("CFB%") and type instanceof Crypto::CFB
|
||||||
|
or
|
||||||
|
name.matches("CTR") and type instanceof Crypto::CTR
|
||||||
|
or
|
||||||
|
name.matches("GCM") and type instanceof Crypto::GCM
|
||||||
|
or
|
||||||
|
name.matches("OFB") and type instanceof Crypto::OFB
|
||||||
|
or
|
||||||
|
name.matches("XTS") and type instanceof Crypto::XTS
|
||||||
|
or
|
||||||
|
name.matches("CCM") and type instanceof Crypto::CCM
|
||||||
|
or
|
||||||
|
name.matches("GCM") and type instanceof Crypto::GCM
|
||||||
|
or
|
||||||
|
name.matches("CCM") and type instanceof Crypto::CCM
|
||||||
|
or
|
||||||
|
name.matches("ECB") and type instanceof Crypto::ECB
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class KnownOpenSSLBlockModeConstantAlgorithmInstance extends OpenSSLAlgorithmInstance,
|
||||||
|
Crypto::ModeOfOperationAlgorithmInstance instanceof KnownOpenSSLBlockModeAlgorithmConstant
|
||||||
|
{
|
||||||
|
OpenSSLAlgorithmValueConsumer getterCall;
|
||||||
|
|
||||||
|
KnownOpenSSLBlockModeConstantAlgorithmInstance() {
|
||||||
|
// Two possibilities:
|
||||||
|
// 1) The source is a literal and flows to a getter, then we know we have an instance
|
||||||
|
// 2) The source is a KnownOpenSSLAlgorithm is call, and we know we have an instance immediately from that
|
||||||
|
// Possibility 1:
|
||||||
|
this instanceof Literal and
|
||||||
|
exists(DataFlow::Node src, DataFlow::Node sink |
|
||||||
|
// Sink is an argument to a CipherGetterCall
|
||||||
|
sink = getterCall.(OpenSSLAlgorithmValueConsumer).getInputNode() and
|
||||||
|
// Source is `this`
|
||||||
|
src.asExpr() = this and
|
||||||
|
// This traces to a getter
|
||||||
|
KnownOpenSSLAlgorithmToAlgorithmValueConsumerFlow::flow(src, sink)
|
||||||
|
)
|
||||||
|
or
|
||||||
|
// Possibility 2:
|
||||||
|
this instanceof DirectAlgorithmValueConsumer and getterCall = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override Crypto::TBlockCipherModeOfOperationType getModeType() {
|
||||||
|
knownOpenSSLConstantToBlockModeFamilyType(this, result)
|
||||||
|
or
|
||||||
|
not knownOpenSSLConstantToBlockModeFamilyType(this, _) and result = Crypto::OtherMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: I'm not going to attempt to parse out the mode specific part, so returning
|
||||||
|
// the same as the raw name for now.
|
||||||
|
override string getRawModeAlgorithmName() { result = this.(Literal).getValue().toString() }
|
||||||
|
|
||||||
|
override OpenSSLAlgorithmValueConsumer getAVC() { result = getterCall }
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import cpp
|
||||||
|
import experimental.quantum.Language
|
||||||
|
import KnownAlgorithmConstants
|
||||||
|
import Crypto::KeyOpAlg as KeyOpAlg
|
||||||
|
import OpenSSLAlgorithmInstanceBase
|
||||||
|
import PaddingAlgorithmInstance
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
||||||
|
import AlgToAVCFlow
|
||||||
|
import BlockAlgorithmInstance
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a `KnownOpenSSLCipherAlgorithmConstant`, converts this to a cipher family type.
|
||||||
|
* Does not bind if there is know mapping (no mapping to 'unknown' or 'other').
|
||||||
|
*/
|
||||||
|
predicate knownOpenSSLConstantToCipherFamilyType(
|
||||||
|
KnownOpenSSLCipherAlgorithmConstant e, Crypto::KeyOpAlg::TAlgorithm type
|
||||||
|
) {
|
||||||
|
exists(string name |
|
||||||
|
name = e.getNormalizedName() and
|
||||||
|
(
|
||||||
|
name.matches("AES%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::AES())
|
||||||
|
or
|
||||||
|
name.matches("ARIA%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::ARIA())
|
||||||
|
or
|
||||||
|
name.matches("BLOWFISH%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::BLOWFISH())
|
||||||
|
or
|
||||||
|
name.matches("BF%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::BLOWFISH())
|
||||||
|
or
|
||||||
|
name.matches("CAMELLIA%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::CAMELLIA())
|
||||||
|
or
|
||||||
|
name.matches("CHACHA20%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::CHACHA20())
|
||||||
|
or
|
||||||
|
name.matches("CAST5%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::CAST5())
|
||||||
|
or
|
||||||
|
name.matches("2DES%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::DoubleDES())
|
||||||
|
or
|
||||||
|
name.matches("3DES%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::TripleDES())
|
||||||
|
or
|
||||||
|
name.matches("DES%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::DES())
|
||||||
|
or
|
||||||
|
name.matches("DESX%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::DESX())
|
||||||
|
or
|
||||||
|
name.matches("GOST%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::GOST())
|
||||||
|
or
|
||||||
|
name.matches("IDEA%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::IDEA())
|
||||||
|
or
|
||||||
|
name.matches("KUZNYECHIK%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::KUZNYECHIK())
|
||||||
|
or
|
||||||
|
name.matches("MAGMA%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::MAGMA())
|
||||||
|
or
|
||||||
|
name.matches("RC2%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::RC2())
|
||||||
|
or
|
||||||
|
name.matches("RC4%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::RC4())
|
||||||
|
or
|
||||||
|
name.matches("RC5%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::RC5())
|
||||||
|
or
|
||||||
|
name.matches("RSA%") and type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA())
|
||||||
|
or
|
||||||
|
name.matches("SEED%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::SEED())
|
||||||
|
or
|
||||||
|
name.matches("SM4%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::SM4())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class KnownOpenSSLCipherConstantAlgorithmInstance extends OpenSSLAlgorithmInstance,
|
||||||
|
Crypto::KeyOperationAlgorithmInstance instanceof KnownOpenSSLCipherAlgorithmConstant
|
||||||
|
{
|
||||||
|
OpenSSLAlgorithmValueConsumer getterCall;
|
||||||
|
|
||||||
|
KnownOpenSSLCipherConstantAlgorithmInstance() {
|
||||||
|
// Two possibilities:
|
||||||
|
// 1) The source is a literal and flows to a getter, then we know we have an instance
|
||||||
|
// 2) The source is a KnownOpenSSLAlgorithm is call, and we know we have an instance immediately from that
|
||||||
|
// Possibility 1:
|
||||||
|
this instanceof Literal and
|
||||||
|
exists(DataFlow::Node src, DataFlow::Node sink |
|
||||||
|
// Sink is an argument to a CipherGetterCall
|
||||||
|
sink = getterCall.(OpenSSLAlgorithmValueConsumer).getInputNode() and
|
||||||
|
// Source is `this`
|
||||||
|
src.asExpr() = this and
|
||||||
|
// This traces to a getter
|
||||||
|
KnownOpenSSLAlgorithmToAlgorithmValueConsumerFlow::flow(src, sink)
|
||||||
|
)
|
||||||
|
or
|
||||||
|
// Possibility 2:
|
||||||
|
this instanceof DirectAlgorithmValueConsumer and getterCall = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() {
|
||||||
|
// if there is a block mode associated with the same element, then that's the block mode
|
||||||
|
// note, if none are associated, we may need to parse if the cipher is a block cipher
|
||||||
|
// to determine if this is an unknown vs not relevant.
|
||||||
|
result = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() {
|
||||||
|
//TODO: the padding is either self, or it flows through getter ctx to a set padding call
|
||||||
|
// like EVP_PKEY_CTX_set_rsa_padding
|
||||||
|
result = this
|
||||||
|
// TODO or trace through getter ctx to set padding
|
||||||
|
}
|
||||||
|
|
||||||
|
override string getRawAlgorithmName() { result = this.(Literal).getValue().toString() }
|
||||||
|
|
||||||
|
override string getKeySizeFixed() {
|
||||||
|
exists(int keySize |
|
||||||
|
this.(KnownOpenSSLCipherAlgorithmConstant).getExplicitKeySize() = keySize and
|
||||||
|
result = keySize.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override Crypto::KeyOpAlg::Algorithm getAlgorithmType() {
|
||||||
|
knownOpenSSLConstantToCipherFamilyType(this, result)
|
||||||
|
or
|
||||||
|
not knownOpenSSLConstantToCipherFamilyType(this, _) and
|
||||||
|
result = Crypto::KeyOpAlg::TUnknownKeyOperationAlgorithmType()
|
||||||
|
}
|
||||||
|
|
||||||
|
override OpenSSLAlgorithmValueConsumer getAVC() { result = getterCall }
|
||||||
|
|
||||||
|
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
|
||||||
|
// TODO: trace to any key size initializer, symmetric and asymmetric
|
||||||
|
none()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import cpp
|
||||||
|
import experimental.quantum.Language
|
||||||
|
import KnownAlgorithmConstants
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
||||||
|
import AlgToAVCFlow
|
||||||
|
|
||||||
|
predicate knownOpenSSLConstantToHashFamilyType(
|
||||||
|
KnownOpenSSLHashAlgorithmConstant e, Crypto::THashType type
|
||||||
|
) {
|
||||||
|
exists(string name |
|
||||||
|
name = e.getNormalizedName() and
|
||||||
|
(
|
||||||
|
name.matches("BLAKE2B") and type instanceof Crypto::BLAKE2B
|
||||||
|
or
|
||||||
|
name.matches("BLAKE2S") and type instanceof Crypto::BLAKE2S
|
||||||
|
or
|
||||||
|
name.matches("GOST%") and type instanceof Crypto::GOSTHash
|
||||||
|
or
|
||||||
|
name.matches("MD2") and type instanceof Crypto::MD2
|
||||||
|
or
|
||||||
|
name.matches("MD4") and type instanceof Crypto::MD4
|
||||||
|
or
|
||||||
|
name.matches("MD5") and type instanceof Crypto::MD5
|
||||||
|
or
|
||||||
|
name.matches("MDC2") and type instanceof Crypto::MDC2
|
||||||
|
or
|
||||||
|
name.matches("POLY1305") and type instanceof Crypto::POLY1305
|
||||||
|
or
|
||||||
|
name.matches(["SHA", "SHA1"]) and type instanceof Crypto::SHA1
|
||||||
|
or
|
||||||
|
name.matches("SHA+%") and not name.matches(["SHA1", "SHA3-"]) and type instanceof Crypto::SHA2
|
||||||
|
or
|
||||||
|
name.matches("SHA3-%") and type instanceof Crypto::SHA3
|
||||||
|
or
|
||||||
|
name.matches(["SHAKE"]) and type instanceof Crypto::SHAKE
|
||||||
|
or
|
||||||
|
name.matches("SM3") and type instanceof Crypto::SM3
|
||||||
|
or
|
||||||
|
name.matches("RIPEMD160") and type instanceof Crypto::RIPEMD160
|
||||||
|
or
|
||||||
|
name.matches("WHIRLPOOL") and type instanceof Crypto::WHIRLPOOL
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class KnownOpenSSLHashConstantAlgorithmInstance extends OpenSSLAlgorithmInstance,
|
||||||
|
Crypto::HashAlgorithmInstance instanceof KnownOpenSSLHashAlgorithmConstant
|
||||||
|
{
|
||||||
|
OpenSSLAlgorithmValueConsumer getterCall;
|
||||||
|
|
||||||
|
KnownOpenSSLHashConstantAlgorithmInstance() {
|
||||||
|
// Two possibilities:
|
||||||
|
// 1) The source is a literal and flows to a getter, then we know we have an instance
|
||||||
|
// 2) The source is a KnownOpenSSLAlgorithm is call, and we know we have an instance immediately from that
|
||||||
|
// Possibility 1:
|
||||||
|
this instanceof Literal and
|
||||||
|
exists(DataFlow::Node src, DataFlow::Node sink |
|
||||||
|
// Sink is an argument to a CipherGetterCall
|
||||||
|
sink = getterCall.(OpenSSLAlgorithmValueConsumer).getInputNode() and
|
||||||
|
// Source is `this`
|
||||||
|
src.asExpr() = this and
|
||||||
|
// This traces to a getter
|
||||||
|
KnownOpenSSLAlgorithmToAlgorithmValueConsumerFlow::flow(src, sink)
|
||||||
|
)
|
||||||
|
or
|
||||||
|
// Possibility 2:
|
||||||
|
this instanceof DirectAlgorithmValueConsumer and getterCall = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override OpenSSLAlgorithmValueConsumer getAVC() { result = getterCall }
|
||||||
|
|
||||||
|
override Crypto::THashType getHashFamily() {
|
||||||
|
knownOpenSSLConstantToHashFamilyType(this, result)
|
||||||
|
or
|
||||||
|
not knownOpenSSLConstantToHashFamilyType(this, _) and result = Crypto::OtherHashType()
|
||||||
|
}
|
||||||
|
|
||||||
|
override string getRawHashAlgorithmName() { result = this.(Literal).getValue().toString() }
|
||||||
|
|
||||||
|
override int getFixedDigestLength() {
|
||||||
|
this.(KnownOpenSSLHashAlgorithmConstant).getExplicitDigestLength() = result
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
|||||||
|
import experimental.quantum.Language
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumerBase
|
||||||
|
|
||||||
|
abstract class OpenSSLAlgorithmInstance extends Crypto::AlgorithmInstance {
|
||||||
|
abstract OpenSSLAlgorithmValueConsumer getAVC();
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import OpenSSLAlgorithmInstanceBase
|
||||||
|
import CipherAlgorithmInstance
|
||||||
|
import PaddingAlgorithmInstance
|
||||||
|
import BlockAlgorithmInstance
|
||||||
|
import HashAlgorithmInstance
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import cpp
|
||||||
|
import experimental.quantum.Language
|
||||||
|
import OpenSSLAlgorithmInstanceBase
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmConstants
|
||||||
|
import AlgToAVCFlow
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.DirectAlgorithmValueConsumer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a `KnownOpenSSLPaddingAlgorithmConstant`, converts this to a padding family type.
|
||||||
|
* Does not bind if there is know mapping (no mapping to 'unknown' or 'other').
|
||||||
|
*/
|
||||||
|
predicate knownOpenSSLConstantToPaddingFamilyType(
|
||||||
|
KnownOpenSSLPaddingAlgorithmConstant e, Crypto::TPaddingType type
|
||||||
|
) {
|
||||||
|
exists(string name |
|
||||||
|
name = e.getNormalizedName() and
|
||||||
|
(
|
||||||
|
name.matches("OAEP") and type = Crypto::OAEP()
|
||||||
|
or
|
||||||
|
name.matches("PSS") and type = Crypto::PSS()
|
||||||
|
or
|
||||||
|
name.matches("PKCS7") and type = Crypto::PKCS7()
|
||||||
|
or
|
||||||
|
name.matches("PKCS1V15") and type = Crypto::PKCS1_v1_5()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//abstract class OpenSSLPaddingAlgorithmInstance extends OpenSSLAlgorithmInstance, Crypto::PaddingAlgorithmInstance{}
|
||||||
|
// TODO: need to alter this to include known padding constants which don't have the
|
||||||
|
// same mechanics as those with known nids
|
||||||
|
class KnownOpenSSLPaddingConstantAlgorithmInstance extends OpenSSLAlgorithmInstance,
|
||||||
|
Crypto::PaddingAlgorithmInstance instanceof Expr
|
||||||
|
{
|
||||||
|
OpenSSLAlgorithmValueConsumer getterCall;
|
||||||
|
boolean isPaddingSpecificConsumer;
|
||||||
|
|
||||||
|
KnownOpenSSLPaddingConstantAlgorithmInstance() {
|
||||||
|
// three possibilities:
|
||||||
|
// 1) The source is a 'typical' literal and flows to a getter, then we know we have an instance
|
||||||
|
// 2) The source is a KnownOpenSSLAlgorithm is call, and we know we have an instance immediately from that
|
||||||
|
// 3) the source is a padding-specific literal flowing to a padding-specific consumer
|
||||||
|
// Possibility 1:
|
||||||
|
this instanceof Literal and
|
||||||
|
this instanceof KnownOpenSSLPaddingAlgorithmConstant and
|
||||||
|
exists(DataFlow::Node src, DataFlow::Node sink |
|
||||||
|
// Sink is an argument to a CipherGetterCall
|
||||||
|
sink = getterCall.(OpenSSLAlgorithmValueConsumer).getInputNode() and
|
||||||
|
// Source is `this`
|
||||||
|
src.asExpr() = this and
|
||||||
|
// This traces to a getter
|
||||||
|
KnownOpenSSLAlgorithmToAlgorithmValueConsumerFlow::flow(src, sink) and
|
||||||
|
isPaddingSpecificConsumer = false
|
||||||
|
)
|
||||||
|
or
|
||||||
|
// Possibility 2:
|
||||||
|
this instanceof DirectAlgorithmValueConsumer and
|
||||||
|
getterCall = this and
|
||||||
|
this instanceof KnownOpenSSLPaddingAlgorithmConstant and
|
||||||
|
isPaddingSpecificConsumer = false
|
||||||
|
or
|
||||||
|
// Possibility 3:
|
||||||
|
// from rsa.h in openssl:
|
||||||
|
// # define RSA_PKCS1_PADDING 1
|
||||||
|
// # define RSA_NO_PADDING 3
|
||||||
|
// # define RSA_PKCS1_OAEP_PADDING 4
|
||||||
|
// # define RSA_X931_PADDING 5
|
||||||
|
// /* EVP_PKEY_ only */
|
||||||
|
// # define RSA_PKCS1_PSS_PADDING 6
|
||||||
|
// # define RSA_PKCS1_WITH_TLS_PADDING 7
|
||||||
|
// /* internal RSA_ only */
|
||||||
|
// # define RSA_PKCS1_NO_IMPLICIT_REJECT_PADDING 8
|
||||||
|
this instanceof Literal and
|
||||||
|
this.getValue().toInt() in [0, 1, 3, 4, 5, 6, 7, 8] and
|
||||||
|
exists(DataFlow::Node src, DataFlow::Node sink |
|
||||||
|
// Sink is an argument to a CipherGetterCall
|
||||||
|
sink = getterCall.(OpenSSLAlgorithmValueConsumer).getInputNode() and
|
||||||
|
// Source is `this`
|
||||||
|
src.asExpr() = this and
|
||||||
|
// This traces to a padding-specific consumer
|
||||||
|
RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerFlow::flow(src, sink)
|
||||||
|
) and
|
||||||
|
isPaddingSpecificConsumer = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override string getRawPaddingAlgorithmName() { result = this.(Literal).getValue().toString() }
|
||||||
|
|
||||||
|
override OpenSSLAlgorithmValueConsumer getAVC() { result = getterCall }
|
||||||
|
|
||||||
|
override Crypto::TPaddingType getPaddingType() {
|
||||||
|
isPaddingSpecificConsumer = true and
|
||||||
|
(
|
||||||
|
if this.(Literal).getValue().toInt() in [1, 7, 8]
|
||||||
|
then result = Crypto::PKCS1_v1_5()
|
||||||
|
else
|
||||||
|
if this.(Literal).getValue().toInt() = 3
|
||||||
|
then result = Crypto::NoPadding()
|
||||||
|
else
|
||||||
|
if this.(Literal).getValue().toInt() = 4
|
||||||
|
then result = Crypto::OAEP()
|
||||||
|
else
|
||||||
|
if this.(Literal).getValue().toInt() = 5
|
||||||
|
then result = Crypto::ANSI_X9_23()
|
||||||
|
else
|
||||||
|
if this.(Literal).getValue().toInt() = 6
|
||||||
|
then result = Crypto::PSS()
|
||||||
|
else result = Crypto::OtherPadding()
|
||||||
|
)
|
||||||
|
or
|
||||||
|
isPaddingSpecificConsumer = false and
|
||||||
|
knownOpenSSLConstantToPaddingFamilyType(this, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Values used for EVP_PKEY_CTX_set_rsa_padding, these are
|
||||||
|
// // not the same as 'typical' constants found in the set of known algorithm constants
|
||||||
|
// // they do not have an NID
|
||||||
|
// // TODO: what about setting the padding directly?
|
||||||
|
// class KnownRSAPaddingConstant extends OpenSSLPaddingAlgorithmInstance, Crypto::PaddingAlgorithmInstance instanceof Literal
|
||||||
|
// {
|
||||||
|
// KnownRSAPaddingConstant() {
|
||||||
|
// // from rsa.h in openssl:
|
||||||
|
// // # define RSA_PKCS1_PADDING 1
|
||||||
|
// // # define RSA_NO_PADDING 3
|
||||||
|
// // # define RSA_PKCS1_OAEP_PADDING 4
|
||||||
|
// // # define RSA_X931_PADDING 5
|
||||||
|
// // /* EVP_PKEY_ only */
|
||||||
|
// // # define RSA_PKCS1_PSS_PADDING 6
|
||||||
|
// // # define RSA_PKCS1_WITH_TLS_PADDING 7
|
||||||
|
// // /* internal RSA_ only */
|
||||||
|
// // # define RSA_PKCS1_NO_IMPLICIT_REJECT_PADDING 8
|
||||||
|
// this instanceof Literal and
|
||||||
|
// this.getValue().toInt() in [0, 1, 3, 4, 5, 6, 7, 8]
|
||||||
|
// // TODO: trace to padding-specific consumers
|
||||||
|
// RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerFlow
|
||||||
|
// }
|
||||||
|
// override string getRawPaddingAlgorithmName() { result = this.(Literal).getValue().toString() }
|
||||||
|
// override Crypto::TPaddingType getPaddingType() {
|
||||||
|
// if this.(Literal).getValue().toInt() in [1, 6, 7, 8]
|
||||||
|
// then result = Crypto::PKCS1_v1_5()
|
||||||
|
// else
|
||||||
|
// if this.(Literal).getValue().toInt() = 3
|
||||||
|
// then result = Crypto::NoPadding()
|
||||||
|
// else
|
||||||
|
// if this.(Literal).getValue().toInt() = 4
|
||||||
|
// then result = Crypto::OAEP()
|
||||||
|
// else
|
||||||
|
// if this.(Literal).getValue().toInt() = 5
|
||||||
|
// then result = Crypto::ANSI_X9_23()
|
||||||
|
// else result = Crypto::OtherPadding()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
class OAEPPaddingAlgorithmInstance extends Crypto::OAEPPaddingAlgorithmInstance,
|
||||||
|
KnownOpenSSLPaddingConstantAlgorithmInstance
|
||||||
|
{
|
||||||
|
OAEPPaddingAlgorithmInstance() {
|
||||||
|
this.(Crypto::PaddingAlgorithmInstance).getPaddingType() = Crypto::OAEP()
|
||||||
|
}
|
||||||
|
|
||||||
|
override Crypto::HashAlgorithmInstance getOAEPEncodingHashAlgorithm() {
|
||||||
|
none() //TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
override Crypto::HashAlgorithmInstance getMGF1HashAlgorithm() {
|
||||||
|
none() //TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import cpp
|
||||||
|
import experimental.quantum.Language
|
||||||
|
import experimental.quantum.OpenSSL.LibraryDetector
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmConstants
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmInstances.OpenSSLAlgorithmInstanceBase
|
||||||
|
import OpenSSLAlgorithmValueConsumerBase
|
||||||
|
|
||||||
|
abstract class CipherAlgorithmValueConsumer extends OpenSSLAlgorithmValueConsumer { }
|
||||||
|
|
||||||
|
// https://www.openssl.org/docs/manmaster/man3/EVP_CIPHER_fetch.html
|
||||||
|
class EVPCipherAlgorithmValueConsumer extends CipherAlgorithmValueConsumer {
|
||||||
|
DataFlow::Node valueArgNode;
|
||||||
|
DataFlow::Node resultNode;
|
||||||
|
|
||||||
|
EVPCipherAlgorithmValueConsumer() {
|
||||||
|
resultNode.asExpr() = this and
|
||||||
|
isPossibleOpenSSLFunction(this.(Call).getTarget()) and
|
||||||
|
(
|
||||||
|
this.(Call).getTarget().getName() in [
|
||||||
|
"EVP_get_cipherbyname", "EVP_get_cipherbyobj", "EVP_get_cipherbynid"
|
||||||
|
] and
|
||||||
|
valueArgNode.asExpr() = this.(Call).getArgument(0)
|
||||||
|
or
|
||||||
|
this.(Call).getTarget().getName() in ["EVP_CIPHER_fetch", "EVP_ASYM_CIPHER_fetch"] and
|
||||||
|
valueArgNode.asExpr() = this.(Call).getArgument(1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getResultNode() { result = resultNode }
|
||||||
|
|
||||||
|
override Crypto::ConsumerInputDataFlowNode getInputNode() { result = valueArgNode }
|
||||||
|
|
||||||
|
// override DataFlow::Node getInputNode() { result = valueArgNode }
|
||||||
|
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
|
||||||
|
exists(OpenSSLAlgorithmInstance i | i.getAVC() = this and result = i)
|
||||||
|
//TODO: As a potential alternative, for OpenSSL only, add a generic source node for literals and only create flow (flowsTo) to
|
||||||
|
// OpenSSL AVCs... the unknown literal sources would have to be any literals not in the known set.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import cpp
|
||||||
|
import experimental.quantum.Language
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmConstants
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumerBase
|
||||||
|
|
||||||
|
// TODO: can self referential to itself, which is also an algorithm (Known algorithm)
|
||||||
|
/**
|
||||||
|
* Cases like EVP_MD5(),
|
||||||
|
* there is no input, rather it directly gets an algorithm
|
||||||
|
* and returns it.
|
||||||
|
*/
|
||||||
|
class DirectAlgorithmValueConsumer extends OpenSSLAlgorithmValueConsumer {
|
||||||
|
DataFlow::Node resultNode;
|
||||||
|
Expr resultExpr;
|
||||||
|
|
||||||
|
DirectAlgorithmValueConsumer() {
|
||||||
|
this instanceof KnownOpenSSLAlgorithmConstant and
|
||||||
|
this instanceof Call and
|
||||||
|
resultExpr = this and
|
||||||
|
resultNode.asExpr() = resultExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These cases take in no explicit value (the value is implicit)
|
||||||
|
*/
|
||||||
|
override Crypto::ConsumerInputDataFlowNode getInputNode() { none() }
|
||||||
|
|
||||||
|
override DataFlow::Node getResultNode() { result = resultNode }
|
||||||
|
|
||||||
|
// override DataFlow::Node getOutputNode() { result = resultNode }
|
||||||
|
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
|
||||||
|
// Note: algorithm source definitions enforces that
|
||||||
|
// this class will be a known algorithm source
|
||||||
|
result = this
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// import EVPHashInitializer
|
||||||
|
// import EVPHashOperation
|
||||||
|
// import EVPHashAlgorithmSource
|
||||||
|
import cpp
|
||||||
|
import experimental.quantum.Language
|
||||||
|
import semmle.code.cpp.dataflow.new.DataFlow
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumerBase
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmInstances.OpenSSLAlgorithmInstances
|
||||||
|
import experimental.quantum.OpenSSL.LibraryDetector
|
||||||
|
|
||||||
|
abstract class HashAlgorithmValueConsumer extends OpenSSLAlgorithmValueConsumer { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EVP_Q_Digest directly consumes algorithm constant values
|
||||||
|
*/
|
||||||
|
class EVP_Q_Digest_Algorithm_Consumer extends OpenSSLAlgorithmValueConsumer {
|
||||||
|
EVP_Q_Digest_Algorithm_Consumer() {
|
||||||
|
isPossibleOpenSSLFunction(this.(Call).getTarget()) and
|
||||||
|
this.(Call).getTarget().getName() = "EVP_Q_digest"
|
||||||
|
}
|
||||||
|
|
||||||
|
override Crypto::ConsumerInputDataFlowNode getInputNode() {
|
||||||
|
result.asExpr() = this.(Call).getArgument(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
|
||||||
|
exists(OpenSSLAlgorithmInstance i | i.getAVC() = this and result = i)
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getResultNode() {
|
||||||
|
// EVP_Q_Digest directly consumes the algorithm constant value and performs the operation, there is no
|
||||||
|
// algorithm result
|
||||||
|
none()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import experimental.quantum.Language
|
||||||
|
import semmle.code.cpp.dataflow.new.DataFlow
|
||||||
|
|
||||||
|
abstract class OpenSSLAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer instanceof Call {
|
||||||
|
/**
|
||||||
|
* Returns the node representing the resulting algorithm
|
||||||
|
*/
|
||||||
|
abstract DataFlow::Node getResultNode();
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import OpenSSLAlgorithmValueConsumerBase
|
||||||
|
import CipherAlgorithmValueConsumer
|
||||||
|
import DirectAlgorithmValueConsumer
|
||||||
|
import PaddingAlgorithmValueConsumer
|
||||||
|
import HashAlgorithmValueConsumer
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import cpp
|
||||||
|
import experimental.quantum.Language
|
||||||
|
import experimental.quantum.OpenSSL.LibraryDetector
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmConstants
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmInstances.OpenSSLAlgorithmInstanceBase
|
||||||
|
import OpenSSLAlgorithmValueConsumerBase
|
||||||
|
|
||||||
|
abstract class PaddingAlgorithmValueConsumer extends OpenSSLAlgorithmValueConsumer { }
|
||||||
|
|
||||||
|
// https://docs.openssl.org/master/man7/EVP_ASYM_CIPHER-RSA/#rsa-asymmetric-cipher-parameters
|
||||||
|
// TODO: need to handle setting padding through EVP_PKEY_CTX_set_params, where modes like "OSSL_PKEY_RSA_PAD_MODE_OAEP"
|
||||||
|
// are set.
|
||||||
|
class EVP_PKEY_CTX_set_rsa_padding_AlgorithmValueConsumer extends PaddingAlgorithmValueConsumer {
|
||||||
|
DataFlow::Node valueArgNode;
|
||||||
|
DataFlow::Node resultNode;
|
||||||
|
|
||||||
|
EVP_PKEY_CTX_set_rsa_padding_AlgorithmValueConsumer() {
|
||||||
|
resultNode.asExpr() = this and
|
||||||
|
isPossibleOpenSSLFunction(this.(Call).getTarget()) and
|
||||||
|
(
|
||||||
|
this.(Call).getTarget().getName() in ["EVP_PKEY_CTX_set_rsa_padding"] and
|
||||||
|
valueArgNode.asExpr() = this.(Call).getArgument(1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getResultNode() { result = resultNode }
|
||||||
|
|
||||||
|
override Crypto::ConsumerInputDataFlowNode getInputNode() { result = valueArgNode }
|
||||||
|
|
||||||
|
// override DataFlow::Node getInputNode() { result = valueArgNode }
|
||||||
|
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
|
||||||
|
exists(OpenSSLAlgorithmInstance i | i.getAVC() = this and result = i)
|
||||||
|
//TODO: As a potential alternative, for OpenSSL only, add a generic source node for literals and only create flow (flowsTo) to
|
||||||
|
// OpenSSL AVCs... the unknown literal sources would have to be any literals not in the known set.
|
||||||
|
}
|
||||||
|
}
|
||||||
99
cpp/ql/lib/experimental/quantum/OpenSSL/CtxFlow.qll
Normal file
99
cpp/ql/lib/experimental/quantum/OpenSSL/CtxFlow.qll
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
//TODO: model as data on open APIs should be able to get common flows, and obviate some of this
|
||||||
|
// e.g., copy/dup calls, need to ingest those models for openSSL and refactor.
|
||||||
|
/**
|
||||||
|
* In OpenSSL, flow between 'context' parameters is often used to
|
||||||
|
* store state/config of how an operation will eventually be performed.
|
||||||
|
* Tracing algorithms and configurations to operations therefore
|
||||||
|
* requires tracing context parameters for many OpenSSL apis.
|
||||||
|
*
|
||||||
|
* This library provides a dataflow analysis to track context parameters
|
||||||
|
* between any two functions accepting openssl context parameters.
|
||||||
|
* The dataflow takes into consideration flowing through duplication and copy calls
|
||||||
|
* as well as flow through flow killers (free/reset calls).
|
||||||
|
*
|
||||||
|
* TODO: we may need to revisit 'free' as a dataflow killer, depending on how
|
||||||
|
* we want to model use after frees.
|
||||||
|
*
|
||||||
|
* This library also provides classes to represent context Types and relevant
|
||||||
|
* arguments/expressions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import semmle.code.cpp.dataflow.new.DataFlow
|
||||||
|
|
||||||
|
class CTXType extends Type {
|
||||||
|
CTXType() {
|
||||||
|
// TODO: should we limit this to an openssl path?
|
||||||
|
this.getUnspecifiedType().stripType().getName().matches("evp_%ctx_%st")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CTXPointerExpr extends Expr {
|
||||||
|
CTXPointerExpr() {
|
||||||
|
this.getType() instanceof CTXType and
|
||||||
|
this.getType() instanceof PointerType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CTXPointerArgument extends CTXPointerExpr {
|
||||||
|
CTXPointerArgument() { exists(Call c | c.getAnArgument() = this) }
|
||||||
|
|
||||||
|
Call getCall() { result.getAnArgument() = this }
|
||||||
|
}
|
||||||
|
|
||||||
|
class CTXClearCall extends Call {
|
||||||
|
CTXClearCall() {
|
||||||
|
this.getTarget().getName().toLowerCase().matches(["%free%", "%reset%"]) and
|
||||||
|
this.getAnArgument() instanceof CTXPointerArgument
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CTXCopyOutArgCall extends Call {
|
||||||
|
CTXCopyOutArgCall() {
|
||||||
|
this.getTarget().getName().toLowerCase().matches(["%copy%"]) and
|
||||||
|
this.getAnArgument() instanceof CTXPointerArgument
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CTXCopyReturnCall extends Call {
|
||||||
|
CTXCopyReturnCall() {
|
||||||
|
this.getTarget().getName().toLowerCase().matches(["%dup%"]) and
|
||||||
|
this.getAnArgument() instanceof CTXPointerArgument and
|
||||||
|
this instanceof CTXPointerExpr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module OpenSSLCTXArgumentFlowConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) { source.asExpr() instanceof CTXPointerArgument }
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof CTXPointerArgument }
|
||||||
|
|
||||||
|
predicate isBarrier(DataFlow::Node node) {
|
||||||
|
exists(CTXClearCall c | c.getAnArgument() = node.asExpr())
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||||
|
exists(CTXCopyOutArgCall c |
|
||||||
|
c.getAnArgument() = node1.asExpr() and
|
||||||
|
c.getAnArgument() = node2.asExpr() and
|
||||||
|
node1.asExpr() != node2.asExpr() and
|
||||||
|
node2.asExpr().getType() instanceof CTXType
|
||||||
|
)
|
||||||
|
or
|
||||||
|
exists(CTXCopyReturnCall c |
|
||||||
|
c.getAnArgument() = node1.asExpr() and
|
||||||
|
c = node2.asExpr() and
|
||||||
|
node1.asExpr() != node2.asExpr() and
|
||||||
|
node2.asExpr().getType() instanceof CTXType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module OpenSSLCTXArgumentFlow = DataFlow::Global<OpenSSLCTXArgumentFlowConfig>;
|
||||||
|
|
||||||
|
predicate ctxArgFlowsToCtxArg(CTXPointerArgument source, CTXPointerArgument sink) {
|
||||||
|
exists(DataFlow::Node a, DataFlow::Node b |
|
||||||
|
OpenSSLCTXArgumentFlow::flow(a, b) and
|
||||||
|
a.asExpr() = source and
|
||||||
|
b.asExpr() = sink
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import cpp
|
||||||
|
|
||||||
|
predicate isPossibleOpenSSLFunction(Function f) {
|
||||||
|
isPossibleOpenSSLLocation(f.getADeclarationLocation())
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isPossibleOpenSSLLocation(Location l) { l.toString().toLowerCase().matches("%openssl%") }
|
||||||
9
cpp/ql/lib/experimental/quantum/OpenSSL/OpenSSL.qll
Normal file
9
cpp/ql/lib/experimental/quantum/OpenSSL/OpenSSL.qll
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import cpp
|
||||||
|
import semmle.code.cpp.dataflow.new.DataFlow
|
||||||
|
|
||||||
|
module OpenSSLModel {
|
||||||
|
import experimental.quantum.Language
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmInstances.OpenSSLAlgorithmInstances
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
||||||
|
import experimental.quantum.OpenSSL.Operations.OpenSSLOperations
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* see: https://docs.openssl.org/master/man3/EVP_EncryptInit/
|
||||||
|
* Models cipher initialization for EVP cipher operations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import experimental.quantum.Language
|
||||||
|
import experimental.quantum.OpenSSL.CtxFlow as CTXFlow
|
||||||
|
|
||||||
|
module EncValToInitEncArgConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) { source.asExpr().getValue().toInt() in [0, 1] }
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) {
|
||||||
|
exists(EVP_Cipher_Initializer initCall | sink.asExpr() = initCall.getOperationSubtypeArg())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module EncValToInitEncArgFlow = DataFlow::Global<EncValToInitEncArgConfig>;
|
||||||
|
|
||||||
|
int getEncConfigValue(Expr e) {
|
||||||
|
exists(EVP_Cipher_Initializer initCall | e = initCall.getOperationSubtypeArg()) and
|
||||||
|
exists(DataFlow::Node a, DataFlow::Node b |
|
||||||
|
EncValToInitEncArgFlow::flow(a, b) and b.asExpr() = e and result = a.asExpr().getValue().toInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingset[i]
|
||||||
|
Crypto::KeyOperationSubtype intToCipherOperationSubtype(int i) {
|
||||||
|
if i = 0
|
||||||
|
then result instanceof Crypto::TEncryptMode
|
||||||
|
else
|
||||||
|
if i = 1
|
||||||
|
then result instanceof Crypto::TDecryptMode
|
||||||
|
else result instanceof Crypto::TUnknownKeyOperationMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: need to add key consumer
|
||||||
|
abstract class EVP_Cipher_Initializer extends Call {
|
||||||
|
Expr getContextArg() { result = this.(Call).getArgument(0) }
|
||||||
|
|
||||||
|
Expr getAlgorithmArg() { result = this.(Call).getArgument(1) }
|
||||||
|
|
||||||
|
abstract Expr getKeyArg();
|
||||||
|
|
||||||
|
abstract Expr getIVArg();
|
||||||
|
|
||||||
|
// abstract Crypto::CipherOperationSubtype getCipherOperationSubtype();
|
||||||
|
abstract Expr getOperationSubtypeArg();
|
||||||
|
|
||||||
|
Crypto::KeyOperationSubtype getCipherOperationSubtype() {
|
||||||
|
if this.(Call).getTarget().getName().toLowerCase().matches("%encrypt%")
|
||||||
|
then result instanceof Crypto::TEncryptMode
|
||||||
|
else
|
||||||
|
if this.(Call).getTarget().getName().toLowerCase().matches("%decrypt%")
|
||||||
|
then result instanceof Crypto::TDecryptMode
|
||||||
|
else
|
||||||
|
if exists(getEncConfigValue(this.getOperationSubtypeArg()))
|
||||||
|
then result = intToCipherOperationSubtype(getEncConfigValue(this.getOperationSubtypeArg()))
|
||||||
|
else result instanceof Crypto::TUnknownKeyOperationMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class EVP_EX_Initializer extends EVP_Cipher_Initializer {
|
||||||
|
override Expr getKeyArg() { result = this.(Call).getArgument(3) }
|
||||||
|
|
||||||
|
override Expr getIVArg() { result = this.(Call).getArgument(4) }
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class EVP_EX2_Initializer extends EVP_Cipher_Initializer {
|
||||||
|
override Expr getKeyArg() { result = this.(Call).getArgument(2) }
|
||||||
|
|
||||||
|
override Expr getIVArg() { result = this.(Call).getArgument(3) }
|
||||||
|
}
|
||||||
|
|
||||||
|
class EVP_Cipher_EX_Init_Call extends EVP_EX_Initializer {
|
||||||
|
EVP_Cipher_EX_Init_Call() {
|
||||||
|
this.(Call).getTarget().getName() in [
|
||||||
|
"EVP_EncryptInit_ex", "EVP_DecryptInit_ex", "EVP_CipherInit_ex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
override Expr getOperationSubtypeArg() {
|
||||||
|
this.(Call).getTarget().getName().toLowerCase().matches("%cipherinit%") and
|
||||||
|
result = this.(Call).getArgument(5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EVP_Cipher_EX2_or_Simple_Init_Call extends EVP_EX2_Initializer {
|
||||||
|
EVP_Cipher_EX2_or_Simple_Init_Call() {
|
||||||
|
this.(Call).getTarget().getName() in [
|
||||||
|
"EVP_EncryptInit_ex2", "EVP_DecryptInit_ex2", "EVP_CipherInit_ex2", "EVP_EncryptInit",
|
||||||
|
"EVP_DecryptInit", "EVP_CipherInit"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
override Expr getOperationSubtypeArg() {
|
||||||
|
this.(Call).getTarget().getName().toLowerCase().matches("%cipherinit%") and
|
||||||
|
result = this.(Call).getArgument(4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EVP_CipherInit_SKEY_Call extends EVP_EX2_Initializer {
|
||||||
|
EVP_CipherInit_SKEY_Call() { this.(Call).getTarget().getName() in ["EVP_CipherInit_SKEY"] }
|
||||||
|
|
||||||
|
override Expr getOperationSubtypeArg() { result = this.(Call).getArgument(5) }
|
||||||
|
}
|
||||||
|
|
||||||
|
class EVPCipherInitializerAlgorithmArgument extends Expr {
|
||||||
|
EVPCipherInitializerAlgorithmArgument() {
|
||||||
|
exists(EVP_Cipher_Initializer initCall | this = initCall.getAlgorithmArg())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EVPCipherInitializerKeyArgument extends Expr {
|
||||||
|
EVPCipherInitializerKeyArgument() {
|
||||||
|
exists(EVP_Cipher_Initializer initCall | this = initCall.getKeyArg())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EVPCipherInitializerIVArgument extends Expr {
|
||||||
|
EVPCipherInitializerIVArgument() {
|
||||||
|
exists(EVP_Cipher_Initializer initCall | this = initCall.getIVArg())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import experimental.quantum.Language
|
||||||
|
import experimental.quantum.OpenSSL.CtxFlow as CTXFlow
|
||||||
|
import EVPCipherInitializer
|
||||||
|
import OpenSSLOperationBase
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
||||||
|
|
||||||
|
private module AlgGetterToAlgConsumerConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) {
|
||||||
|
exists(OpenSSLAlgorithmValueConsumer c | c.getResultNode() = source)
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) {
|
||||||
|
exists(EVP_Cipher_Operation c | c.getInitCall().getAlgorithmArg() = sink.asExpr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private module AlgGetterToAlgConsumerFlow = DataFlow::Global<AlgGetterToAlgConsumerConfig>;
|
||||||
|
|
||||||
|
// import experimental.quantum.OpenSSL.AlgorithmValueConsumers.AlgorithmValueConsumers
|
||||||
|
// import OpenSSLOperation
|
||||||
|
// class EVPCipherOutput extends CipherOutputArtifact {
|
||||||
|
// EVPCipherOutput() { exists(EVP_Cipher_Operation op | op.getOutputArg() = this) }
|
||||||
|
// override DataFlow::Node getOutputNode() { result.asDefiningArgument() = this }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
/**
|
||||||
|
* see: https://docs.openssl.org/master/man3/EVP_EncryptInit/#synopsis
|
||||||
|
* Base configuration for all EVP cipher operations.
|
||||||
|
* NOTE: cannot extend instance of OpenSSLOperation, as we need to override
|
||||||
|
* elements of OpenSSLOperation (i.e., we are creating an instance)
|
||||||
|
*/
|
||||||
|
abstract class EVP_Cipher_Operation extends OpenSSLOperation, Crypto::KeyOperationInstance {
|
||||||
|
Expr getContextArg() { result = this.(Call).getArgument(0) }
|
||||||
|
|
||||||
|
override Expr getOutputArg() { result = this.(Call).getArgument(1) }
|
||||||
|
|
||||||
|
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
|
||||||
|
result instanceof Crypto::TEncryptMode and
|
||||||
|
this.(Call).getTarget().getName().toLowerCase().matches("%encrypt%")
|
||||||
|
or
|
||||||
|
result instanceof Crypto::TDecryptMode and
|
||||||
|
this.(Call).getTarget().getName().toLowerCase().matches("%decrypt%")
|
||||||
|
or
|
||||||
|
result = this.getInitCall().getCipherOperationSubtype() and
|
||||||
|
this.(Call).getTarget().getName().toLowerCase().matches("%cipher%")
|
||||||
|
}
|
||||||
|
|
||||||
|
EVP_Cipher_Initializer getInitCall() {
|
||||||
|
CTXFlow::ctxArgFlowsToCtxArg(result.getContextArg(), this.getContextArg())
|
||||||
|
}
|
||||||
|
|
||||||
|
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() {
|
||||||
|
this.getInitCall().getIVArg() = result.asExpr()
|
||||||
|
}
|
||||||
|
|
||||||
|
override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() }
|
||||||
|
|
||||||
|
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
|
||||||
|
this.getInitCall().getKeyArg() = result.asExpr()
|
||||||
|
}
|
||||||
|
|
||||||
|
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result = this.getOutputNode() }
|
||||||
|
|
||||||
|
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
|
||||||
|
AlgGetterToAlgConsumerFlow::flow(result.(OpenSSLAlgorithmValueConsumer).getResultNode(),
|
||||||
|
DataFlow::exprNode(this.getInitCall().getAlgorithmArg()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// abstract class EVP_Update_Call extends EVP_Cipher_Operation { }
|
||||||
|
abstract class EVP_Final_Call extends EVP_Cipher_Operation {
|
||||||
|
override Expr getInputArg() { none() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: only model Final (model final as operation and model update but not as an operation)
|
||||||
|
// Updates are multiple input consumers (most important)
|
||||||
|
// TODO: assuming update doesn't ouput, otherwise it outputs artifacts, but is not an operation
|
||||||
|
class EVP_Cipher_Call extends EVP_Cipher_Operation {
|
||||||
|
EVP_Cipher_Call() { this.(Call).getTarget().getName() = "EVP_Cipher" }
|
||||||
|
|
||||||
|
override Expr getInputArg() { result = this.(Call).getArgument(2) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ******* TODO: model UPDATE but not as the core operation, rather a step towards final
|
||||||
|
// see the JCA
|
||||||
|
// class EVP_Encrypt_Decrypt_or_Cipher_Update_Call extends EVP_Update_Call {
|
||||||
|
// EVP_Encrypt_Decrypt_or_Cipher_Update_Call() {
|
||||||
|
// this.(Call).getTarget().getName() in [
|
||||||
|
// "EVP_EncryptUpdate", "EVP_DecryptUpdate", "EVP_CipherUpdate"
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// override Expr getInputArg() { result = this.(Call).getArgument(3) }
|
||||||
|
// }
|
||||||
|
class EVP_Encrypt_Decrypt_or_Cipher_Final_Call extends EVP_Final_Call {
|
||||||
|
EVP_Encrypt_Decrypt_or_Cipher_Final_Call() {
|
||||||
|
this.(Call).getTarget().getName() in [
|
||||||
|
"EVP_EncryptFinal_ex", "EVP_DecryptFinal_ex", "EVP_CipherFinal_ex", "EVP_EncryptFinal",
|
||||||
|
"EVP_DecryptFinal", "EVP_CipherFinal"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EVP_PKEY_Operation extends EVP_Cipher_Operation {
|
||||||
|
EVP_PKEY_Operation() {
|
||||||
|
this.(Call).getTarget().getName() in ["EVP_PKEY_decrypt", "EVP_PKEY_encrypt"]
|
||||||
|
}
|
||||||
|
|
||||||
|
override Expr getInputArg() { result = this.(Call).getArgument(3) }
|
||||||
|
// TODO: how PKEY is initialized is different that symmetric cipher
|
||||||
|
// Consider making an entirely new class for this and specializing
|
||||||
|
// the get init call
|
||||||
|
}
|
||||||
|
|
||||||
|
class EVPCipherInputArgument extends Expr {
|
||||||
|
EVPCipherInputArgument() { exists(EVP_Cipher_Operation op | op.getInputArg() = this) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import cpp
|
||||||
|
|
||||||
|
abstract class EVP_Hash_Initializer extends Call {
|
||||||
|
Expr getContextArg() { result = this.(Call).getArgument(0) }
|
||||||
|
|
||||||
|
abstract Expr getAlgorithmArg();
|
||||||
|
}
|
||||||
|
|
||||||
|
class EVP_DigestInit_Variant_Calls extends EVP_Hash_Initializer {
|
||||||
|
EVP_DigestInit_Variant_Calls() {
|
||||||
|
this.(Call).getTarget().getName() in [
|
||||||
|
"EVP_DigestInit", "EVP_DigestInit_ex", "EVP_DigestInit_ex2"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
override Expr getAlgorithmArg() { result = this.(Call).getArgument(1) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/**
|
||||||
|
* https://docs.openssl.org/3.0/man3/EVP_DigestInit/#synopsis
|
||||||
|
*/
|
||||||
|
|
||||||
|
import experimental.quantum.Language
|
||||||
|
import experimental.quantum.OpenSSL.CtxFlow as CTXFlow
|
||||||
|
import experimental.quantum.OpenSSL.LibraryDetector
|
||||||
|
import OpenSSLOperationBase
|
||||||
|
import EVPHashInitializer
|
||||||
|
import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
||||||
|
|
||||||
|
// import EVPHashConsumers
|
||||||
|
abstract class EVP_Hash_Operation extends OpenSSLOperation, Crypto::HashOperationInstance {
|
||||||
|
Expr getContextArg() { result = this.(Call).getArgument(0) }
|
||||||
|
|
||||||
|
EVP_Hash_Initializer getInitCall() {
|
||||||
|
CTXFlow::ctxArgFlowsToCtxArg(result.getContextArg(), this.getContextArg())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private module AlgGetterToAlgConsumerConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) {
|
||||||
|
exists(OpenSSLAlgorithmValueConsumer c | c.getResultNode() = source)
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) {
|
||||||
|
exists(EVP_Hash_Operation c | c.getInitCall().getAlgorithmArg() = sink.asExpr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private module AlgGetterToAlgConsumerFlow = DataFlow::Global<AlgGetterToAlgConsumerConfig>;
|
||||||
|
|
||||||
|
//https://docs.openssl.org/3.0/man3/EVP_DigestInit/#synopsis
|
||||||
|
class EVP_Q_Digest_Operation extends EVP_Hash_Operation {
|
||||||
|
EVP_Q_Digest_Operation() {
|
||||||
|
this.(Call).getTarget().getName() = "EVP_Q_digest" and
|
||||||
|
isPossibleOpenSSLFunction(this.(Call).getTarget())
|
||||||
|
}
|
||||||
|
|
||||||
|
//override Crypto::AlgorithmConsumer getAlgorithmConsumer() { }
|
||||||
|
override EVP_Hash_Initializer getInitCall() {
|
||||||
|
// This variant of digest does not use an init
|
||||||
|
// and even if it were used, the init would be ignored/undefined
|
||||||
|
none()
|
||||||
|
}
|
||||||
|
|
||||||
|
override Expr getOutputArg() { result = this.(Call).getArgument(5) }
|
||||||
|
|
||||||
|
override Expr getInputArg() { result = this.(Call).getArgument(3) }
|
||||||
|
|
||||||
|
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result = this.getOutputNode() }
|
||||||
|
|
||||||
|
override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() }
|
||||||
|
|
||||||
|
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
|
||||||
|
// The operation is a direct algorithm consumer
|
||||||
|
// NOTE: the operation itself is already modeld as a value consumer, so we can
|
||||||
|
// simply return 'this', see modeled hash algorithm consuers for EVP_Q_Digest
|
||||||
|
this = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EVP_Digest_Operation extends EVP_Hash_Operation {
|
||||||
|
EVP_Digest_Operation() {
|
||||||
|
this.(Call).getTarget().getName() = "EVP_Digest" and
|
||||||
|
isPossibleOpenSSLFunction(this.(Call).getTarget())
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is no context argument for this function
|
||||||
|
override Expr getContextArg() { none() }
|
||||||
|
|
||||||
|
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
|
||||||
|
AlgGetterToAlgConsumerFlow::flow(result.(OpenSSLAlgorithmValueConsumer).getResultNode(),
|
||||||
|
DataFlow::exprNode(this.(Call).getArgument(4)))
|
||||||
|
}
|
||||||
|
|
||||||
|
override EVP_Hash_Initializer getInitCall() {
|
||||||
|
// This variant of digest does not use an init
|
||||||
|
// and even if it were used, the init would be ignored/undefined
|
||||||
|
none()
|
||||||
|
}
|
||||||
|
|
||||||
|
override Expr getOutputArg() { result = this.(Call).getArgument(2) }
|
||||||
|
|
||||||
|
override Expr getInputArg() { result = this.(Call).getArgument(0) }
|
||||||
|
|
||||||
|
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result = this.getOutputNode() }
|
||||||
|
|
||||||
|
override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() }
|
||||||
|
}
|
||||||
|
// // override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
|
||||||
|
// // AlgGetterToAlgConsumerFlow::flow(result.(OpenSSLAlgorithmValueConsumer).getResultNode(),
|
||||||
|
// // DataFlow::exprNode(this.getInitCall().getAlgorithmArg()))
|
||||||
|
// // }
|
||||||
|
// // ***** TODO *** complete modelinlg for hash operations, but have consideration for terminal and non-terminal (non intermedaite) steps
|
||||||
|
// // see the JCA. May need to update the cipher operations similarly
|
||||||
|
// // ALSO SEE cipher for how we currently model initialization of the algorithm through an init call
|
||||||
|
// class EVP_DigestUpdate_Operation extends EVP_Hash_Operation {
|
||||||
|
// EVP_DigestUpdate_Operation() {
|
||||||
|
// this.(Call).getTarget().getName() = "EVP_DigestUpdate" and
|
||||||
|
// isPossibleOpenSSLFunction(this.(Call).getTarget())
|
||||||
|
// }
|
||||||
|
// override Crypto::AlgorithmConsumer getAlgorithmConsumer() {
|
||||||
|
// this.getInitCall().getAlgorithmArg() = result
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// class EVP_DigestFinal_Variants_Operation extends EVP_Hash_Operation {
|
||||||
|
// EVP_DigestFinal_Variants_Operation() {
|
||||||
|
// this.(Call).getTarget().getName() in [
|
||||||
|
// "EVP_DigestFinal", "EVP_DigestFinal_ex", "EVP_DigestFinalXOF"
|
||||||
|
// ] and
|
||||||
|
// isPossibleOpenSSLFunction(this.(Call).getTarget())
|
||||||
|
// }
|
||||||
|
// override Crypto::AlgorithmConsumer getAlgorithmConsumer() {
|
||||||
|
// this.getInitCall().getAlgorithmArg() = result
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
abstract class OpenSSLOperation extends Crypto::OperationInstance instanceof Call {
|
||||||
|
abstract Expr getInputArg();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be an argument of a call or a return value of a function.
|
||||||
|
*/
|
||||||
|
abstract Expr getOutputArg();
|
||||||
|
|
||||||
|
DataFlow::Node getInputNode() {
|
||||||
|
// Assumed to be default to asExpr
|
||||||
|
result.asExpr() = this.getInputArg()
|
||||||
|
}
|
||||||
|
|
||||||
|
DataFlow::Node getOutputNode() {
|
||||||
|
if exists(Call c | c.getAnArgument() = this)
|
||||||
|
then result.asDefiningArgument() = this
|
||||||
|
else result.asExpr() = this
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import OpenSSLOperationBase
|
||||||
|
import EVPCipherOperation
|
||||||
|
import EVPHashOperation
|
||||||
18
cpp/ql/lib/experimental/quantum/OpenSSL/Random.qll
Normal file
18
cpp/ql/lib/experimental/quantum/OpenSSL/Random.qll
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import cpp
|
||||||
|
private import experimental.quantum.Language
|
||||||
|
private import LibraryDetector
|
||||||
|
private import semmle.code.cpp.dataflow.new.DataFlow
|
||||||
|
|
||||||
|
class OpenSSLRandomNumberGeneratorInstance extends Crypto::RandomNumberGenerationInstance instanceof Call
|
||||||
|
{
|
||||||
|
OpenSSLRandomNumberGeneratorInstance() {
|
||||||
|
this.(Call).getTarget().getName() in ["RAND_bytes", "RAND_pseudo_bytes"] and
|
||||||
|
isPossibleOpenSSLFunction(this.(Call).getTarget())
|
||||||
|
}
|
||||||
|
|
||||||
|
override Crypto::DataFlowNode getOutputNode() {
|
||||||
|
result.asDefiningArgument() = this.(Call).getArgument(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override string getGeneratorName() { result = this.(Call).getTarget().getName() }
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ upgrades: upgrades
|
|||||||
dependencies:
|
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}
|
||||||
|
|||||||
23
cpp/ql/src/experimental/quantum/PrintCBOMGraph.ql
Normal file
23
cpp/ql/src/experimental/quantum/PrintCBOMGraph.ql
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* @name Print CBOM Graph
|
||||||
|
* @description Outputs a graph representation of the cryptographic bill of materials.
|
||||||
|
* This query only supports DGML output, as CodeQL DOT output omits properties.
|
||||||
|
* @kind graph
|
||||||
|
* @id cpp/print-cbom-graph
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
query predicate nodes(Crypto::NodeBase node, string key, string value) {
|
||||||
|
Crypto::nodes_graph_impl(node, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
query predicate edges(Crypto::NodeBase source, Crypto::NodeBase target, string key, string value) {
|
||||||
|
Crypto::edges_graph_impl(source, target, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
query predicate graphProperties(string key, string value) {
|
||||||
|
key = "semmle.graphKind" and value = "graph"
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os.path
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
# Add Model as Data script directory to sys.path.
|
|
||||||
gitroot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip()
|
|
||||||
madpath = os.path.join(gitroot, "misc/scripts/models-as-data/")
|
|
||||||
sys.path.append(madpath)
|
|
||||||
|
|
||||||
import generate_flow_model as model
|
|
||||||
|
|
||||||
language = "cpp"
|
|
||||||
model.Generator.make(language).run()
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os.path
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
# Add Model as Data script directory to sys.path.
|
|
||||||
gitroot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip()
|
|
||||||
madpath = os.path.join(gitroot, "misc/scripts/models-as-data/")
|
|
||||||
sys.path.append(madpath)
|
|
||||||
|
|
||||||
import generate_flow_model as model
|
|
||||||
|
|
||||||
language = "csharp"
|
|
||||||
model.Generator.make(language).run()
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
semmle-extractor-options: /nostdlib /noconfig
|
semmle-extractor-options: /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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs /r:System.Collections.Specialized.dll /r:System.Xml.ReaderWriter.dll /r:System.Private.Xml.dll /r:System.Runtime.Extensions.dll
|
semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs
|
||||||
|
semmle-extractor-options: /r:System.Collections.Specialized.dll /r:System.Xml.ReaderWriter.dll /r:System.Private.Xml.dll /r:System.Runtime.Extensions.dll
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
semmle-extractor-options: /nostdlib /noconfig
|
semmle-extractor-options: /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
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ namespace System.Net.Http
|
|||||||
{
|
{
|
||||||
public StringContent(string s) { }
|
public StringContent(string s) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace System.Net.Mail
|
namespace System.Net.Mail
|
||||||
|
|||||||
14
csharp/ql/test/resources/stubs/System.Web.Http.cs
Normal file
14
csharp/ql/test/resources/stubs/System.Web.Http.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace System.Web.Http
|
||||||
|
{
|
||||||
|
public class ApiController
|
||||||
|
{
|
||||||
|
public Microsoft.AspNetCore.Http.HttpContext Context => null;
|
||||||
|
public virtual Microsoft.AspNetCore.Mvc.RedirectResult Redirect(Uri location) => null;
|
||||||
|
public virtual Microsoft.AspNetCore.Mvc.RedirectResult Redirect(string location) => null;
|
||||||
|
public virtual ResponseMessageResult ResponseMessage(System.Net.Http.HttpResponseMessage response) => null;
|
||||||
|
public virtual Microsoft.AspNetCore.Mvc.RedirectToRouteResult RedirectToRoute(string routeName, object routeValues) => null;
|
||||||
|
public Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ResponseMessageResult { }
|
||||||
|
}
|
||||||
@@ -19,6 +19,15 @@ namespace System.Web
|
|||||||
public class HttpResponseBase
|
public 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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=
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
1618
java/ql/lib/experimental/quantum/JCA.qll
Normal file
1618
java/ql/lib/experimental/quantum/JCA.qll
Normal file
File diff suppressed because it is too large
Load Diff
218
java/ql/lib/experimental/quantum/Language.qll
Normal file
218
java/ql/lib/experimental/quantum/Language.qll
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
private import java as Language
|
||||||
|
private import semmle.code.java.security.InsecureRandomnessQuery
|
||||||
|
private import semmle.code.java.security.RandomQuery
|
||||||
|
private import semmle.code.java.dataflow.DataFlow
|
||||||
|
private import semmle.code.java.dataflow.FlowSources
|
||||||
|
private import codeql.quantum.experimental.Model
|
||||||
|
|
||||||
|
private class UnknownLocation extends Language::Location {
|
||||||
|
UnknownLocation() { this.getFile().getAbsolutePath() = "" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dummy location which is used when something doesn't have a location in
|
||||||
|
* the source code but needs to have a `Location` associated with it. There
|
||||||
|
* may be several distinct kinds of unknown locations. For example: one for
|
||||||
|
* expressions, one for statements and one for other program elements.
|
||||||
|
*/
|
||||||
|
private class UnknownDefaultLocation extends UnknownLocation {
|
||||||
|
UnknownDefaultLocation() { locations_default(this, _, 0, 0, 0, 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
module CryptoInput implements InputSig<Language::Location> {
|
||||||
|
class DataFlowNode = DataFlow::Node;
|
||||||
|
|
||||||
|
class LocatableElement = Language::Element;
|
||||||
|
|
||||||
|
class UnknownLocation = UnknownDefaultLocation;
|
||||||
|
|
||||||
|
string locationToFileBaseNameAndLineNumberString(Location location) {
|
||||||
|
result = location.getFile().getBaseName() + ":" + location.getStartLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
LocatableElement dfn_to_element(DataFlow::Node node) {
|
||||||
|
result = node.asExpr() or
|
||||||
|
result = node.asParameter()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate artifactOutputFlowsToGenericInput(
|
||||||
|
DataFlow::Node artifactOutput, DataFlow::Node otherInput
|
||||||
|
) {
|
||||||
|
ArtifactFlow::flow(artifactOutput, otherInput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instantiate the `CryptographyBase` module
|
||||||
|
module Crypto = CryptographyBase<Language::Location, CryptoInput>;
|
||||||
|
|
||||||
|
// Definitions of various generic sources
|
||||||
|
final class DefaultFlowSource = SourceNode;
|
||||||
|
|
||||||
|
final class DefaultRemoteFlowSource = RemoteFlowSource;
|
||||||
|
|
||||||
|
private class GenericUnreferencedParameterSource extends Crypto::GenericUnreferencedParameterSource {
|
||||||
|
GenericUnreferencedParameterSource() {
|
||||||
|
exists(Parameter p | this = p and not exists(p.getAnArgument()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override predicate flowsTo(Crypto::FlowAwareElement other) {
|
||||||
|
GenericDataSourceFlow::flow(this.getOutputNode(), other.getInputNode())
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getOutputNode() { result.asParameter() = this }
|
||||||
|
|
||||||
|
override string getAdditionalDescription() { result = this.toString() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GenericLocalDataSource extends Crypto::GenericLocalDataSource {
|
||||||
|
GenericLocalDataSource() {
|
||||||
|
any(DefaultFlowSource src | not src instanceof DefaultRemoteFlowSource).asExpr() = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getOutputNode() { result.asExpr() = this }
|
||||||
|
|
||||||
|
override predicate flowsTo(Crypto::FlowAwareElement other) {
|
||||||
|
GenericDataSourceFlow::flow(this.getOutputNode(), other.getInputNode())
|
||||||
|
}
|
||||||
|
|
||||||
|
override string getAdditionalDescription() { result = this.toString() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GenericRemoteDataSource extends Crypto::GenericRemoteDataSource {
|
||||||
|
GenericRemoteDataSource() { any(DefaultRemoteFlowSource src).asExpr() = this }
|
||||||
|
|
||||||
|
override DataFlow::Node getOutputNode() { result.asExpr() = this }
|
||||||
|
|
||||||
|
override predicate flowsTo(Crypto::FlowAwareElement other) {
|
||||||
|
GenericDataSourceFlow::flow(this.getOutputNode(), other.getInputNode())
|
||||||
|
}
|
||||||
|
|
||||||
|
override string getAdditionalDescription() { result = this.toString() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ConstantDataSource extends Crypto::GenericConstantSourceInstance instanceof Literal {
|
||||||
|
ConstantDataSource() {
|
||||||
|
// TODO: this is an API specific workaround for JCA, as 'EC' is a constant that may be used
|
||||||
|
// where typical algorithms are specified, but EC specifically means set up a
|
||||||
|
// default curve container, that will later be specified explicitly (or if not a default)
|
||||||
|
// curve is used.
|
||||||
|
this.getValue() != "EC"
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getOutputNode() { result.asExpr() = this }
|
||||||
|
|
||||||
|
override predicate flowsTo(Crypto::FlowAwareElement other) {
|
||||||
|
// TODO: separate config to avoid blowing up data-flow analysis
|
||||||
|
GenericDataSourceFlow::flow(this.getOutputNode(), other.getInputNode())
|
||||||
|
}
|
||||||
|
|
||||||
|
override string getAdditionalDescription() { result = this.toString() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An instance of random number generation, modelled as the expression
|
||||||
|
* tied to an output node (i.e., the result of the source of randomness)
|
||||||
|
*/
|
||||||
|
abstract class RandomnessInstance extends Crypto::RandomNumberGenerationInstance {
|
||||||
|
override DataFlow::Node getOutputNode() { result.asExpr() = this }
|
||||||
|
}
|
||||||
|
|
||||||
|
class SecureRandomnessInstance extends RandomnessInstance {
|
||||||
|
RandomDataSource source;
|
||||||
|
|
||||||
|
SecureRandomnessInstance() {
|
||||||
|
this = source.getOutput() and
|
||||||
|
source.getSourceOfRandomness() instanceof SecureRandomNumberGenerator
|
||||||
|
}
|
||||||
|
|
||||||
|
override string getGeneratorName() { result = source.getSourceOfRandomness().getQualifiedName() }
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsecureRandomnessInstance extends RandomnessInstance {
|
||||||
|
RandomDataSource source;
|
||||||
|
|
||||||
|
InsecureRandomnessInstance() {
|
||||||
|
any(InsecureRandomnessSource src).asExpr() = this and source.getOutput() = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override string getGeneratorName() { result = source.getSourceOfRandomness().getQualifiedName() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An additional flow step in generic data-flow configurations.
|
||||||
|
* Where a step is an edge between nodes `n1` and `n2`,
|
||||||
|
* `this` = `n1` and `getOutput()` = `n2`.
|
||||||
|
*
|
||||||
|
* FOR INTERNAL MODELING USE ONLY.
|
||||||
|
*/
|
||||||
|
abstract class AdditionalFlowInputStep extends DataFlow::Node {
|
||||||
|
abstract DataFlow::Node getOutput();
|
||||||
|
|
||||||
|
final DataFlow::Node getInput() { result = this }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic data source to node input configuration
|
||||||
|
*/
|
||||||
|
module GenericDataSourceFlowConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) {
|
||||||
|
source = any(Crypto::GenericSourceInstance i).getOutputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) {
|
||||||
|
sink = any(Crypto::FlowAwareElement other).getInputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isBarrierOut(DataFlow::Node node) {
|
||||||
|
node = any(Crypto::FlowAwareElement element).getInputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isBarrierIn(DataFlow::Node node) {
|
||||||
|
node = any(Crypto::FlowAwareElement element).getOutputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||||
|
node1.(AdditionalFlowInputStep).getOutput() = node2
|
||||||
|
or
|
||||||
|
exists(MethodCall m |
|
||||||
|
m.getMethod().hasQualifiedName("java.lang", "String", "getBytes") and
|
||||||
|
node1.asExpr() = m.getQualifier() and
|
||||||
|
node2.asExpr() = m
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module ArtifactFlowConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) {
|
||||||
|
source = any(Crypto::ArtifactInstance artifact).getOutputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) {
|
||||||
|
sink = any(Crypto::FlowAwareElement other).getInputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isBarrierOut(DataFlow::Node node) {
|
||||||
|
node = any(Crypto::FlowAwareElement element).getInputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isBarrierIn(DataFlow::Node node) {
|
||||||
|
node = any(Crypto::FlowAwareElement element).getOutputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||||
|
node1.(AdditionalFlowInputStep).getOutput() = node2
|
||||||
|
or
|
||||||
|
exists(MethodCall m |
|
||||||
|
m.getMethod().hasQualifiedName("java.lang", "String", "getBytes") and
|
||||||
|
node1.asExpr() = m.getQualifier() and
|
||||||
|
node2.asExpr() = m
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module GenericDataSourceFlow = TaintTracking::Global<GenericDataSourceFlowConfig>;
|
||||||
|
|
||||||
|
module ArtifactFlow = DataFlow::Global<ArtifactFlowConfig>;
|
||||||
|
|
||||||
|
// Import library-specific modeling
|
||||||
|
import JCA
|
||||||
@@ -8,6 +8,7 @@ upgrades: upgrades
|
|||||||
dependencies:
|
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}
|
||||||
|
|||||||
70
java/ql/src/experimental/quantum/Analysis/ArtifactReuse.qll
Normal file
70
java/ql/src/experimental/quantum/Analysis/ArtifactReuse.qll
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import java
|
||||||
|
import semmle.code.java.dataflow.DataFlow
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flow from any function that appears to return a value
|
||||||
|
* to an artifact node.
|
||||||
|
* NOTE: TODO: need to handle call by refernece for now. Need to re-evaluate (see notes below)
|
||||||
|
* Such functions may be 'wrappers' for some derived value.
|
||||||
|
*/
|
||||||
|
private module WrapperConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) {
|
||||||
|
exists(Call c |
|
||||||
|
c = source.asExpr()
|
||||||
|
// not handling references yet, I think we want to flat say references are only ok
|
||||||
|
// if I know the source, otherwise, it has to be through an additional flow step, which
|
||||||
|
// we filter as a source, i.e., references are only allowed as sources only,
|
||||||
|
// no inferrece? Not sure if that would work
|
||||||
|
//or
|
||||||
|
// source.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr() = c.getAnArgument()
|
||||||
|
) and
|
||||||
|
// Filter out sources that are known additional flow steps, as these are likely not the
|
||||||
|
// kind of wrapper source we are looking for.
|
||||||
|
not exists(AdditionalFlowInputStep s | s.getOutput() = source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flow through additional flow steps
|
||||||
|
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||||
|
node1.(AdditionalFlowInputStep).getOutput() = node2
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(Crypto::ArtifactNode i).asElement() }
|
||||||
|
}
|
||||||
|
|
||||||
|
module WrapperFlow = DataFlow::Global<WrapperConfig>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Using a set approach to determine if reuse of an artifact exists.
|
||||||
|
* This predicate produces a set of 'wrappers' that flow to the artifact node.
|
||||||
|
* This set can be compared with the set to another artifact node to determine if they are the same.
|
||||||
|
*/
|
||||||
|
private DataFlow::Node getWrapperSet(Crypto::NonceArtifactNode a) {
|
||||||
|
WrapperFlow::flow(result, DataFlow::exprNode(a.asElement()))
|
||||||
|
or
|
||||||
|
result.asExpr() = a.getSourceElement()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Two different artifact nodes are considered reuse if any of the following conditions are met:
|
||||||
|
* 1. The source for artifact `a` and artifact `b` are the same and the source is a literal.
|
||||||
|
* 2. The source for artifact `a` and artifact `b` are not the same and the source is a literal of the same value.
|
||||||
|
* 3. For all 'wrappers' that return the source of artifact `a`, and that wrapper also exists for artifact `b`.
|
||||||
|
* 4. For all 'wrappers' that return the source of artifact `b`, and that wrapper also exists for artifact `a`.
|
||||||
|
*/
|
||||||
|
predicate isArtifactReuse(Crypto::ArtifactNode a, Crypto::ArtifactNode b) {
|
||||||
|
a != b and
|
||||||
|
(
|
||||||
|
a.getSourceElement() = b.getSourceElement() and a.getSourceElement() instanceof Literal
|
||||||
|
or
|
||||||
|
a.getSourceElement().(Literal).getValue() = b.getSourceElement().(Literal).getValue()
|
||||||
|
or
|
||||||
|
forex(DataFlow::Node e | e = getWrapperSet(a) |
|
||||||
|
exists(DataFlow::Node e2 | e2 = getWrapperSet(b) | e = e2)
|
||||||
|
)
|
||||||
|
or
|
||||||
|
forex(DataFlow::Node e | e = getWrapperSet(b) |
|
||||||
|
exists(DataFlow::Node e2 | e2 = getWrapperSet(a) | e = e2)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* @name Insecure nonce at a cipher operation
|
||||||
|
* @id java/quantum/insecure-nonce
|
||||||
|
* @description A nonce is generated from a source that is not secure. This can lead to
|
||||||
|
* vulnerabilities such as replay attacks or key recovery.
|
||||||
|
* @kind problem
|
||||||
|
* @problem.severity error
|
||||||
|
* @precision high
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
predicate isInsecureNonceSource(Crypto::NonceArtifactNode n, Crypto::NodeBase src) {
|
||||||
|
src = n.getSourceNode() and
|
||||||
|
not src.asElement() instanceof SecureRandomnessInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
from Crypto::KeyOperationNode op, Crypto::NodeBase src
|
||||||
|
where isInsecureNonceSource(op.getANonce(), src)
|
||||||
|
select op, "Operation uses insecure nonce source $@", src, src.toString()
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @name Weak known key derivation function iteration count
|
||||||
|
* @description Detects key derivation operations with a known weak iteration count.
|
||||||
|
* @id java/quantum/weak-kdf-iteration-count
|
||||||
|
* @kind problem
|
||||||
|
* @problem.severity error
|
||||||
|
* @precision high
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Crypto::KeyDerivationOperationNode op, Literal l
|
||||||
|
where
|
||||||
|
op.getIterationCount().asElement() = l and
|
||||||
|
l.getValue().toInt() < 100000
|
||||||
|
select op, "Key derivation operation configures iteration count below 100k: $@", l,
|
||||||
|
l.getValue().toString()
|
||||||
17
java/ql/src/experimental/quantum/Analysis/ReusedNonce.ql
Normal file
17
java/ql/src/experimental/quantum/Analysis/ReusedNonce.ql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* @name Reuse of cryptographic nonce
|
||||||
|
* @description Reuse of nonce in cryptographic operations can lead to vulnerabilities.
|
||||||
|
* @id java/quantum/reused-nonce
|
||||||
|
* @kind problem
|
||||||
|
* @problem.severity error
|
||||||
|
* @precision medium
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import ArtifactReuse
|
||||||
|
|
||||||
|
from Crypto::NonceArtifactNode nonce1, Crypto::NonceArtifactNode nonce2
|
||||||
|
where isArtifactReuse(nonce1, nonce2)
|
||||||
|
select nonce1, "Reuse with nonce $@", nonce2, nonce2.toString()
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* @name Unknown key derivation function iteration count
|
||||||
|
* @description Detects key derivation operations with an unknown iteration count.
|
||||||
|
* @id java/quantum/unknown-kdf-iteration-count
|
||||||
|
* @kind problem
|
||||||
|
* @precision medium
|
||||||
|
* @severity warning
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Crypto::KeyDerivationOperationNode op, Element e, string msg
|
||||||
|
where
|
||||||
|
e = op.getIterationCount().asElement() and
|
||||||
|
not e instanceof Literal and
|
||||||
|
msg = "Key derivation operation with unknown iteration: $@"
|
||||||
|
or
|
||||||
|
not exists(op.getIterationCount()) and
|
||||||
|
e = op.asElement() and
|
||||||
|
msg = "Key derivation operation with no iteration configuration."
|
||||||
|
select op, msg, e, e.toString()
|
||||||
75
java/ql/src/experimental/quantum/Examples/BrokenCrypto.ql
Normal file
75
java/ql/src/experimental/quantum/Examples/BrokenCrypto.ql
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* @name Use of a broken or risky cryptographic algorithm
|
||||||
|
* @description Using broken or weak cryptographic algorithms can allow an attacker to compromise security.
|
||||||
|
* @kind problem
|
||||||
|
* @problem.severity warning
|
||||||
|
* @security-severity 7.5
|
||||||
|
* @precision high
|
||||||
|
* @id java/weak-cryptographic-algorithm-new-model
|
||||||
|
* @tags security
|
||||||
|
* external/cwe/cwe-327
|
||||||
|
* external/cwe/cwe-328
|
||||||
|
*/
|
||||||
|
|
||||||
|
//THIS QUERY IS A REPLICA OF: https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.ql
|
||||||
|
//but uses the **NEW MODELLING**
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the name of an algorithm that is known to be insecure.
|
||||||
|
*/
|
||||||
|
string getAnInsecureAlgorithmName() {
|
||||||
|
result =
|
||||||
|
[
|
||||||
|
"DES", "RC2", "RC4", "RC5",
|
||||||
|
// ARCFOUR is a variant of RC4
|
||||||
|
"ARCFOUR",
|
||||||
|
// Encryption mode ECB like AES/ECB/NoPadding is vulnerable to replay and other attacks
|
||||||
|
"ECB",
|
||||||
|
// CBC mode of operation with PKCS#5 or PKCS#7 padding is vulnerable to padding oracle attacks
|
||||||
|
"AES/CBC/PKCS[57]Padding"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private string rankedInsecureAlgorithm(int i) {
|
||||||
|
result = rank[i](string s | s = getAnInsecureAlgorithmName())
|
||||||
|
}
|
||||||
|
|
||||||
|
private string insecureAlgorithmString(int i) {
|
||||||
|
i = 1 and result = rankedInsecureAlgorithm(i)
|
||||||
|
or
|
||||||
|
result = rankedInsecureAlgorithm(i) + "|" + insecureAlgorithmString(i - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the regular expression used for matching strings that look like they
|
||||||
|
* contain an algorithm that is known to be insecure.
|
||||||
|
*/
|
||||||
|
string getInsecureAlgorithmRegex() {
|
||||||
|
result = algorithmRegex(insecureAlgorithmString(max(int i | exists(rankedInsecureAlgorithm(i)))))
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingset[algorithmString]
|
||||||
|
private string algorithmRegex(string algorithmString) {
|
||||||
|
// Algorithms usually appear in names surrounded by characters that are not
|
||||||
|
// alphabetical characters in the same case. This handles the upper and lower
|
||||||
|
// case cases.
|
||||||
|
result =
|
||||||
|
"((^|.*[^A-Z])(" + algorithmString + ")([^A-Z].*|$))" +
|
||||||
|
// or...
|
||||||
|
"|" +
|
||||||
|
// For lowercase, we want to be careful to avoid being confused by camelCase
|
||||||
|
// hence we require two preceding uppercase letters to be sure of a case switch,
|
||||||
|
// or a preceding non-alphabetic character
|
||||||
|
"((^|.*[A-Z]{2}|.*[^a-zA-Z])(" + algorithmString.toLowerCase() + ")([^a-z].*|$))"
|
||||||
|
}
|
||||||
|
|
||||||
|
from Crypto::AlgorithmNode alg
|
||||||
|
where
|
||||||
|
alg.getAlgorithmName().regexpMatch(getInsecureAlgorithmRegex()) and
|
||||||
|
// Exclude RSA/ECB/.* ciphers.
|
||||||
|
not alg.getAlgorithmName().regexpMatch("RSA/ECB.*") and
|
||||||
|
// Exclude German and French sentences.
|
||||||
|
not alg.getAlgorithmName().regexpMatch(".*\\p{IsLowercase} des \\p{IsLetter}.*")
|
||||||
|
select alg, "Cryptographic algorithm $@ is weak and should not be used.", alg,
|
||||||
|
alg.getAlgorithmName()
|
||||||
16
java/ql/src/experimental/quantum/Examples/TestAESGCMNonce.ql
Normal file
16
java/ql/src/experimental/quantum/Examples/TestAESGCMNonce.ql
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* @name "PQC Test"
|
||||||
|
*/
|
||||||
|
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
class AESGCMAlgorithmNode extends Crypto::KeyOperationAlgorithmNode {
|
||||||
|
AESGCMAlgorithmNode() {
|
||||||
|
this.getAlgorithmType() = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::AES()) and
|
||||||
|
this.getModeOfOperation().getModeType() = Crypto::GCM()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
from Crypto::KeyOperationNode op, Crypto::NonceArtifactNode nonce
|
||||||
|
where op.getAKnownAlgorithm() instanceof AESGCMAlgorithmNode and nonce = op.getANonce()
|
||||||
|
select op, nonce.getSourceNode()
|
||||||
18
java/ql/src/experimental/quantum/Examples/TestCipher.ql
Normal file
18
java/ql/src/experimental/quantum/Examples/TestCipher.ql
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* @name "Key operation slice table demo query"
|
||||||
|
*/
|
||||||
|
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from
|
||||||
|
Crypto::KeyOperationNode op, Crypto::KeyOperationAlgorithmNode a,
|
||||||
|
Crypto::ModeOfOperationAlgorithmNode m, Crypto::PaddingAlgorithmNode p,
|
||||||
|
Crypto::NonceArtifactNode nonce, Crypto::KeyArtifactNode k
|
||||||
|
where
|
||||||
|
a = op.getAKnownAlgorithm() and
|
||||||
|
m = a.getModeOfOperation() and
|
||||||
|
p = a.getPaddingAlgorithm() and
|
||||||
|
nonce = op.getANonce() and
|
||||||
|
k = op.getAKey()
|
||||||
|
select op, op.getKeyOperationSubtype(), a, a.getRawAlgorithmName(), m, m.getRawAlgorithmName(), p,
|
||||||
|
p.getRawAlgorithmName(), nonce, k
|
||||||
9
java/ql/src/experimental/quantum/Examples/TestHash.ql
Normal file
9
java/ql/src/experimental/quantum/Examples/TestHash.ql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @name "Hash operation slice table demo query"
|
||||||
|
*/
|
||||||
|
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Crypto::HashOperationNode op, Crypto::HashAlgorithmNode alg
|
||||||
|
where alg = op.getAKnownAlgorithm()
|
||||||
|
select op, op.getDigest(), alg, alg.getRawAlgorithmName()
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* @name Operations using known asymmetric cipher algorithms (slice)
|
||||||
|
* @description Outputs operations where the algorithm used is a known asymmetric cipher algorithm.
|
||||||
|
* @id java/quantum/slices/known-asymmetric-algorithm
|
||||||
|
* @kind table
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Crypto::AsymmetricAlgorithmNode a
|
||||||
|
select a, a.asAlgorithmNode().getAlgorithmName()
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* @name Known asymmetric cipher algorithms (slice)
|
||||||
|
* @description Outputs known asymmetric cipher algorithms.
|
||||||
|
* @id java/quantum/slices/known-asymmetric-cipher-algorithm
|
||||||
|
* @kind table
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Crypto::KeyOperationAlgorithmNode a
|
||||||
|
where a.getAlgorithmType() instanceof Crypto::KeyOpAlg::AsymmetricCipherAlgorithm
|
||||||
|
select a, a.getAlgorithmName()
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* @name Operations using known asymmetric algorithms (slice)
|
||||||
|
* @description Outputs operations where the algorithm used is a known asymmetric algorithm.
|
||||||
|
* @id java/quantum/slices/known-asymmetric-operation-algorithm
|
||||||
|
* @kind table
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Crypto::OperationNode op, Crypto::AsymmetricAlgorithmNode a
|
||||||
|
where a = op.getAKnownAlgorithm()
|
||||||
|
select op, a.asAlgorithmNode().getAlgorithmName()
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* @name Known cipher algorithms (slice)
|
||||||
|
* @description Outputs known cipher algorithms.
|
||||||
|
* @id java/quantum/slices/known-cipher-algorithm
|
||||||
|
* @kind table
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
// TODO: should there be a cipher algorithm node?
|
||||||
|
from Crypto::KeyOperationAlgorithmNode a
|
||||||
|
where
|
||||||
|
a.getAlgorithmType() instanceof Crypto::KeyOpAlg::AsymmetricCipherAlgorithm or
|
||||||
|
a.getAlgorithmType() instanceof Crypto::KeyOpAlg::SymmetricCipherAlgorithm
|
||||||
|
select a, a.getAlgorithmName()
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* @name Known elliptic curve algorithms (slice)
|
||||||
|
* @description Outputs known elliptic curve algorithms.
|
||||||
|
* @id java/quantum/slices/known-elliptic-curve-algorithm
|
||||||
|
* @kind table
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Crypto::EllipticCurveNode a
|
||||||
|
select a, a.getAlgorithmName()
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* @name Known hashing algorithms (slice)
|
||||||
|
* @description Outputs known hashing algorithms.
|
||||||
|
* @id java/quantum/slices/known-hashing-algorithm
|
||||||
|
* @kind table
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Crypto::HashAlgorithmNode a
|
||||||
|
select a, a.getAlgorithmName()
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* @name Known hashing operations (slice)
|
||||||
|
* @description Outputs known hashing operations.
|
||||||
|
* @id java/quantum/slices/known-hashing-operation
|
||||||
|
* @kind table
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Crypto::HashOperationNode op
|
||||||
|
select op
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* @name Operations using known hashing algorithms (slice)
|
||||||
|
* @description Outputs operations where the algorithm used is a known hashing algorithm.
|
||||||
|
* @id java/quantum/slices/operation-with-known-hashing-algorithm
|
||||||
|
* @kind table
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Crypto::OperationNode op, Crypto::HashAlgorithmNode a
|
||||||
|
where a = op.getAKnownAlgorithm()
|
||||||
|
select op, a.getAlgorithmName()
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* @name Known key derivation algorithms (slice)
|
||||||
|
* @description Outputs known key derivation algorithms.
|
||||||
|
* @id java/quantum/slices/known-key-derivation-algorithm
|
||||||
|
* @kind table
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Crypto::KeyDerivationAlgorithmNode alg
|
||||||
|
select alg, alg.getAlgorithmName()
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* @name Known key derivation operations (slice)
|
||||||
|
* @description Outputs known key derivation operations.
|
||||||
|
* @id java/quantum/slices/known-key-derivation-operation
|
||||||
|
* @kind table
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Crypto::KeyDerivationOperationNode op
|
||||||
|
select op
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* @name Operations using known key derivation algorithms (slice)
|
||||||
|
* @description Outputs operations where the algorithm used is a known key derivation algorithm.
|
||||||
|
* @id java/quantum/slices/operation-with-known-kdf-algorithm
|
||||||
|
* @kind table
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Crypto::OperationNode op, Crypto::KeyDerivationAlgorithmNode a
|
||||||
|
where a = op.getAKnownAlgorithm()
|
||||||
|
select op, a.getAlgorithmName()
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* @name Known symmetric cipher algorithms (slice)
|
||||||
|
* @description Outputs known symmetric cipher algorithms.
|
||||||
|
* @id java/quantum/slices/known-symmetric-cipher-algorithm
|
||||||
|
* @kind table
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Crypto::KeyOperationAlgorithmNode a
|
||||||
|
where a.getAlgorithmType() instanceof Crypto::KeyOpAlg::SymmetricCipherAlgorithm
|
||||||
|
select a, a.getAlgorithmName()
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @name Likely crypto API function
|
||||||
|
* @description Outputs functions that take in crypto configuration parameters but calls are not detected in source.
|
||||||
|
* @id java/quantum/slices/likely-crypto-api-function
|
||||||
|
* @kind problem
|
||||||
|
* @severity info
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
from Callable f, Parameter p, Crypto::OperationNode op
|
||||||
|
where
|
||||||
|
op.asElement().(Expr).getEnclosingCallable() = f and
|
||||||
|
op.getAnAlgorithmOrGenericSource().asElement() = p
|
||||||
|
select f,
|
||||||
|
"Likely crypto API function: Operation $@ configured by parameter $@ with no known configuring call",
|
||||||
|
op, op.toString(), p, p.toString()
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* @name Operations with unknown algorithm
|
||||||
|
* @description Outputs operations where the algorithm applied is unknown
|
||||||
|
* @id java/quantum/slices/operation-with-unknown-algorithm
|
||||||
|
* @kind problem
|
||||||
|
* @severity info
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
//TODO: can we have an unknown node concept?
|
||||||
|
from Crypto::OperationNode op, Element e, string msg
|
||||||
|
where
|
||||||
|
not exists(op.getAnAlgorithmOrGenericSource()) and
|
||||||
|
e = op.asElement() and
|
||||||
|
msg = "Operation with unconfigured algorithm (no known sources)."
|
||||||
|
or
|
||||||
|
exists(Crypto::GenericSourceNode n |
|
||||||
|
n = op.getAnAlgorithmOrGenericSource() and
|
||||||
|
e = n.asElement()
|
||||||
|
) and
|
||||||
|
msg = "Operation with unknown algorithm source: $@"
|
||||||
|
select op, msg, e, e.toString()
|
||||||
23
java/ql/src/experimental/quantum/PrintCBOMGraph.ql
Normal file
23
java/ql/src/experimental/quantum/PrintCBOMGraph.ql
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* @name Print CBOM Graph
|
||||||
|
* @description Outputs a graph representation of the cryptographic bill of materials.
|
||||||
|
* This query only supports DGML output, as CodeQL DOT output omits properties.
|
||||||
|
* @kind graph
|
||||||
|
* @id java/print-cbom-graph
|
||||||
|
* @tags quantum
|
||||||
|
* experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
import experimental.quantum.Language
|
||||||
|
|
||||||
|
query predicate nodes(Crypto::NodeBase node, string key, string value) {
|
||||||
|
Crypto::nodes_graph_impl(node, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
query predicate edges(Crypto::NodeBase source, Crypto::NodeBase target, string key, string value) {
|
||||||
|
Crypto::edges_graph_impl(source, target, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
query predicate graphProperties(string key, string value) {
|
||||||
|
key = "semmle.graphKind" and value = "graph"
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os.path
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
# Add Model as Data script directory to sys.path.
|
|
||||||
gitroot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip()
|
|
||||||
madpath = os.path.join(gitroot, "misc/scripts/models-as-data/")
|
|
||||||
sys.path.append(madpath)
|
|
||||||
|
|
||||||
import generate_flow_model as model
|
|
||||||
|
|
||||||
language = "java"
|
|
||||||
model.Generator.make(language).run()
|
|
||||||
@@ -35,8 +35,8 @@ def regenerateModel(lgtmSlug, extractedDb):
|
|||||||
sys.exit(1)
|
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)
|
||||||
|
|||||||
4
javascript/ql/lib/change-notes/2025-04-30-shelljs.md
Normal file
4
javascript/ql/lib/change-notes/2025-04-30-shelljs.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
category: minorAnalysis
|
||||||
|
---
|
||||||
|
* Improved modeling of the [`shelljs`](https://www.npmjs.com/package/shelljs) and [`async-shelljs`](https://www.npmjs.com/package/async-shelljs) libraries by adding support for the `which`, `cmd`, `asyncExec` and `env`.
|
||||||
6
javascript/ql/lib/ext/shelljs.model.yml
Normal file
6
javascript/ql/lib/ext/shelljs.model.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
extensions:
|
||||||
|
- addsTo:
|
||||||
|
pack: codeql/javascript-all
|
||||||
|
extensible: sourceModel
|
||||||
|
data:
|
||||||
|
- ["shelljs", "Member[env]", "environment"]
|
||||||
@@ -264,3 +264,7 @@ module Stage {
|
|||||||
cached
|
cached
|
||||||
predicate backref() { optionalStep(_, _, _) }
|
predicate backref() { optionalStep(_, _, _) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
predicate unsupportedCallable = Private::unsupportedCallable/1;
|
||||||
|
|
||||||
|
predicate unsupportedCallable = Private::unsupportedCallable/4;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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`. */
|
||||||
|
|||||||
@@ -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" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) |
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 |
|
||||||
|
|||||||
@@ -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 |
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 |
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
category: queryMetadata
|
||||||
|
---
|
||||||
|
|
||||||
|
* The precision of `rb/useless-assignment-to-local` has been adjusted from `medium` to `high`.
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
* @tags maintainability
|
* @tags maintainability
|
||||||
* quality
|
* quality
|
||||||
* external/cwe/cwe-563
|
* external/cwe/cwe-563
|
||||||
* @precision medium
|
* @precision high
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import codeql.ruby.AST
|
import codeql.ruby.AST
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ pkg_filegroup(
|
|||||||
srcs = [
|
srcs = [
|
||||||
":tools-arch",
|
":tools-arch",
|
||||||
"//rust/tools",
|
"//rust/tools",
|
||||||
|
"//rust/tools/builtins",
|
||||||
],
|
],
|
||||||
prefix = "tools",
|
prefix = "tools",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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_",
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user