Skip to content

Commit 9ed5814

Browse files
SONARKT-164 S6293: Fix false-negatives for android.hardware.biometrics.BiometricPrompt (#136)
1 parent 94aead3 commit 9ed5814

File tree

9 files changed

+84
-60
lines changed

9 files changed

+84
-60
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package android.hardware.biometrics;
2+
3+
import android.os.CancellationSignal;
4+
5+
import java.util.concurrent.Executor;
6+
7+
public class BiometricPrompt {
8+
public static class AuthenticationCallback {
9+
10+
}
11+
12+
public static final class CryptoObject {
13+
14+
}
15+
16+
public void authenticate(CancellationSignal cancel, Executor executor, AuthenticationCallback callback) {
17+
18+
}
19+
20+
public void authenticate(CryptoObject crypto, CancellationSignal cancel, Executor executor, AuthenticationCallback callback) {
21+
22+
}
23+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package android.os;
2+
3+
public class Bundle {
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package android.os;
2+
3+
public class CancellationSignal {
4+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package androidx.biometric;
2+
3+
public class BiometricPrompt {
4+
public static class CryptoObject {
5+
6+
}
7+
8+
public static class PromptInfo {
9+
10+
}
11+
12+
public void authenticate(PromptInfo info, CryptoObject crypto) {
13+
}
14+
15+
public void authenticate(PromptInfo info) {
16+
}
17+
}

kotlin-checks-test-sources/src/main/kotlin/android/hardware/biometrics/BiometricPrompt.kt

Lines changed: 0 additions & 10 deletions
This file was deleted.

kotlin-checks-test-sources/src/main/kotlin/android/os/Bundle.kt

Lines changed: 0 additions & 5 deletions
This file was deleted.

kotlin-checks-test-sources/src/main/kotlin/androidx/biometric/BiometricPrompt.kt

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
package checks
22

3+
import android.os.CancellationSignal
34
import android.hardware.biometrics.BiometricPrompt as BiometricPromptAndroid
45
import androidx.biometric.BiometricPrompt as BiometricPromptAndroidX
56

6-
val promptInfo = ""
7-
val cipher = ""
7+
val promptInfo = androidx.biometric.BiometricPrompt.PromptInfo()
88

99
fun android() {
1010
val biometricPrompt = BiometricPromptAndroid()
1111

12-
biometricPrompt.authenticate(promptInfo) // Noncompliant {{Make sure performing a biometric authentication without a "CryptoObject" is safe here.}}
12+
biometricPrompt.authenticate(CancellationSignal(), { }, BiometricPromptAndroid.AuthenticationCallback()) // Noncompliant {{Make sure performing a biometric authentication without a "CryptoObject" is safe here.}}
13+
1314
// Noncompliant@+1
14-
biometricPrompt.authenticate(promptInfo, null)
15-
// ^^^^^^^^^^^^> ^^^^
15+
biometricPrompt.authenticate (null, CancellationSignal(), { }, BiometricPromptAndroid.AuthenticationCallback())
16+
// ^^^^^^^^^^^^> ^^^^
1617

17-
biometricPrompt.authenticate(promptInfo, BiometricPromptAndroid.CryptoObject(cipher)) // Compliant
18+
biometricPrompt.authenticate(BiometricPromptAndroid.CryptoObject(), CancellationSignal(), { }, BiometricPromptAndroid.AuthenticationCallback()) // Compliant
1819
}
1920

2021
fun androidx() {
@@ -25,5 +26,5 @@ fun androidx() {
2526
biometricPrompt.authenticate(promptInfo, null)
2627
// ^^^^^^^^^^^^> ^^^^
2728

28-
biometricPrompt.authenticate(promptInfo, BiometricPromptAndroidX.CryptoObject(cipher)) // Compliant
29+
biometricPrompt.authenticate(promptInfo, BiometricPromptAndroidX.CryptoObject()) // Compliant
2930
}

sonar-kotlin-plugin/src/main/java/org/sonarsource/kotlin/checks/BiometricAuthWithoutCryptoCheck.kt

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,48 +22,48 @@ package org.sonarsource.kotlin.checks
2222
import org.jetbrains.kotlin.psi.KtCallExpression
2323
import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression
2424
import org.jetbrains.kotlin.psi.psiUtil.isNull
25-
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
25+
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
2626
import org.sonar.check.Rule
27-
import org.sonarsource.kotlin.api.AbstractCheck
27+
import org.sonarsource.kotlin.api.CallAbstractCheck
2828
import org.sonarsource.kotlin.api.FunMatcher
29+
import org.sonarsource.kotlin.api.FunMatcherImpl
2930
import org.sonarsource.kotlin.api.SecondaryLocation
30-
import org.sonarsource.kotlin.api.matches
3131
import org.sonarsource.kotlin.converter.KotlinTextRanges.textRange
3232
import org.sonarsource.kotlin.plugin.KotlinFileContext
3333

34-
private val AUTHENTICATE_FUN_MATCHERS = listOf(
35-
FunMatcher(qualifier = "android.hardware.biometrics.BiometricPrompt", name = "authenticate"),
36-
FunMatcher(qualifier = "androidx.biometric.BiometricPrompt", name = "authenticate")
37-
)
34+
private val ANDROID_HARDWARE_AUTH = FunMatcher(qualifier = "android.hardware.biometrics.BiometricPrompt", name = "authenticate")
35+
private val ANDROIDX_AUTH = FunMatcher(qualifier = "androidx.biometric.BiometricPrompt", name = "authenticate")
3836

3937
private const val MESSAGE = """Make sure performing a biometric authentication without a "CryptoObject" is safe here."""
4038

4139
@Rule(key = "S6293")
42-
class BiometricAuthWithoutCryptoCheck : AbstractCheck() {
40+
class BiometricAuthWithoutCryptoCheck : CallAbstractCheck() {
41+
override val functionsToVisit = setOf(ANDROID_HARDWARE_AUTH, ANDROIDX_AUTH)
4342

44-
override fun visitCallExpression(callExpression: KtCallExpression, ctx: KotlinFileContext) {
45-
if (functionCallMatches(callExpression, ctx)) {
46-
if (callExpression.valueArguments.size < 2) {
47-
48-
// No second argument -> automatically insecure.
49-
ctx.reportIssue(callExpression, MESSAGE)
50-
51-
} else {
43+
override fun visitFunctionCall(
44+
callExpression: KtCallExpression,
45+
resolvedCall: ResolvedCall<*>,
46+
matchedFun: FunMatcherImpl,
47+
kotlinFileContext: KotlinFileContext,
48+
) {
49+
when(matchedFun) {
50+
ANDROID_HARDWARE_AUTH -> checkCall(callExpression, kotlinFileContext, 4, 0)
51+
ANDROIDX_AUTH -> checkCall(callExpression, kotlinFileContext, 2, 1)
52+
}
53+
}
5254

53-
// Second argument is null? Also insecure.
54-
callExpression.valueArguments[1].getArgumentExpression()?.let { relevantArg ->
55-
if (relevantArg.isNull()) {
56-
val secondaryExpression = callExpression.getCallNameExpression() ?: callExpression
57-
ctx.reportIssue(relevantArg, MESSAGE, listOf(SecondaryLocation(ctx.textRange(secondaryExpression))))
58-
}
55+
private fun checkCall(callExpression: KtCallExpression, ctx: KotlinFileContext, numberOfSafeArgs: Int, argumentIndex: Int) {
56+
if (callExpression.valueArguments.size < numberOfSafeArgs) {
57+
// No CryptoObject -> automatically insecure.
58+
ctx.reportIssue(callExpression, MESSAGE)
59+
} else {
60+
// CryptoObject is null -> also insecure.
61+
callExpression.valueArguments[argumentIndex].getArgumentExpression()?.let { relevantArg ->
62+
if (relevantArg.isNull()) {
63+
val secondaryExpression = callExpression.getCallNameExpression() ?: callExpression
64+
ctx.reportIssue(relevantArg, MESSAGE, listOf(SecondaryLocation(ctx.textRange(secondaryExpression))))
5965
}
60-
6166
}
6267
}
6368
}
64-
65-
private fun functionCallMatches(callExpression: KtCallExpression, ctx: KotlinFileContext) =
66-
callExpression.getResolvedCall(ctx.bindingContext)?.let { resolvedCall ->
67-
AUTHENTICATE_FUN_MATCHERS.any { resolvedCall matches it }
68-
} ?: false
6969
}

0 commit comments

Comments
 (0)