Use inline-expectation tests for StaticInitializationVector.ql

This commit is contained in:
Artem Smotrakov
2021-07-17 01:04:52 +02:00
parent 218731ca0a
commit cfe74b527a
7 changed files with 190 additions and 184 deletions

View File

@@ -17,173 +17,9 @@
*/
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.TaintTracking2
import experimental.semmle.code.java.security.StaticInitializationVectorQuery
import DataFlow::PathGraph
/**
* Holds if `array` is initialized only with constants, for example,
* `new byte[8]` or `new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }`.
*/
private predicate initializedWithConstants(ArrayCreationExpr array) {
not exists(array.getInit())
or
forex(Expr element | element = array.getInit().getAChildExpr() |
element instanceof CompileTimeConstantExpr
)
}
/**
* An expression that creates a byte array that is initialized with constants.
*/
private class StaticByteArrayCreation extends ArrayCreationExpr {
StaticByteArrayCreation() {
this.getType().(Array).getElementType().(PrimitiveType).getName() = "byte" and
initializedWithConstants(this)
}
}
/** Defines a sub-set of expressions that update an array. */
private class ArrayUpdate extends Expr {
Expr array;
ArrayUpdate() {
exists(Assignment assign, ArrayAccess arrayAccess | arrayAccess = assign.getDest() |
assign = this and
arrayAccess.getArray() = array and
not assign.getSource() instanceof CompileTimeConstantExpr
)
or
exists(StaticMethodAccess ma |
ma.getMethod().hasQualifiedName("java.lang", "System", "arraycopy") and
ma = this and
ma.getArgument(2) = array
)
or
exists(StaticMethodAccess ma |
ma.getMethod().hasQualifiedName("java.util", "Arrays", "copyOf") and
ma = this and
ma = array
)
or
exists(MethodAccess ma, Method m |
m = ma.getMethod() and
ma = this and
ma.getArgument(0) = array
|
m.hasQualifiedName("java.io", "InputStream", "read") or
m.hasQualifiedName("java.nio", "ByteBuffer", "get") or
m.hasQualifiedName("java.security", "SecureRandom", "nextBytes") or
m.hasQualifiedName("java.util", "Random", "nextBytes")
)
}
/** Returns the updated array. */
Expr getArray() { result = array }
}
/**
* A config that tracks dataflow from creating an array to an operation that updates it.
*/
private class ArrayUpdateConfig extends TaintTracking2::Configuration {
ArrayUpdateConfig() { this = "ArrayUpdateConfig" }
override predicate isSource(DataFlow::Node source) {
source.asExpr() instanceof StaticByteArrayCreation
}
override predicate isSink(DataFlow::Node sink) {
exists(ArrayUpdate update | update.getArray() = sink.asExpr())
}
}
/**
* A source that defines an array that doesn't get updated.
*/
private class StaticInitializationVectorSource extends DataFlow::Node {
StaticInitializationVectorSource() {
exists(StaticByteArrayCreation array | array = this.asExpr() |
not exists(ArrayUpdate update, ArrayUpdateConfig config |
config.hasFlow(DataFlow2::exprNode(array), DataFlow2::exprNode(update.getArray()))
)
)
}
}
/**
* A config that tracks initialization of a cipher for encryption.
*/
private class EncryptionModeConfig extends TaintTracking2::Configuration {
EncryptionModeConfig() { this = "EncryptionModeConfig" }
override predicate isSource(DataFlow::Node source) {
source.asExpr().(VarAccess).getVariable().hasName("ENCRYPT_MODE")
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
m.hasQualifiedName("javax.crypto", "Cipher", "init") and
ma.getArgument(0) = sink.asExpr()
)
}
}
/**
* A sink that initializes a cipher for encryption with unsafe parameters.
*/
private class EncryptionInitializationSink extends DataFlow::Node {
EncryptionInitializationSink() {
exists(MethodAccess ma, Method m, EncryptionModeConfig config | m = ma.getMethod() |
m.hasQualifiedName("javax.crypto", "Cipher", "init") and
m.getParameterType(2)
.(RefType)
.hasQualifiedName("java.security.spec", "AlgorithmParameterSpec") and
ma.getArgument(2) = this.asExpr() and
config.hasFlowToExpr(ma.getArgument(0))
)
}
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step
* that creates cipher's parameters with initialization vector.
*/
private predicate createInitializationVectorSpecStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(ConstructorCall cc, RefType type |
cc = toNode.asExpr() and type = cc.getConstructedType()
|
type.hasQualifiedName("javax.crypto.spec", "IvParameterSpec") and
cc.getArgument(0) = fromNode.asExpr()
or
type.hasQualifiedName("javax.crypto.spec", ["GCMParameterSpec", "RC2ParameterSpec"]) and
cc.getArgument(1) = fromNode.asExpr()
or
type.hasQualifiedName("javax.crypto.spec", "RC5ParameterSpec") and
cc.getArgument(3) = fromNode.asExpr()
)
}
/**
* A config that tracks dataflow to initializing a cipher with a static initialization vector.
*/
private class StaticInitializationVectorConfig extends TaintTracking::Configuration {
StaticInitializationVectorConfig() { this = "StaticInitializationVectorConfig" }
override predicate isSource(DataFlow::Node source) {
source instanceof StaticInitializationVectorSource
}
override predicate isSink(DataFlow::Node sink) { sink instanceof EncryptionInitializationSink }
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
createInitializationVectorSpecStep(fromNode, toNode)
}
override predicate isSanitizer(DataFlow::Node node) {
exists(ArrayUpdate update | update.getArray() = node.asExpr())
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, StaticInitializationVectorConfig conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "A $@ should not be used for encryption.", source.getNode(),

View File

@@ -0,0 +1,166 @@
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.TaintTracking2
/**
* Holds if `array` is initialized only with constants, for example,
* `new byte[8]` or `new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }`.
*/
private predicate initializedWithConstants(ArrayCreationExpr array) {
not exists(array.getInit())
or
forex(Expr element | element = array.getInit().getAChildExpr() |
element instanceof CompileTimeConstantExpr
)
}
/**
* An expression that creates a byte array that is initialized with constants.
*/
private class StaticByteArrayCreation extends ArrayCreationExpr {
StaticByteArrayCreation() {
this.getType().(Array).getElementType().(PrimitiveType).getName() = "byte" and
initializedWithConstants(this)
}
}
/** Defines a sub-set of expressions that update an array. */
private class ArrayUpdate extends Expr {
Expr array;
ArrayUpdate() {
exists(Assignment assign, ArrayAccess arrayAccess | arrayAccess = assign.getDest() |
assign = this and
arrayAccess.getArray() = array and
not assign.getSource() instanceof CompileTimeConstantExpr
)
or
exists(StaticMethodAccess ma |
ma.getMethod().hasQualifiedName("java.lang", "System", "arraycopy") and
ma = this and
ma.getArgument(2) = array
)
or
exists(StaticMethodAccess ma |
ma.getMethod().hasQualifiedName("java.util", "Arrays", "copyOf") and
ma = this and
ma = array
)
or
exists(MethodAccess ma, Method m |
m = ma.getMethod() and
ma = this and
ma.getArgument(0) = array
|
m.hasQualifiedName("java.io", "InputStream", "read") or
m.hasQualifiedName("java.nio", "ByteBuffer", "get") or
m.hasQualifiedName("java.security", "SecureRandom", "nextBytes") or
m.hasQualifiedName("java.util", "Random", "nextBytes")
)
}
/** Returns the updated array. */
Expr getArray() { result = array }
}
/**
* A config that tracks dataflow from creating an array to an operation that updates it.
*/
private class ArrayUpdateConfig extends TaintTracking2::Configuration {
ArrayUpdateConfig() { this = "ArrayUpdateConfig" }
override predicate isSource(DataFlow::Node source) {
source.asExpr() instanceof StaticByteArrayCreation
}
override predicate isSink(DataFlow::Node sink) {
exists(ArrayUpdate update | update.getArray() = sink.asExpr())
}
}
/**
* A source that defines an array that doesn't get updated.
*/
private class StaticInitializationVectorSource extends DataFlow::Node {
StaticInitializationVectorSource() {
exists(StaticByteArrayCreation array | array = this.asExpr() |
not exists(ArrayUpdate update, ArrayUpdateConfig config |
config.hasFlow(DataFlow2::exprNode(array), DataFlow2::exprNode(update.getArray()))
)
)
}
}
/**
* A config that tracks initialization of a cipher for encryption.
*/
private class EncryptionModeConfig extends TaintTracking2::Configuration {
EncryptionModeConfig() { this = "EncryptionModeConfig" }
override predicate isSource(DataFlow::Node source) {
source.asExpr().(VarAccess).getVariable().hasName("ENCRYPT_MODE")
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
m.hasQualifiedName("javax.crypto", "Cipher", "init") and
ma.getArgument(0) = sink.asExpr()
)
}
}
/**
* A sink that initializes a cipher for encryption with unsafe parameters.
*/
private class EncryptionInitializationSink extends DataFlow::Node {
EncryptionInitializationSink() {
exists(MethodAccess ma, Method m, EncryptionModeConfig config | m = ma.getMethod() |
m.hasQualifiedName("javax.crypto", "Cipher", "init") and
m.getParameterType(2)
.(RefType)
.hasQualifiedName("java.security.spec", "AlgorithmParameterSpec") and
ma.getArgument(2) = this.asExpr() and
config.hasFlowToExpr(ma.getArgument(0))
)
}
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step
* that creates cipher's parameters with initialization vector.
*/
private predicate createInitializationVectorSpecStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(ConstructorCall cc, RefType type |
cc = toNode.asExpr() and type = cc.getConstructedType()
|
type.hasQualifiedName("javax.crypto.spec", "IvParameterSpec") and
cc.getArgument(0) = fromNode.asExpr()
or
type.hasQualifiedName("javax.crypto.spec", ["GCMParameterSpec", "RC2ParameterSpec"]) and
cc.getArgument(1) = fromNode.asExpr()
or
type.hasQualifiedName("javax.crypto.spec", "RC5ParameterSpec") and
cc.getArgument(3) = fromNode.asExpr()
)
}
/**
* A config that tracks dataflow to initializing a cipher with a static initialization vector.
*/
class StaticInitializationVectorConfig extends TaintTracking::Configuration {
StaticInitializationVectorConfig() { this = "StaticInitializationVectorConfig" }
override predicate isSource(DataFlow::Node source) {
source instanceof StaticInitializationVectorSource
}
override predicate isSink(DataFlow::Node sink) { sink instanceof EncryptionInitializationSink }
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
createInitializationVectorSpecStep(fromNode, toNode)
}
override predicate isSanitizer(DataFlow::Node node) {
exists(ArrayUpdate update | update.getArray() = node.asExpr())
}
}

