Crypto: Reused nonce query updates and test updates to address false positives.

This commit is contained in:
REDMOND\brodes
2025-10-10 12:25:31 -04:00
parent fba80870a6
commit 758759a304
4 changed files with 115 additions and 17 deletions

View File

@@ -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
)
)
)
}

View File

@@ -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()

View File

@@ -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 |

View File

@@ -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();