mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Crypto: Reused nonce query updates and test updates to address false positives.
This commit is contained in:
@@ -8,7 +8,7 @@ import experimental.quantum.Language
|
||||
* 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 {
|
||||
private module ArtifactGeneratingWrapperConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr() instanceof Call and
|
||||
// not handling references yet, I think we want to flat say references are only ok
|
||||
@@ -28,25 +28,41 @@ private module WrapperConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(Crypto::ArtifactNode i).asElement() }
|
||||
}
|
||||
|
||||
module WrapperFlow = DataFlow::Global<WrapperConfig>;
|
||||
module ArtifactGeneratingWrapperFlow = TaintTracking::Global<ArtifactGeneratingWrapperConfig>;
|
||||
|
||||
/**
|
||||
* 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()))
|
||||
private DataFlow::Node getGeneratingWrapperSet(Crypto::NonceArtifactNode a) {
|
||||
ArtifactGeneratingWrapperFlow::flow(result, DataFlow::exprNode(a.asElement()))
|
||||
or
|
||||
result.asExpr() = a.getSourceElement()
|
||||
}
|
||||
|
||||
private predicate ancestorOfArtifact(
|
||||
Crypto::ArtifactNode a, Callable enclosingCallable, ControlFlow::Node midOrTarget
|
||||
) {
|
||||
a.asElement().(Expr).getEnclosingCallable() = enclosingCallable and
|
||||
(
|
||||
midOrTarget.asExpr() = a.asElement() or
|
||||
midOrTarget.asExpr().(Call).getCallee().calls*(a.asElement().(Expr).getEnclosingCallable())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
* 3. For all 'wrappers' that return the source of artifact `a`, and each wrapper also exists for artifact `b`.
|
||||
* 4. For all 'wrappers' that return the source of artifact `b`, and each wrapper also exists for artifact `a`.
|
||||
*
|
||||
* The above conditions determine that the use of the IV is from the same source, but the use may
|
||||
* be on separate code paths that do not occur sequentially. We must therefore also find a common callable ancestor
|
||||
* for both uses, and in that ancestor, there must be control flow from the call or use of one to the call or use of the other.
|
||||
* Note that if no shared ancestor callable exists, it means the flow is more nuanced and ignore the shared ancestor
|
||||
* use flow.
|
||||
*/
|
||||
predicate isArtifactReuse(Crypto::ArtifactNode a, Crypto::ArtifactNode b) {
|
||||
a != b and
|
||||
@@ -55,12 +71,32 @@ predicate isArtifactReuse(Crypto::ArtifactNode a, Crypto::ArtifactNode b) {
|
||||
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)
|
||||
forex(DataFlow::Node e | e = getGeneratingWrapperSet(a) |
|
||||
exists(DataFlow::Node e2 | e2 = getGeneratingWrapperSet(b) | e = e2)
|
||||
)
|
||||
or
|
||||
forex(DataFlow::Node e | e = getWrapperSet(b) |
|
||||
exists(DataFlow::Node e2 | e2 = getWrapperSet(a) | e = e2)
|
||||
forex(DataFlow::Node e | e = getGeneratingWrapperSet(b) |
|
||||
exists(DataFlow::Node e2 | e2 = getGeneratingWrapperSet(a) | e = e2)
|
||||
)
|
||||
) and
|
||||
// If there is a common parent, there is control flow between the two uses in the parent
|
||||
// TODO: this logic needs to be examined/revisited to ensure it is correct
|
||||
(
|
||||
exists(Callable commonParent |
|
||||
ancestorOfArtifact(a, commonParent, _) and
|
||||
ancestorOfArtifact(b, commonParent, _)
|
||||
)
|
||||
implies
|
||||
exists(Callable commonParent, ControlFlow::Node aMid, ControlFlow::Node bMid |
|
||||
ancestorOfArtifact(a, commonParent, aMid) and
|
||||
ancestorOfArtifact(b, commonParent, bMid) and
|
||||
a instanceof Crypto::NonceArtifactNode and
|
||||
b instanceof Crypto::NonceArtifactNode and
|
||||
(
|
||||
aMid.getASuccessor*() = bMid
|
||||
or
|
||||
bMid.getASuccessor*() = aMid
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,40 @@
|
||||
import java
|
||||
import ArtifactReuse
|
||||
|
||||
from Crypto::NonceArtifactNode nonce1, Crypto::NonceArtifactNode nonce2
|
||||
where isArtifactReuse(nonce1, nonce2)
|
||||
select nonce1, "Reuse with nonce $@", nonce2, nonce2.toString()
|
||||
from Crypto::NonceArtifactNode nonce1, Crypto::NonceArtifactNode nonce2, Crypto::NodeBase sourceNode
|
||||
where
|
||||
isArtifactReuse(nonce1, nonce2) and
|
||||
// NOTE: in general we may not know a source, but see possible reuse,
|
||||
// we are not detecting these cases here (only where the source is the same).
|
||||
sourceNode = nonce1.getSourceNode() and
|
||||
sourceNode = nonce2.getSourceNode() and
|
||||
// Null literals are typically used for initialization, and if two 'nulls'
|
||||
// are reused, it is likely an uninitialization path that would result in a NullPointerException.
|
||||
not sourceNode.asElement() instanceof NullLiteral and
|
||||
// if the nonce is used in an encryption and decryption, ignore that reuse
|
||||
not exists(Crypto::CipherOperationNode op1, Crypto::CipherOperationNode op2 |
|
||||
op1 != op2 and
|
||||
op1.getANonce() = nonce1 and
|
||||
op2.getANonce() = nonce2 and
|
||||
(
|
||||
(
|
||||
op1.getKeyOperationSubtype() instanceof Crypto::TEncryptMode or
|
||||
op1.getKeyOperationSubtype() instanceof Crypto::TWrapMode
|
||||
) and
|
||||
(
|
||||
op2.getKeyOperationSubtype() instanceof Crypto::TDecryptMode or
|
||||
op2.getKeyOperationSubtype() instanceof Crypto::TUnwrapMode
|
||||
)
|
||||
or
|
||||
(
|
||||
op2.getKeyOperationSubtype() instanceof Crypto::TEncryptMode or
|
||||
op2.getKeyOperationSubtype() instanceof Crypto::TWrapMode
|
||||
) and
|
||||
(
|
||||
op1.getKeyOperationSubtype() instanceof Crypto::TDecryptMode or
|
||||
op1.getKeyOperationSubtype() instanceof Crypto::TUnwrapMode
|
||||
)
|
||||
)
|
||||
)
|
||||
select sourceNode, "Nonce source is reused, see $@ and $@", nonce1, nonce1.toString(), nonce2,
|
||||
nonce2.toString()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
| Test.java:40:47:40:52 | Nonce | Reuse with nonce $@ | Test.java:49:47:49:52 | Nonce | Nonce |
|
||||
| Test.java:49:47:49:52 | Nonce | Reuse with nonce $@ | Test.java:40:47:40:52 | Nonce | Nonce |
|
||||
| Test.java:76:48:76:54 | Nonce | Reuse with nonce $@ | Test.java:82:49:82:55 | Nonce | Nonce |
|
||||
| Test.java:82:49:82:55 | Nonce | Reuse with nonce $@ | Test.java:76:48:76:54 | Nonce | Nonce |
|
||||
| Test.java:19:38:19:40 | RandomNumberGeneration | Nonce source is reused, see $@ and $@ | Test.java:40:47:40:52 | Nonce | Nonce | Test.java:49:47:49:52 | Nonce | Nonce |
|
||||
| Test.java:19:38:19:40 | RandomNumberGeneration | Nonce source is reused, see $@ and $@ | Test.java:49:47:49:52 | Nonce | Nonce | Test.java:40:47:40:52 | Nonce | Nonce |
|
||||
| Test.java:19:38:19:40 | RandomNumberGeneration | Nonce source is reused, see $@ and $@ | Test.java:76:48:76:54 | Nonce | Nonce | Test.java:82:49:82:55 | Nonce | Nonce |
|
||||
| Test.java:19:38:19:40 | RandomNumberGeneration | Nonce source is reused, see $@ and $@ | Test.java:82:49:82:55 | Nonce | Nonce | Test.java:76:48:76:54 | Nonce | Nonce |
|
||||
|
||||
@@ -83,6 +83,34 @@ public class Test {
|
||||
byte[] ciphertext2 = cipher2.doFinal("Simple Test Data".getBytes());
|
||||
}
|
||||
|
||||
public void falsePositive1() throws Exception {
|
||||
byte[] iv = null;
|
||||
new SecureRandom().nextBytes(iv);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
SecretKey key = generateAESKey();
|
||||
if (iv != null) {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec); // GOOD
|
||||
byte[] ciphertext = cipher.doFinal("Simple Test Data".getBytes());
|
||||
} else if(iv.length > 0) {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec); // GOOD
|
||||
byte[] ciphertext = cipher.doFinal("Simple Test Data".getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
public void falsePositive2() throws Exception {
|
||||
byte[] iv = null;
|
||||
new SecureRandom().nextBytes(iv);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
SecretKey key = generateAESKey();
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec); // GOOD
|
||||
byte[] ciphertext = cipher.doFinal("Simple Test Data".getBytes());
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec); // GOOD
|
||||
byte[] decryptedData = cipher.doFinal(ciphertext);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
funcA2();
|
||||
|
||||
Reference in New Issue
Block a user