View File

@@ -1,15 +0,0 @@
edges
| StaticInitializationVector.java:13:21:13:81 | new byte[] : byte[] | StaticInitializationVector.java:19:51:19:56 | ivSpec |
| StaticInitializationVector.java:26:21:26:32 | new byte[] : byte[] | StaticInitializationVector.java:32:51:32:56 | ivSpec |
| StaticInitializationVector.java:39:21:39:32 | new byte[] : byte[] | StaticInitializationVector.java:48:51:48:56 | ivSpec |
nodes
| StaticInitializationVector.java:13:21:13:81 | new byte[] : byte[] | semmle.label | new byte[] : byte[] |
| StaticInitializationVector.java:19:51:19:56 | ivSpec | semmle.label | ivSpec |
| StaticInitializationVector.java:26:21:26:32 | new byte[] : byte[] | semmle.label | new byte[] : byte[] |
| StaticInitializationVector.java:32:51:32:56 | ivSpec | semmle.label | ivSpec |
| StaticInitializationVector.java:39:21:39:32 | new byte[] : byte[] | semmle.label | new byte[] : byte[] |
| StaticInitializationVector.java:48:51:48:56 | ivSpec | semmle.label | ivSpec |
#select
| StaticInitializationVector.java:19:51:19:56 | ivSpec | StaticInitializationVector.java:13:21:13:81 | new byte[] : byte[] | StaticInitializationVector.java:19:51:19:56 | ivSpec | A $@ should not be used for encryption. | StaticInitializationVector.java:13:21:13:81 | new byte[] | static initialization vector |
| StaticInitializationVector.java:32:51:32:56 | ivSpec | StaticInitializationVector.java:26:21:26:32 | new byte[] : byte[] | StaticInitializationVector.java:32:51:32:56 | ivSpec | A $@ should not be used for encryption. | StaticInitializationVector.java:26:21:26:32 | new byte[] | static initialization vector |
| StaticInitializationVector.java:48:51:48:56 | ivSpec | StaticInitializationVector.java:39:21:39:32 | new byte[] : byte[] | StaticInitializationVector.java:48:51:48:56 | ivSpec | A $@ should not be used for encryption. | StaticInitializationVector.java:39:21:39:32 | new byte[] | static initialization vector |

View File

@@ -16,7 +16,7 @@ public class StaticInitializationVector {
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); // $staticInitializationVector
cipher.update(plaintext);
return cipher.doFinal();
}
@@ -29,7 +29,7 @@ public class StaticInitializationVector {
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); // $staticInitializationVector
cipher.update(plaintext);
return cipher.doFinal();
}
@@ -45,7 +45,7 @@ public class StaticInitializationVector {
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); // $staticInitializationVector
cipher.update(plaintext);
return cipher.doFinal();
}

View File

@@ -1 +0,0 @@
experimental/Security/CWE/CWE-1204/StaticInitializationVector.ql

View File

@@ -0,0 +1,20 @@
import java
import experimental.semmle.code.java.security.StaticInitializationVectorQuery
import TestUtilities.InlineExpectationsTest
class StaticInitializationVectorTest extends InlineExpectationsTest {
StaticInitializationVectorTest() { this = "StaticInitializationVectorTest" }
override string getARelevantTag() { result = "staticInitializationVector" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "staticInitializationVector" and
exists(DataFlow::Node src, DataFlow::Node sink, StaticInitializationVectorConfig conf |
conf.hasFlow(src, sink)
|
sink.getLocation() = location and
element = sink.toString() and
value = ""
)
}
}