mirror of
https://github.com/github/codeql.git
synced 2026-04-26 17:25:19 +02:00
Refactor instances and consumers + add JCA hashes
This commit is contained in:
@@ -205,7 +205,7 @@ module JCAModel {
|
||||
*
|
||||
* For example, in `Cipher.getInstance(algorithm)`, this class represents `algorithm`.
|
||||
*/
|
||||
class CipherGetInstanceAlgorithmArg extends Crypto::AlgorithmConsumer instanceof Expr {
|
||||
class CipherGetInstanceAlgorithmArg extends Crypto::AlgorithmValueConsumer instanceof Expr {
|
||||
CipherGetInstanceCall call;
|
||||
|
||||
CipherGetInstanceAlgorithmArg() { this = call.getAlgorithmArg() }
|
||||
@@ -218,7 +218,7 @@ module JCAModel {
|
||||
value = result.getValue()
|
||||
}
|
||||
|
||||
override Crypto::AlgorithmElement getAKnownAlgorithmSource() {
|
||||
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
|
||||
result.(CipherStringLiteralAlgorithmInstance).getConsumer() = this
|
||||
}
|
||||
}
|
||||
@@ -354,15 +354,17 @@ module JCAModel {
|
||||
|
||||
override Crypto::CipherOperationSubtype getCipherOperationSubtype() { result = mode }
|
||||
|
||||
override Crypto::NonceArtifactConsumer getNonceConsumer() {
|
||||
result = sink.getState().(InitializedCipherModeFlowState).getInitCall().getNonceArg()
|
||||
override DataFlow::Node getNonceConsumer() {
|
||||
result.asExpr() = sink.getState().(InitializedCipherModeFlowState).getInitCall().getNonceArg()
|
||||
}
|
||||
|
||||
override Crypto::CipherInputConsumer getInputConsumer() {
|
||||
result = doFinalize.getMessageArg().asExpr()
|
||||
override DataFlow::Node getInputConsumer() { result = doFinalize.getMessageArg() }
|
||||
|
||||
override DataFlow::Node getKeyConsumer() {
|
||||
result.asExpr() = sink.getState().(InitializedCipherModeFlowState).getInitCall().getKeyArg()
|
||||
}
|
||||
|
||||
override Crypto::AlgorithmConsumer getAlgorithmConsumer() { result = consumer }
|
||||
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = consumer }
|
||||
|
||||
override Crypto::CipherOutputArtifactInstance getOutputArtifact() {
|
||||
result = doFinalize.getOutput()
|
||||
@@ -493,27 +495,114 @@ module JCAModel {
|
||||
}
|
||||
}
|
||||
|
||||
class CipherInitCallNonceArgConsumer extends Crypto::NonceArtifactConsumer instanceof Expr {
|
||||
CipherInitCallNonceArgConsumer() { this = any(CipherInitCall call).getNonceArg() }
|
||||
|
||||
override DataFlow::Node getInputNode() { result.asExpr() = this }
|
||||
}
|
||||
|
||||
class CipherInitCallKeyConsumer extends Crypto::ArtifactConsumer {
|
||||
CipherInitCallKeyConsumer() { this = any(CipherInitCall call).getKeyArg() }
|
||||
|
||||
override DataFlow::Node getInputNode() { result.asExpr() = this }
|
||||
}
|
||||
|
||||
class CipherMessageInputConsumer extends Crypto::CipherInputConsumer {
|
||||
CipherMessageInputConsumer() { this = any(CipherOperationCall call).getMessageArg().asExpr() }
|
||||
|
||||
override DataFlow::Node getInputNode() { result.asExpr() = this }
|
||||
}
|
||||
|
||||
class CipherOperationCallOutput extends CipherOutputArtifact {
|
||||
CipherOperationCallOutput() { this = any(CipherOperationCall call).getOutput() }
|
||||
|
||||
override DataFlow::Node getOutputNode() { result.asExpr() = this }
|
||||
}
|
||||
|
||||
bindingset[hash]
|
||||
predicate hash_names(string hash) {
|
||||
hash.toUpperCase()
|
||||
.matches([
|
||||
"SHA-1", "SHA-256", "SHA-384", "SHA-512", "SHA3-224", "SHA3-256", "SHA3-384",
|
||||
"SHA3-512", "BLAKE2b", "BLAKE2s"
|
||||
].toUpperCase())
|
||||
}
|
||||
|
||||
// flow config from a known hash algorithm literal to MessageDigest.getInstance
|
||||
module KnownHashAlgorithmLiteralToMessageDigestConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node src) { hash_names(src.asExpr().(StringLiteral).getValue()) }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
exists(MessageDigestGetInstanceCall call | sink.asExpr() = call.getAlgorithmArg())
|
||||
}
|
||||
}
|
||||
|
||||
module KnownHashAlgorithmLiteralToMessageDigestFlow =
|
||||
DataFlow::Global<KnownHashAlgorithmLiteralToMessageDigestConfig>;
|
||||
|
||||
class KnownHashAlgorithm extends Crypto::HashAlgorithmInstance instanceof StringLiteral {
|
||||
MessageDigestAlgorithmValueConsumer consumer;
|
||||
|
||||
KnownHashAlgorithm() {
|
||||
hash_names(this.getValue()) and
|
||||
KnownHashAlgorithmLiteralToMessageDigestFlow::flow(DataFlow::exprNode(this),
|
||||
consumer.getInputNode())
|
||||
}
|
||||
|
||||
MessageDigestAlgorithmValueConsumer getConsumer() { result = consumer }
|
||||
|
||||
override string getRawAlgorithmName() { result = this.(StringLiteral).getValue() }
|
||||
|
||||
override Crypto::THashType getHashFamily() {
|
||||
result = Crypto::OtherHashType() // TODO
|
||||
}
|
||||
}
|
||||
|
||||
class MessageDigestAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer {
|
||||
MessageDigestGetInstanceCall call;
|
||||
|
||||
MessageDigestAlgorithmValueConsumer() { this = call.getAlgorithmArg() }
|
||||
|
||||
override DataFlow::Node getInputNode() { result.asExpr() = this }
|
||||
|
||||
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
|
||||
exists(KnownHashAlgorithm l | l.getConsumer() = this and result = l)
|
||||
}
|
||||
}
|
||||
|
||||
class MessageDigestGetInstanceCall extends MethodCall {
|
||||
MessageDigestGetInstanceCall() {
|
||||
this.getCallee().hasQualifiedName("java.security", "MessageDigest", "getInstance")
|
||||
}
|
||||
|
||||
Expr getAlgorithmArg() { result = this.getArgument(0) }
|
||||
|
||||
DigestHashOperation getDigestCall() {
|
||||
DigestGetInstanceToDigestFlow::flow(DataFlow::exprNode(this),
|
||||
DataFlow::exprNode(result.(DigestCall).getQualifier()))
|
||||
}
|
||||
}
|
||||
|
||||
class DigestCall extends MethodCall {
|
||||
DigestCall() { this.getCallee().hasQualifiedName("java.security", "MessageDigest", "digest") }
|
||||
|
||||
Expr getDigestArtifactOutput() { result = this }
|
||||
}
|
||||
|
||||
// flow config from MessageDigest.getInstance to MessageDigest.digest
|
||||
module DigestGetInstanceToDigestConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof MessageDigestGetInstanceCall }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
exists(DigestCall c | c.getQualifier() = sink.asExpr())
|
||||
}
|
||||
}
|
||||
|
||||
module DigestGetInstanceToDigestFlow = DataFlow::Global<DigestGetInstanceToDigestConfig>;
|
||||
|
||||
class DigestArtifact extends DigestArtifactInstance {
|
||||
DigestArtifact() { this = any(DigestCall call).getDigestArtifactOutput() }
|
||||
|
||||
override DataFlow::Node getOutputNode() { result.asExpr() = this }
|
||||
}
|
||||
|
||||
class DigestHashOperation extends Crypto::HashOperationInstance instanceof DigestCall {
|
||||
override Crypto::DigestArtifactInstance getDigestArtifact() {
|
||||
result = this.(DigestCall).getDigestArtifactOutput()
|
||||
}
|
||||
|
||||
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
|
||||
exists(MessageDigestGetInstanceCall call |
|
||||
call.getDigestCall() = this and result = call.getAlgorithmArg()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ module CryptoInput implements InputSig<Language::Location> {
|
||||
class LocatableElement = Language::Element;
|
||||
|
||||
class UnknownLocation = UnknownDefaultLocation;
|
||||
|
||||
LocatableElement dfn_to_element(DataFlow::Node node) {
|
||||
result = node.asExpr() or
|
||||
result = node.asParameter()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,6 +105,17 @@ class InsecureRandomnessInstance extends RandomnessInstance {
|
||||
InsecureRandomnessInstance() { exists(InsecureRandomnessSource node | this = node.asExpr()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Output artifact flow logic
|
||||
*/
|
||||
abstract class DigestArtifactInstance extends Crypto::DigestArtifactInstance {
|
||||
override predicate flowsTo(Crypto::FlowAwareElement other) {
|
||||
ArtifactUniversalFlow::flow(this.getOutputNode(), other.getInputNode())
|
||||
}
|
||||
|
||||
override predicate isConsumerArtifact() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Artifact output to node input configuration
|
||||
*/
|
||||
@@ -115,6 +131,8 @@ abstract class CipherOutputArtifact extends Crypto::CipherOutputArtifactInstance
|
||||
override predicate flowsTo(Crypto::FlowAwareElement other) {
|
||||
ArtifactUniversalFlow::flow(this.getOutputNode(), other.getInputNode())
|
||||
}
|
||||
|
||||
override predicate isConsumerArtifact() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,7 +162,7 @@ module GenericDataSourceUniversalFlowConfig implements DataFlow::ConfigSig {
|
||||
|
||||
module ArtifactUniversalFlowConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source = any(Crypto::ArtifactElement artifact).getOutputNode()
|
||||
source = any(Crypto::ArtifactInstance artifact).getOutputNode()
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
|
||||
@@ -6,17 +6,19 @@
|
||||
|
||||
import experimental.Quantum.Language
|
||||
|
||||
from Crypto::NonceNode n, Crypto::CipherOperationNode op, Crypto::FlowAwareElement src, string msg
|
||||
from
|
||||
Crypto::NonceArtifactNode n, Crypto::CipherOperationNode op, Crypto::FlowAwareElement src,
|
||||
string msg
|
||||
where
|
||||
op.getANonce() = n and
|
||||
// Only encryption mode is relevant for insecure nonces, consder any 'unknown' subtype
|
||||
// as possibly encryption.
|
||||
// as possibly encryption.
|
||||
(
|
||||
op.getCipherOperationSubtype() instanceof Crypto::EncryptionSubtype
|
||||
or
|
||||
op.getCipherOperationSubtype() instanceof Crypto::WrapSubtype
|
||||
or
|
||||
op.getCipherOperationSubtype() instanceof Crypto::UnwrapSubtype
|
||||
op.getCipherOperationSubtype() instanceof Crypto::UnwrapSubtype
|
||||
) and
|
||||
(
|
||||
// Known sources cases that are not secure
|
||||
@@ -30,7 +32,6 @@ where
|
||||
src = n.asElement()
|
||||
)
|
||||
select n, msg, src, src.toString()
|
||||
|
||||
// variant using instances, does not yield the same results
|
||||
// from Crypto::NonceArtifactConsumer n, Crypto::CipherOperationInstance op, Crypto::FlowAwareElement src, string msg
|
||||
// where
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
/**
|
||||
* @name Possible Nonce Reuse: Produces false positives if reuse occurs in a source that is a re-entry point.
|
||||
* @id java/possible-nonce-reuse
|
||||
* @kind problem
|
||||
*/
|
||||
|
||||
import experimental.Quantum.Language
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
|
||||
from
|
||||
Crypto::CipherOperationNode op1, Crypto::CipherOperationNode op2, Crypto::NonceNode nonce1,
|
||||
Crypto::NonceNode nonce2, Crypto::FlowAwareElement src1, Crypto::FlowAwareElement src2
|
||||
Crypto::CipherOperationNode op1, Crypto::CipherOperationNode op2,
|
||||
Crypto::NonceArtifactNode nonce1, Crypto::NonceArtifactNode nonce2, Crypto::FlowAwareElement src1,
|
||||
Crypto::FlowAwareElement src2
|
||||
where
|
||||
// NOTE: not looking at value of the nonce, if we knew value, it would be insecure (hard coded)
|
||||
// Instead trying to find nonce sources that trace to multiple operations.
|
||||
|
||||
@@ -6,7 +6,8 @@ import experimental.Quantum.Language
|
||||
|
||||
from
|
||||
Crypto::CipherOperationNode op, Crypto::CipherAlgorithmNode a,
|
||||
Crypto::ModeOfOperationAlgorithmNode m, Crypto::PaddingAlgorithmNode p, Crypto::NonceNode nonce
|
||||
Crypto::ModeOfOperationAlgorithmNode m, Crypto::PaddingAlgorithmNode p,
|
||||
Crypto::NonceArtifactNode nonce
|
||||
where
|
||||
a = op.getAKnownCipherAlgorithm() and
|
||||
m = a.getModeOfOperation() and
|
||||
|
||||
9
java/ql/src/experimental/Quantum/TestHash.ql
Normal file
9
java/ql/src/experimental/Quantum/TestHash.ql
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @name TestHashOperations
|
||||
*/
|
||||
|
||||
import experimental.Quantum.Language
|
||||
|
||||
from Crypto::HashOperationNode op, Crypto::HashAlgorithmNode alg
|
||||
where alg = op.getAKnownHashAlgorithm()
|
||||
select op, op.getDigest(), alg, alg.getRawAlgorithmName()
|
||||
Reference in New Issue
Block a user