Merge "Introduce Android lint checks around Binder.clearCallingIdentity()"
This commit is contained in:
committed by
Android (Google) Code Review
commit
99fbf6f106
@@ -21,6 +21,9 @@ java_defaults {
|
||||
// "-XepPatchLocation:/tmp/refaster/",
|
||||
],
|
||||
},
|
||||
lint: {
|
||||
extra_check_modules: ["AndroidFrameworkLintChecker"],
|
||||
},
|
||||
}
|
||||
|
||||
filegroup {
|
||||
|
||||
46
tools/lint/Android.bp
Normal file
46
tools/lint/Android.bp
Normal 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",
|
||||
// ],
|
||||
//}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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)`."
|
||||
}
|
||||
}
|
||||
@@ -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", "")
|
||||
}
|
||||
Reference in New Issue
Block a user