Skip to content

Commit ac186b8

Browse files
SONARJAVA-4895: Integrate custom IV byte array factories into byte array generator detection
1 parent f05d60f commit ac186b8

File tree

1 file changed

+62
-3
lines changed

1 file changed

+62
-3
lines changed

java-checks/src/main/java/org/sonar/java/checks/security/CipherBlockChainingCheck.java

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,29 @@
4242

4343
@Rule(key = "S3329")
4444
public class CipherBlockChainingCheck extends AbstractMethodDetection {
45-
private final SecureByteArrayGeneratorDetector secureByteArrayGeneratorDetector = new SecureByteArrayGeneratorDetector();
45+
// A note on detecting secure IV generation across procedures:
46+
//
47+
// This check is able to avoid FPs when secure IVs are generated in separate methods, with quite some limitations, for example
48+
// * nested calls can not be handled.
49+
// * calls to methods outside the current surrounding top-level class can not be handled.
50+
// * the flow of (secure) arguments can not be traced
51+
// * ...
52+
//
53+
// In other words, there will be FPs when secure IV generating methods are used that are defined across (top-level) classes or files.
54+
// This could be handled by doing a pass of SecureByteArrayFactoryFinder across all classes and files first, before performing
55+
// CipherBlockChainingCheck.
56+
//
57+
// However, this is likely not worth the additional complexity.
58+
// Instead, if we need to be more precise for S3329 across files and methods, we should switch to an analysis that is better suited for
59+
// following dataflows (either DBD-based, or a taint analysis).
60+
// These analyses are already cross-procedural and cross-file, can follow chains of calls, etc.
61+
//
62+
// Furthermore, we can not simply mute this check as soon as a call is involved in generating IVs because then we will lose a lot of the
63+
// cases that are already detected by previous iterations of this check.
64+
private final SecureByteArrayFactoryFinder secureByteArrayFactoryFinder = new SecureByteArrayFactoryFinder();
65+
private final SecureByteArrayGeneratorDetector secureByteArrayGeneratorDetector = new SecureByteArrayGeneratorDetector()
66+
.countParametersAsDynamic()
67+
.detectCustomFactories(secureByteArrayFactoryFinder);
4668

4769
private static final MethodMatchers SECURE_RANDOM_GENERATE_SEED = MethodMatchers.create()
4870
.ofTypes("java.security.SecureRandom")
@@ -209,16 +231,49 @@ private static Symbol ivSymbol(NewClassTree newClassTree) {
209231
}
210232

211233
private static class SecureByteArrayGeneratorDetector {
234+
private @Nullable SecureByteArrayFactoryFinder secureByteArrayFactoryFinder = null;
235+
private boolean shouldCountParametersAsDynamic = false;
236+
237+
/**
238+
* When called, this detector will treat all byte arrays produced by method calls to secure IV-generating functions as secure.
239+
* It uses the given {@link SecureByteArrayFactoryFinder} to judge whether a method call is secure.
240+
*/
241+
public SecureByteArrayGeneratorDetector detectCustomFactories(SecureByteArrayFactoryFinder secureByteArrayFactoryFinder) {
242+
this.secureByteArrayFactoryFinder = secureByteArrayFactoryFinder;
243+
return this;
244+
}
245+
246+
/**
247+
* If called, this detector will treat parameters as secure byte arrays.
248+
* This is optional, since we can not trace arguments across methods and hence can not always assume them to be secure.
249+
* However, for the methods where we see parameters being directly passed into IvParameterSpec, we do want to treat them as secure
250+
* to match the behaviour of previous iterations of CipherBlockChainingCheck
251+
*/
252+
public SecureByteArrayGeneratorDetector countParametersAsDynamic() {
253+
this.shouldCountParametersAsDynamic = true;
254+
return this;
255+
}
256+
212257
public boolean isDynamicallyGenerated(ExpressionTree tree) {
213-
if (tree instanceof IdentifierTree identifierTree) {
258+
if (shouldCountParametersAsDynamic && tree instanceof IdentifierTree identifierTree) {
214259
Symbol symbol = identifierTree.symbol();
215260
if (symbol.isParameter()) {
216261
return true;
217262
}
218263
}
219264

220265
return findConstructingMethods(tree)
221-
.anyMatch(SECURE_RANDOM_GENERATE_SEED::matches);
266+
.anyMatch(methodInvocationTree -> {
267+
if (SECURE_RANDOM_GENERATE_SEED.matches(methodInvocationTree)) {
268+
return true;
269+
}
270+
271+
if (secureByteArrayFactoryFinder == null) {
272+
return false;
273+
}
274+
275+
return secureByteArrayFactoryFinder.producesSecureBytesArray(methodInvocationTree);
276+
});
222277
}
223278

224279
private static Stream<MethodInvocationTree> findConstructingMethods(ExpressionTree expressionTree) {
@@ -257,6 +312,10 @@ private static Stream<MethodInvocationTree> findConstructingMethods(ExpressionTr
257312
private static class SecureByteArrayFactoryFinder extends BaseTreeVisitor {
258313
private @Nullable MethodTree currentMethodTree = null;
259314
private final Set<String> secureByteArrayFactories = new HashSet<>();
315+
// Note, that we do not call detectCustomFactories() on secureByteArrayGeneratorDetector.
316+
// Meaning, that we will not trace whether methods called inside IV factories are secure.
317+
// In other words, CipherBlockChainingCheck supports a call-depth of at most 1.
318+
// See also the note on cross-procedural FPs in the surrounding class.
260319
private final SecureByteArrayGeneratorDetector secureByteArrayGeneratorDetector = new SecureByteArrayGeneratorDetector();
261320

262321
public boolean producesSecureBytesArray(MethodInvocationTree methodInvocation) {

0 commit comments

Comments
 (0)