Merge "Introduce Android lint checks around Binder.clearCallingIdentity()"

This commit is contained in:
Azhara Assanova
2021-06-24 14:49:08 +00:00
committed by Android (Google) Code Review
5 changed files with 1308 additions and 0 deletions

View File

@@ -21,6 +21,9 @@ java_defaults {
// "-XepPatchLocation:/tmp/refaster/",
],
},
lint: {
extra_check_modules: ["AndroidFrameworkLintChecker"],
},
}
filegroup {

46
tools/lint/Android.bp Normal file
View File

@@ -0,0 +1,46 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
}
java_library_host {
name: "AndroidFrameworkLintChecker",
srcs: ["checks/src/main/java/**/*.kt"],
plugins: ["auto_service_plugin"],
libs: [
"auto_service_annotations",
"lint_api",
],
}
// TODO: (b/162368644) Implement these (working in gradle) Kotlin Tests to run on Soong
//java_test_host {
// name: "AndroidFrameworkLintCheckerTest",
// srcs: [
// "checks/src/test/java/**/*.kt",
// "checks/src/main/java/**/*.kt",
// ],
// plugins: ["auto_service_plugin"],
// static_libs: [
// "auto_service_annotations",
// "lint_api",
// ],
//}

View File

@@ -0,0 +1,288 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.lint
import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_NESTED_CLEAR_IDENTITY_CALLS
import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_NON_FINAL_TOKEN
import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK
import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_UNUSED_TOKEN
import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY
import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageNestedClearIdentityCallsPrimary
import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageNestedClearIdentityCallsSecondary
import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageNonFinalToken
import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageRestoreIdentityCallNotInFinallyBlock
import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageUnusedToken
import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Context
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Location
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.intellij.psi.PsiMethod
import com.intellij.psi.search.PsiSearchScopeUtil
import com.intellij.psi.search.SearchScope
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.ULocalVariable
import org.jetbrains.uast.UQualifiedReferenceExpression
import org.jetbrains.uast.USimpleNameReferenceExpression
import org.jetbrains.uast.UTryExpression
import org.jetbrains.uast.getParentOfType
import org.jetbrains.uast.isUastChildOf
/**
* Lint Detector that finds issues with improper usages of the token returned by
* Binder.clearCallingIdentity()
*/
@Suppress("UnstableApiUsage")
class CallingIdentityTokenDetector : Detector(), SourceCodeScanner {
private companion object {
const val CLASS_BINDER = "android.os.Binder"
const val CLASS_USER_HANDLE = "android.os.UserHandle"
@JvmField
val callerAwareMethods = listOf(
Method.BINDER_GET_CALLING_PID,
Method.BINDER_GET_CALLING_UID,
Method.BINDER_GET_CALLING_UID_OR_THROW,
Method.BINDER_GET_CALLING_USER_HANDLE,
Method.USER_HANDLE_GET_CALLING_APP_ID,
Method.USER_HANDLE_GET_CALLING_USER_ID
)
}
/** Map of <Token variable name, Token object> */
private val tokensMap = mutableMapOf<String, Token>()
override fun getApplicableUastTypes(): List<Class<out UElement?>> =
listOf(ULocalVariable::class.java, UQualifiedReferenceExpression::class.java)
override fun createUastHandler(context: JavaContext): UElementHandler =
TokenUastHandler(context)
/** File analysis starts with a clear map */
override fun beforeCheckFile(context: Context) {
tokensMap.clear()
}
/**
* - If tokensMap has tokens after checking the file -> reports all locations as unused token
* issue incidents
* - File analysis ends with a clear map
*/
override fun afterCheckFile(context: Context) {
for (token in tokensMap.values) {
context.report(
ISSUE_UNUSED_TOKEN,
token.location,
getIncidentMessageUnusedToken(token.variableName)
)
}
tokensMap.clear()
}
/** UAST handler that analyses elements and reports incidents */
private inner class TokenUastHandler(val context: JavaContext) : UElementHandler() {
/**
* For every variable initialization with Binder.clearCallingIdentity():
* - Checks for non-final token issue
* - Checks for unused token issue within different scopes
* - Checks for nested calls of clearCallingIdentity() issue
* - Stores token variable name, scope in the file and its location in tokensMap
*/
override fun visitLocalVariable(node: ULocalVariable) {
val rhsExpression = node.uastInitializer as? UQualifiedReferenceExpression ?: return
if (!isMethodCall(rhsExpression, Method.BINDER_CLEAR_CALLING_IDENTITY)) return
val location = context.getLocation(node as UElement)
val variableName = node.getName()
if (!node.isFinal) {
context.report(
ISSUE_NON_FINAL_TOKEN,
location,
getIncidentMessageNonFinalToken(variableName)
)
}
// If there exists an unused variable with the same name in the map, we can imply that
// we left the scope of the previous declaration, so we need to report the unused token
val oldToken = tokensMap[variableName]
if (oldToken != null) {
context.report(
ISSUE_UNUSED_TOKEN,
oldToken.location,
getIncidentMessageUnusedToken(oldToken.variableName)
)
}
// If there exists a token in the same scope as the current new token, it means that
// clearCallingIdentity() has been called at least twice without immediate restoration
// of identity, so we need to report the nested call of clearCallingIdentity()
val firstCallToken = findFirstTokenInScope(node)
if (firstCallToken != null) {
context.report(
ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
createNestedLocation(firstCallToken, location),
getIncidentMessageNestedClearIdentityCallsPrimary(
firstCallToken.variableName,
variableName
)
)
}
tokensMap[variableName] = Token(variableName, node.sourcePsi?.getUseScope(), location)
}
/**
* For every class.method():
* - Checks use of caller-aware methods issue
* For every call of Binder.restoreCallingIdentity(token):
* - Checks for restoreCallingIdentity() not in the finally block issue
* - Removes token from tokensMap if token is within the scope of the method
*/
override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression) {
val token = findFirstTokenInScope(node)
if (isCallerAwareMethod(node) && token != null) {
context.report(
ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
context.getLocation(node),
getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity(
token.variableName,
node.asRenderString()
)
)
return
}
if (!isMethodCall(node, Method.BINDER_RESTORE_CALLING_IDENTITY)) return
val selector = node.selector as UCallExpression
val arg = selector.valueArguments[0] as? USimpleNameReferenceExpression ?: return
val variableName = arg.identifier
if (!isInFinallyBlock(node)) {
context.report(
ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
context.getLocation(node),
getIncidentMessageRestoreIdentityCallNotInFinallyBlock(variableName)
)
}
val originalScope = tokensMap[variableName]?.scope ?: return
val psi = arg.sourcePsi ?: return
if (PsiSearchScopeUtil.isInScope(originalScope, psi)) {
tokensMap.remove(variableName)
}
}
private fun isCallerAwareMethod(expression: UQualifiedReferenceExpression): Boolean =
callerAwareMethods.any { method -> isMethodCall(expression, method) }
private fun isMethodCall(
expression: UQualifiedReferenceExpression,
method: Method
): Boolean {
val psiMethod = expression.resolve() as? PsiMethod ?: return false
return psiMethod.getName() == method.methodName &&
context.evaluator.methodMatches(
psiMethod,
method.className,
/* allowInherit */ true,
*method.args
)
}
}
private fun isInFinallyBlock(expression: UExpression): Boolean {
val tryExpression = expression.getParentOfType<UTryExpression>(strict = true)
?: return false
return expression.isUastChildOf(tryExpression.finallyClause)
}
private fun findFirstTokenInScope(node: UElement): Token? {
val psi = node.sourcePsi ?: return null
for (token in tokensMap.values) {
if (token.scope != null && PsiSearchScopeUtil.isInScope(token.scope, psi)) {
return token
}
}
return null
}
/**
* Creates a new instance of the primary location with the secondary location
*
* Here, secondary location is the helper location that shows where the issue originated
*
* The detector reports locations as objects, so when we add a secondary location to a location
* that has multiple issues, the secondary location gets displayed every time a location is
* referenced.
*
* Example:
* 1: final long token1 = Binder.clearCallingIdentity();
* 2: long token2 = Binder.clearCallingIdentity();
* 3: Binder.restoreCallingIdentity(token1);
* 4: Binder.restoreCallingIdentity(token2);
*
* Explanation:
* token2 has 2 issues: NonFinal and NestedCalls
*
* Lint report without cloning Lint report with cloning
* line 2: [NonFinalIssue] line 2: [NonFinalIssue]
* line 1: [NestedCallsIssue]
* line 2: [NestedCallsIssue] line 2: [NestedCallsIssue]
* line 1: [NestedCallsIssue] line 1: [NestedCallsIssue]
*/
private fun createNestedLocation(
firstCallToken: Token,
secondCallTokenLocation: Location
): Location {
return cloneLocation(secondCallTokenLocation)
.withSecondary(
cloneLocation(firstCallToken.location),
getIncidentMessageNestedClearIdentityCallsSecondary(
firstCallToken.variableName
)
)
}
private fun cloneLocation(location: Location): Location {
// smart cast of location.start to 'Position' is impossible, because 'location.start' is a
// public API property declared in different module
val locationStart = location.start
return if (locationStart == null) {
Location.create(location.file)
} else {
Location.create(location.file, locationStart, location.end)
}
}
private enum class Method(
val className: String,
val methodName: String,
val args: Array<String>
) {
BINDER_CLEAR_CALLING_IDENTITY(CLASS_BINDER, "clearCallingIdentity", emptyArray()),
BINDER_RESTORE_CALLING_IDENTITY(CLASS_BINDER, "restoreCallingIdentity", arrayOf("long")),
BINDER_GET_CALLING_PID(CLASS_BINDER, "getCallingPid", emptyArray()),
BINDER_GET_CALLING_UID(CLASS_BINDER, "getCallingUid", emptyArray()),
BINDER_GET_CALLING_UID_OR_THROW(CLASS_BINDER, "getCallingUidOrThrow", emptyArray()),
BINDER_GET_CALLING_USER_HANDLE(CLASS_BINDER, "getCallingUserHandle", emptyArray()),
USER_HANDLE_GET_CALLING_APP_ID(CLASS_USER_HANDLE, "getCallingAppId", emptyArray()),
USER_HANDLE_GET_CALLING_USER_ID(CLASS_USER_HANDLE, "getCallingUserId", emptyArray())
}
private data class Token(
val variableName: String,
val scope: SearchScope?,
val location: Location
)
}

View File

@@ -0,0 +1,224 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.lint
import com.android.tools.lint.client.api.IssueRegistry
// TODO: uncomment when lint API in Soong becomes 30.0+
// import com.android.tools.lint.client.api.Vendor
import com.android.tools.lint.detector.api.CURRENT_API
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.google.auto.service.AutoService
@AutoService(IssueRegistry::class)
@Suppress("UnstableApiUsage")
class CallingIdentityTokenIssueRegistry : IssueRegistry() {
override val issues = listOf(
ISSUE_UNUSED_TOKEN,
ISSUE_NON_FINAL_TOKEN,
ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY
)
override val api: Int
get() = CURRENT_API
override val minApi: Int
get() = 8
// TODO: uncomment when lint API in Soong becomes 30.0+
// override val vendor: Vendor = Vendor(
// vendorName = "Android Open Source Project",
// feedbackUrl = "http://b/issues/new?component=315013",
// contact = "brufino@google.com"
// )
companion object {
/** Issue: unused token from Binder.clearCallingIdentity() */
@JvmField
val ISSUE_UNUSED_TOKEN: Issue = Issue.create(
id = "UnusedTokenOfOriginalCallingIdentity",
briefDescription = "Unused token of Binder.clearCallingIdentity()",
explanation = """
You cleared the original calling identity with \
`Binder.clearCallingIdentity()`, but have not used the returned token to \
restore the identity.
Call `Binder.restoreCallingIdentity(token)` in the `finally` block, at the end \
of the method or when you need to restore the identity.
`token` is the result of `Binder.clearCallingIdentity()`
""",
category = Category.SECURITY,
priority = 6,
severity = Severity.WARNING,
implementation = Implementation(
CallingIdentityTokenDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
fun getIncidentMessageUnusedToken(variableName: String) = "`$variableName` has not been " +
"used to restore the calling identity. Call " +
"`Binder.restoreCallingIdentity($variableName)` or remove `$variableName`."
/** Issue: non-final token from Binder.clearCallingIdentity() */
@JvmField
val ISSUE_NON_FINAL_TOKEN: Issue = Issue.create(
id = "NonFinalTokenOfOriginalCallingIdentity",
briefDescription = "Non-final token of Binder.clearCallingIdentity()",
explanation = """
You cleared the original calling identity with \
`Binder.clearCallingIdentity()`, but have not made the returned token `final`.
The token should be `final` in order to prevent it from being overwritten, \
which can cause problems when restoring the identity with \
`Binder.restoreCallingIdentity(token)`.
""",
category = Category.SECURITY,
priority = 6,
severity = Severity.WARNING,
implementation = Implementation(
CallingIdentityTokenDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
fun getIncidentMessageNonFinalToken(variableName: String) = "`$variableName` is a " +
"non-final token from `Binder.clearCallingIdentity()`. Add `final` keyword to " +
"`$variableName`."
/** Issue: nested calls of Binder.clearCallingIdentity() */
@JvmField
val ISSUE_NESTED_CLEAR_IDENTITY_CALLS: Issue = Issue.create(
id = "NestedClearCallingIdentityCalls",
briefDescription = "Nested calls of Binder.clearCallingIdentity()",
explanation = """
You cleared the original calling identity with \
`Binder.clearCallingIdentity()` twice without restoring identity with the \
result of the first call.
Make sure to restore the identity after each clear identity call.
""",
category = Category.SECURITY,
priority = 6,
severity = Severity.WARNING,
implementation = Implementation(
CallingIdentityTokenDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
fun getIncidentMessageNestedClearIdentityCallsPrimary(
firstCallVariableName: String,
secondCallVariableName: String
): String = "The calling identity has already been cleared and returned into " +
"`$firstCallVariableName`. Move `$secondCallVariableName` declaration after " +
"restoring the calling identity with " +
"`Binder.restoreCallingIdentity($firstCallVariableName)`."
fun getIncidentMessageNestedClearIdentityCallsSecondary(
firstCallVariableName: String
): String = "Location of the `$firstCallVariableName` declaration."
/** Issue: Binder.restoreCallingIdentity() is not in finally block */
@JvmField
val ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK: Issue = Issue.create(
id = "RestoreIdentityCallNotInFinallyBlock",
briefDescription = "Binder.restoreCallingIdentity() is not in finally block",
explanation = """
You are restoring the original calling identity with \
`Binder.restoreCallingIdentity()`, but the call is not in the `finally` block \
of the `try` statement.
Use the following pattern for running operations with your own identity:
```
final long token = Binder.clearCallingIdentity();
try {
// Code using your own identity
} finally {
Binder.restoreCallingIdentity();
}
```
If you do not surround the code using your identity with the `try` statement \
and call `Binder.restoreCallingIdentity()` in the `finally` block, you may run \
code with your identity that was originally intended to run with the calling \
application's identity.
""",
category = Category.SECURITY,
priority = 6,
severity = Severity.WARNING,
implementation = Implementation(
CallingIdentityTokenDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
fun getIncidentMessageRestoreIdentityCallNotInFinallyBlock(variableName: String): String =
"`Binder.restoreCallingIdentity($variableName)` is not in the `finally` block. " +
"Surround the call with `finally` block."
/** Issue: Use of caller-aware methods after Binder.clearCallingIdentity() */
@JvmField
val ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY: Issue = Issue.create(
id = "UseOfCallerAwareMethodsWithClearedIdentity",
briefDescription = "Use of caller-aware methods after " +
"Binder.clearCallingIdentity()",
explanation = """
You cleared the original calling identity with \
`Binder.clearCallingIdentity()`, but used one of the methods below before \
restoring the identity. These methods will use your own identity instead of \
the caller's identity, so if this is expected replace them with methods that \
explicitly query your own identity such as `Process.myUid()`, \
`Process.myPid()` and `UserHandle.myUserId()`, otherwise move those methods \
out of the `Binder.clearCallingIdentity()` / `Binder.restoreCallingIdentity()` \
section.
```
Binder.getCallingPid()
Binder.getCallingUid()
Binder.getCallingUidOrThrow()
Binder.getCallingUserHandle()
UserHandle.getCallingAppId()
UserHandle.getCallingUserId()
```
""",
category = Category.SECURITY,
priority = 6,
severity = Severity.WARNING,
implementation = Implementation(
CallingIdentityTokenDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
fun getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity(
variableName: String,
methodName: String
): String = "You cleared the original identity with `Binder.clearCallingIdentity()` " +
"and returned into `$variableName`, so `$methodName` will be using your own " +
"identity instead of the caller's. Either explicitly query your own identity or " +
"move it after restoring the identity with " +
"`Binder.restoreCallingIdentity($variableName)`."
}
}

View File

@@ -0,0 +1,747 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
@Suppress("UnstableApiUsage")
class CallingIdentityTokenDetectorTest : LintDetectorTest() {
override fun getDetector(): Detector = CallingIdentityTokenDetector()
override fun getIssues(): List<Issue> = listOf(
CallingIdentityTokenIssueRegistry.ISSUE_UNUSED_TOKEN,
CallingIdentityTokenIssueRegistry.ISSUE_NON_FINAL_TOKEN,
CallingIdentityTokenIssueRegistry.ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
CallingIdentityTokenIssueRegistry.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
CallingIdentityTokenIssueRegistry
.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY
)
/** No issue scenario */
fun testDoesNotDetectIssuesInCorrectScenario() {
lint().files(
java(
"""
package test.pkg;
import android.os.Binder;
public class TestClass1 {
private void testMethod() {
final long token1 = Binder.clearCallingIdentity();
try {
} finally {
Binder.restoreCallingIdentity(token1);
}
final long token2 = android.os.Binder.clearCallingIdentity();
try {
} finally {
android.os.Binder.restoreCallingIdentity(token2);
}
}
}
"""
).indented(),
*stubs
)
.run()
.expectClean()
}
/** Unused token issue tests */
fun testDetectsUnusedTokens() {
lint().files(
java(
"""
package test.pkg;
import android.os.Binder;
public class TestClass1 {
private void testMethod1() {
final long token1 = Binder.clearCallingIdentity();
}
private void testMethod2() {
final long token2 = android.os.Binder.clearCallingIdentity();
}
}
"""
).indented(),
*stubs
)
.run()
.expect(
"""
src/test/pkg/TestClass1.java:5: Warning: token1 has not been used to \
restore the calling identity. Call Binder.restoreCallingIdentity(token1) \
or remove token1. [UnusedTokenOfOriginalCallingIdentity]
final long token1 = Binder.clearCallingIdentity();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:8: Warning: token2 has not been used to \
restore the calling identity. Call Binder.restoreCallingIdentity(token2) \
or remove token2. [UnusedTokenOfOriginalCallingIdentity]
final long token2 = android.os.Binder.clearCallingIdentity();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 2 warnings
""".addLineContinuation()
)
}
fun testDetectsUnusedTokensInScopes() {
lint().files(
java(
"""
package test.pkg;
import android.os.Binder;
public class TestClass1 {
private void testMethod1() {
final long token = Binder.clearCallingIdentity();
}
private void testMethod2() {
long token = 0;
try {
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
"""
).indented(),
*stubs
)
.run()
.expect(
"""
src/test/pkg/TestClass1.java:5: Warning: token has not been used to \
restore the calling identity. Call Binder.restoreCallingIdentity(token) \
or remove token. [UnusedTokenOfOriginalCallingIdentity]
final long token = Binder.clearCallingIdentity();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
""".addLineContinuation()
)
}
fun testDoesNotDetectUsedTokensInScopes() {
lint().files(
java(
"""
package test.pkg;
import android.os.Binder;
public class TestClass1 {
private void testMethod1() {
final long token = Binder.clearCallingIdentity();
try {
} finally {
Binder.restoreCallingIdentity(token);
}
}
private void testMethod2() {
long token = 0;
try {
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
"""
).indented(),
*stubs
)
.run()
.expectClean()
}
fun testDetectsUnusedTokensWithSimilarNamesInScopes() {
lint().files(
java(
"""
package test.pkg;
import android.os.Binder;
public class TestClass1 {
private void testMethod1() {
final long token = Binder.clearCallingIdentity();
try {
} finally {
}
}
private void testMethod2() {
final long token = Binder.clearCallingIdentity();
try {
} finally {
}
}
}
"""
).indented(),
*stubs
)
.run()
.expect(
"""
src/test/pkg/TestClass1.java:5: Warning: token has not been used to \
restore the calling identity. Call Binder.restoreCallingIdentity(token) \
or remove token. [UnusedTokenOfOriginalCallingIdentity]
final long token = Binder.clearCallingIdentity();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:11: Warning: token has not been used to \
restore the calling identity. Call Binder.restoreCallingIdentity(token) \
or remove token. [UnusedTokenOfOriginalCallingIdentity]
final long token = Binder.clearCallingIdentity();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 2 warnings
""".addLineContinuation()
)
}
/** Non-final token issue tests */
fun testDetectsNonFinalTokens() {
lint().files(
java(
"""
package test.pkg;
import android.os.Binder;
public class TestClass1 {
private void testMethod() {
long token1 = Binder.clearCallingIdentity();
try {
} finally {
Binder.restoreCallingIdentity(token1);
}
long token2 = android.os.Binder.clearCallingIdentity();
try {
} finally {
android.os.Binder.restoreCallingIdentity(token2);
}
}
}
"""
).indented(),
*stubs
)
.run()
.expect(
"""
src/test/pkg/TestClass1.java:5: Warning: token1 is a non-final token from \
Binder.clearCallingIdentity(). Add final keyword to token1. \
[NonFinalTokenOfOriginalCallingIdentity]
long token1 = Binder.clearCallingIdentity();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:10: Warning: token2 is a non-final token from \
Binder.clearCallingIdentity(). Add final keyword to token2. \
[NonFinalTokenOfOriginalCallingIdentity]
long token2 = android.os.Binder.clearCallingIdentity();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 2 warnings
""".addLineContinuation()
)
}
/** Nested clearCallingIdentity() calls issue tests */
fun testDetectsNestedClearCallingIdentityCallsPattern1() {
// Pattern: clear - clear - restore - clear - restore - restore
lint().files(
java(
"""
package test.pkg;
import android.os.Binder;
public class TestClass1 {
private void testMethod() {
final long token1 = Binder.clearCallingIdentity();
final long token2 = android.os.Binder.clearCallingIdentity();
try {
} finally {
android.os.Binder.restoreCallingIdentity(token1);
}
final long token3 = android.os.Binder.clearCallingIdentity();
try {
} finally {
android.os.Binder.restoreCallingIdentity(token2);
}
try {
} finally {
android.os.Binder.restoreCallingIdentity(token3);
}
}
}
"""
).indented(),
*stubs
)
.run()
.expect(
"""
src/test/pkg/TestClass1.java:6: Warning: The calling identity has already \
been cleared and returned into token1. Move token2 declaration after \
restoring the calling identity with Binder.restoreCallingIdentity(token1). \
[NestedClearCallingIdentityCalls]
final long token2 = android.os.Binder.clearCallingIdentity();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:5: Location of the token1 declaration.
src/test/pkg/TestClass1.java:11: Warning: The calling identity has already \
been cleared and returned into token2. Move token3 declaration after \
restoring the calling identity with Binder.restoreCallingIdentity(token2). \
[NestedClearCallingIdentityCalls]
final long token3 = android.os.Binder.clearCallingIdentity();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:6: Location of the token2 declaration.
0 errors, 2 warnings
""".addLineContinuation()
)
}
fun testDetectsNestedClearCallingIdentityCallsPattern2() {
// Pattern: clear - clear - clear - restore - restore - restore
lint().files(
java(
"""
package test.pkg;
import android.os.Binder;
public class TestClass1 {
private void testMethod() {
final long token1 = Binder.clearCallingIdentity();
final long token2 = android.os.Binder.clearCallingIdentity();
final long token3 = android.os.Binder.clearCallingIdentity();
try {
} finally {
Binder.restoreCallingIdentity(token1);
}
try {
} finally {
android.os.Binder.restoreCallingIdentity(token2);
}
try {
} finally {
android.os.Binder.restoreCallingIdentity(token3);
}
}
}
"""
).indented(),
*stubs
)
.run()
.expect(
"""
src/test/pkg/TestClass1.java:6: Warning: The calling identity has already \
been cleared and returned into token1. Move token2 declaration after \
restoring the calling identity with Binder.restoreCallingIdentity(token1). \
[NestedClearCallingIdentityCalls]
final long token2 = android.os.Binder.clearCallingIdentity();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:5: Location of the token1 declaration.
src/test/pkg/TestClass1.java:7: Warning: The calling identity has already \
been cleared and returned into token1. Move token3 declaration after \
restoring the calling identity with Binder.restoreCallingIdentity(token1). \
[NestedClearCallingIdentityCalls]
final long token3 = android.os.Binder.clearCallingIdentity();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:5: Location of the token1 declaration.
0 errors, 2 warnings
""".addLineContinuation()
)
}
fun testDetectsNestedClearCallingIdentityCallsInScopes() {
lint().files(
java(
"""
package test.pkg;
import android.os.Binder;
public class TestClass1 {
private void testMethod() {
final long token1 = Binder.clearCallingIdentity();
try {
final long token2 = android.os.Binder.clearCallingIdentity();
try {
} finally {
Binder.restoreCallingIdentity(token2);
}
} finally {
android.os.Binder.restoreCallingIdentity(token1);
}
}
}
"""
).indented(),
*stubs
)
.run()
.expect(
"""
src/test/pkg/TestClass1.java:7: Warning: The calling identity has already \
been cleared and returned into token1. Move token2 declaration after \
restoring the calling identity with Binder.restoreCallingIdentity(token1). \
[NestedClearCallingIdentityCalls]
final long token2 = android.os.Binder.clearCallingIdentity();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:5: Location of the token1 declaration.
0 errors, 1 warnings
""".addLineContinuation()
)
}
/** restoreCallingIdentity() call not in finally block issue tests */
fun testDetectsRestoreCallingIdentityCallNotInFinally() {
lint().files(
java(
"""
package test.pkg;
import android.os.Binder;
public class TestClass1 {
private void testMethod1() {
final long token = Binder.clearCallingIdentity();
try {
} catch (Exception e) {
}
Binder.restoreCallingIdentity(token);
}
private void testMethod2() {
final long token = android.os.Binder.clearCallingIdentity();
try {
} finally {
}
android.os.Binder.restoreCallingIdentity(token);
}
}
"""
).indented(),
*stubs
)
.run()
.expect(
"""
src/test/pkg/TestClass1.java:9: Warning: \
Binder.restoreCallingIdentity(token) is not in the finally block. Surround \
the call with finally block. [RestoreIdentityCallNotInFinallyBlock]
Binder.restoreCallingIdentity(token);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:16: Warning: \
Binder.restoreCallingIdentity(token) is not in the finally block. Surround \
the call with finally block. [RestoreIdentityCallNotInFinallyBlock]
android.os.Binder.restoreCallingIdentity(token);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 2 warnings
""".addLineContinuation()
)
}
fun testDetectsRestoreCallingIdentityCallNotInFinallyInScopes() {
lint().files(
java(
"""
package test.pkg;
import android.os.Binder;
public class TestClass1 {
private void testMethod1() {
final long token = Binder.clearCallingIdentity();
try {
} catch (Exception e) {
}
{
Binder.restoreCallingIdentity(token);
}
}
private void testMethod2() {
final long token = android.os.Binder.clearCallingIdentity();
try {
} finally {
}
{
{
{
android.os.Binder.restoreCallingIdentity(token);
}
}
}
}
}
"""
).indented(),
*stubs
)
.run()
.expect(
"""
src/test/pkg/TestClass1.java:10: Warning: \
Binder.restoreCallingIdentity(token) is not in the finally block. Surround \
the call with finally block. [RestoreIdentityCallNotInFinallyBlock]
Binder.restoreCallingIdentity(token);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:21: Warning: \
Binder.restoreCallingIdentity(token) is not in the finally block. Surround \
the call with finally block. [RestoreIdentityCallNotInFinallyBlock]
android.os.Binder.restoreCallingIdentity(token);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 2 warnings
""".addLineContinuation()
)
}
fun testDoesNotDetectRestoreCallingIdentityCallInFinallyInScopes() {
lint().files(
java(
"""
package test.pkg;
import android.os.Binder;
public class TestClass1 {
private void testMethod() {
final long token1 = Binder.clearCallingIdentity();
try {
} finally {
{
{
Binder.restoreCallingIdentity(token1);
}
}
}
final long token2 = android.os.Binder.clearCallingIdentity();
try {
} finally {
{
{
android.os.Binder.restoreCallingIdentity(token2);
}
}
}
}
}
"""
).indented(),
*stubs
)
.run()
.expectClean()
}
/** Use of caller-aware methods after clearCallingIdentity() issue tests */
fun testDetectsUseOfCallerAwareMethodsWithClearedIdentityIssuesInScopes() {
lint().files(
java(
"""
package test.pkg;
import android.os.Binder;
import android.os.UserHandle;
public class TestClass1 {
private void testMethod() {
final long token = Binder.clearCallingIdentity();
int pid1 = Binder.getCallingPid();
int pid2 = android.os.Binder.getCallingPid();
int uid1 = Binder.getCallingUid();
int uid2 = android.os.Binder.getCallingUid();
try {
int uid3 = Binder.getCallingUidOrThrow();
int uid4 = android.os.Binder.getCallingUidOrThrow();
UserHandle uh1 = Binder.getCallingUserHandle();
UserHandle uh2 = android.os.Binder.getCallingUserHandle();
{
int appId1 = UserHandle.getCallingAppId();
int appId2 = android.os.UserHandle.getCallingAppId();
int userId1 = UserHandle.getCallingUserId();
int userId2 = android.os.UserHandle.getCallingUserId();
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
"""
).indented(),
*stubs
)
.run()
.expect(
"""
src/test/pkg/TestClass1.java:7: Warning: You cleared the original identity \
with Binder.clearCallingIdentity() and returned into token, so \
Binder.getCallingPid() will be using your own identity instead of the \
caller's. Either explicitly query your own identity or move it after \
restoring the identity with Binder.restoreCallingIdentity(token). \
[UseOfCallerAwareMethodsWithClearedIdentity]
int pid1 = Binder.getCallingPid();
~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:8: Warning: You cleared the original identity \
with Binder.clearCallingIdentity() and returned into token, so \
android.os.Binder.getCallingPid() will be using your own identity instead \
of the caller's. Either explicitly query your own identity or move it \
after restoring the identity with Binder.restoreCallingIdentity(token). \
[UseOfCallerAwareMethodsWithClearedIdentity]
int pid2 = android.os.Binder.getCallingPid();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:9: Warning: You cleared the original identity \
with Binder.clearCallingIdentity() and returned into token, so \
Binder.getCallingUid() will be using your own identity instead of the \
caller's. Either explicitly query your own identity or move it after \
restoring the identity with Binder.restoreCallingIdentity(token). \
[UseOfCallerAwareMethodsWithClearedIdentity]
int uid1 = Binder.getCallingUid();
~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:10: Warning: You cleared the original \
identity with Binder.clearCallingIdentity() and returned into token, so \
android.os.Binder.getCallingUid() will be using your own identity instead \
of the caller's. Either explicitly query your own identity or move it \
after restoring the identity with Binder.restoreCallingIdentity(token). \
[UseOfCallerAwareMethodsWithClearedIdentity]
int uid2 = android.os.Binder.getCallingUid();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:12: Warning: You cleared the original \
identity with Binder.clearCallingIdentity() and returned into token, so \
Binder.getCallingUidOrThrow() will be using your own identity instead of \
the caller's. Either explicitly query your own identity or move it after \
restoring the identity with Binder.restoreCallingIdentity(token). \
[UseOfCallerAwareMethodsWithClearedIdentity]
int uid3 = Binder.getCallingUidOrThrow();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:13: Warning: You cleared the original \
identity with Binder.clearCallingIdentity() and returned into token, so \
android.os.Binder.getCallingUidOrThrow() will be using your own identity \
instead of the caller's. Either explicitly query your own identity or move \
it after restoring the identity with Binder.restoreCallingIdentity(token). \
[UseOfCallerAwareMethodsWithClearedIdentity]
int uid4 = android.os.Binder.getCallingUidOrThrow();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:14: Warning: You cleared the original \
identity with Binder.clearCallingIdentity() and returned into token, so \
Binder.getCallingUserHandle() will be using your own identity instead of \
the caller's. Either explicitly query your own identity or move it after \
restoring the identity with Binder.restoreCallingIdentity(token). \
[UseOfCallerAwareMethodsWithClearedIdentity]
UserHandle uh1 = Binder.getCallingUserHandle();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:15: Warning: You cleared the original \
identity with Binder.clearCallingIdentity() and returned into token, so \
android.os.Binder.getCallingUserHandle() will be using your own identity \
instead of the caller's. Either explicitly query your own identity or move \
it after restoring the identity with Binder.restoreCallingIdentity(token). \
[UseOfCallerAwareMethodsWithClearedIdentity]
UserHandle uh2 = android.os.Binder.getCallingUserHandle();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:17: Warning: You cleared the original \
identity with Binder.clearCallingIdentity() and returned into token, so \
UserHandle.getCallingAppId() will be using your own identity instead of \
the caller's. Either explicitly query your own identity or move it after \
restoring the identity with Binder.restoreCallingIdentity(token). \
[UseOfCallerAwareMethodsWithClearedIdentity]
int appId1 = UserHandle.getCallingAppId();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:18: Warning: You cleared the original \
identity with Binder.clearCallingIdentity() and returned into token, so \
android.os.UserHandle.getCallingAppId() will be using your own identity \
instead of the caller's. Either explicitly query your own identity or move \
it after restoring the identity with Binder.restoreCallingIdentity(token). \
[UseOfCallerAwareMethodsWithClearedIdentity]
int appId2 = android.os.UserHandle.getCallingAppId();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:19: Warning: You cleared the original \
identity with Binder.clearCallingIdentity() and returned into token, so \
UserHandle.getCallingUserId() will be using your own identity instead of \
the caller's. Either explicitly query your own identity or move it after \
restoring the identity with Binder.restoreCallingIdentity(token). \
[UseOfCallerAwareMethodsWithClearedIdentity]
int userId1 = UserHandle.getCallingUserId();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/test/pkg/TestClass1.java:20: Warning: You cleared the original \
identity with Binder.clearCallingIdentity() and returned into token, so \
android.os.UserHandle.getCallingUserId() will be using your own identity \
instead of the caller's. Either explicitly query your own identity or move \
it after restoring the identity with Binder.restoreCallingIdentity(token). \
[UseOfCallerAwareMethodsWithClearedIdentity]
int userId2 = android.os.UserHandle.getCallingUserId();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 12 warnings
""".addLineContinuation()
)
}
/** Stubs for classes used for testing */
private val binderStub: TestFile = java(
"""
package android.os;
public class Binder {
public static final native long clearCallingIdentity() {
return 0;
}
public static final native void restoreCallingIdentity(long token) {
}
public static final native int getCallingPid() {
return 0;
}
public static final native int getCallingUid() {
return 0;
}
public static final int getCallingUidOrThrow() {
return 0;
}
public static final @NonNull UserHandle getCallingUserHandle() {
return UserHandle.of(UserHandle.getUserId(getCallingUid()));
}
}
"""
).indented()
private val userHandleStub: TestFile = java(
"""
package android.os;
import android.annotation.AppIdInt;
import android.annotation.UserIdInt;
public class UserHandle {
public static @AppIdInt int getCallingAppId() {
return getAppId(Binder.getCallingUid());
}
public static @UserIdInt int getCallingUserId() {
return getUserId(Binder.getCallingUid());
}
public static @UserIdInt int getUserId(int uid) {
return 0;
}
public static @AppIdInt int getAppId(int uid) {
return 0;
}
public static UserHandle of(@UserIdInt int userId) {
return new UserHandle();
}
}
"""
).indented()
private val userIdIntStub: TestFile = java(
"""
package android.annotation;
public @interface UserIdInt {
}
"""
).indented()
private val appIdIntStub: TestFile = java(
"""
package android.annotation;
public @interface AppIdInt {
}
"""
).indented()
private val stubs = arrayOf(binderStub, userHandleStub, userIdIntStub, appIdIntStub)
// Substitutes "backslash + new line" with an empty string to imitate line continuation
private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")
}