• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.android.lint
18 
19 import com.android.tools.lint.client.api.UElementHandler
20 import com.android.tools.lint.detector.api.Category
21 import com.android.tools.lint.detector.api.Context
22 import com.android.tools.lint.detector.api.Detector
23 import com.android.tools.lint.detector.api.Implementation
24 import com.android.tools.lint.detector.api.Issue
25 import com.android.tools.lint.detector.api.JavaContext
26 import com.android.tools.lint.detector.api.Location
27 import com.android.tools.lint.detector.api.Scope
28 import com.android.tools.lint.detector.api.Severity
29 import com.android.tools.lint.detector.api.SourceCodeScanner
30 import com.intellij.psi.search.PsiSearchScopeUtil
31 import com.intellij.psi.search.SearchScope
32 import org.jetbrains.uast.UBlockExpression
33 import org.jetbrains.uast.UCallExpression
34 import org.jetbrains.uast.UDeclarationsExpression
35 import org.jetbrains.uast.UElement
36 import org.jetbrains.uast.UIfExpression
37 import org.jetbrains.uast.ULocalVariable
38 import org.jetbrains.uast.USimpleNameReferenceExpression
39 import org.jetbrains.uast.UTryExpression
40 import org.jetbrains.uast.getParentOfType
41 import org.jetbrains.uast.getQualifiedParentOrThis
42 import org.jetbrains.uast.getUCallExpression
43 import org.jetbrains.uast.skipParenthesizedExprDown
44 import org.jetbrains.uast.skipParenthesizedExprUp
45 
46 /**
47  * Lint Detector that finds issues with improper usages of the token returned by
48  * Binder.clearCallingIdentity()
49  */
50 @Suppress("UnstableApiUsage")
51 class CallingIdentityTokenDetector : Detector(), SourceCodeScanner {
52     /** Map of <Token variable name, Token object> */
53     private val tokensMap = mutableMapOf<String, Token>()
54 
55     override fun getApplicableUastTypes(): List<Class<out UElement?>> =
56         listOf(ULocalVariable::class.java, UCallExpression::class.java)
57 
58     override fun createUastHandler(context: JavaContext): UElementHandler =
59         TokenUastHandler(context)
60 
61     /** File analysis starts with a clear map */
62     override fun beforeCheckFile(context: Context) {
63         tokensMap.clear()
64     }
65 
66     /**
67      * - If tokensMap has tokens after checking the file -> reports all locations as unused token
68      * issue incidents
69      * - File analysis ends with a clear map
70      */
71     override fun afterCheckFile(context: Context) {
72         for (token in tokensMap.values) {
73             context.report(
74                 ISSUE_UNUSED_TOKEN,
75                 token.location,
76                 getIncidentMessageUnusedToken(token.variableName)
77             )
78         }
79         tokensMap.clear()
80     }
81 
82     /** UAST handler that analyses elements and reports incidents */
83     private inner class TokenUastHandler(val context: JavaContext) : UElementHandler() {
84         /**
85          * For every variable initialization with Binder.clearCallingIdentity():
86          * - Checks for non-final token issue
87          * - Checks for unused token issue within different scopes
88          * - Checks for nested calls of clearCallingIdentity() issue
89          * - Checks for clearCallingIdentity() not followed by try-finally issue
90          * - Stores token variable name, scope in the file, location and finally block in tokensMap
91          */
92         override fun visitLocalVariable(node: ULocalVariable) {
93             val initializer = node.uastInitializer?.skipParenthesizedExprDown()
94             val rhsExpression = initializer?.getUCallExpression() ?: return
95             if (!isMethodCall(rhsExpression, Method.BINDER_CLEAR_CALLING_IDENTITY)) return
96             val location = context.getLocation(node as UElement)
97             val variableName = node.getName()
98             if (!node.isFinal) {
99                 context.report(
100                     ISSUE_NON_FINAL_TOKEN,
101                     location,
102                     getIncidentMessageNonFinalToken(variableName)
103                 )
104             }
105             // If there exists an unused variable with the same name in the map, we can imply that
106             // we left the scope of the previous declaration, so we need to report the unused token
107             val oldToken = tokensMap[variableName]
108             if (oldToken != null) {
109                 context.report(
110                     ISSUE_UNUSED_TOKEN,
111                     oldToken.location,
112                     getIncidentMessageUnusedToken(oldToken.variableName)
113                 )
114             }
115             // If there exists a token in the same scope as the current new token, it means that
116             // clearCallingIdentity() has been called at least twice without immediate restoration
117             // of identity, so we need to report the nested call of clearCallingIdentity()
118             val firstCallToken = findFirstTokenInScope(node)
119             if (firstCallToken != null) {
120                 context.report(
121                     ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
122                     createNestedLocation(firstCallToken, location),
123                     getIncidentMessageNestedClearIdentityCallsPrimary(
124                         firstCallToken.variableName,
125                         variableName
126                     )
127                 )
128             }
129             // If the next statement in the tree is not a try-finally statement, we need to report
130             // the "clearCallingIdentity() is not followed by try-finally" issue
131             val finallyClause = (getNextStatementOfLocalVariable(node) as? UTryExpression)
132                 ?.finallyClause
133             if (finallyClause == null) {
134                 context.report(
135                     ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY,
136                     location,
137                     getIncidentMessageClearIdentityCallNotFollowedByTryFinally(variableName)
138                 )
139             }
140             tokensMap[variableName] = Token(
141                 variableName,
142                 node.sourcePsi?.getUseScope(),
143                 location,
144                 finallyClause
145             )
146         }
147 
148         override fun visitCallExpression(node: UCallExpression) {
149             when {
150                 isMethodCall(node, Method.BINDER_CLEAR_CALLING_IDENTITY) -> {
151                     checkClearCallingIdentityCall(node)
152                 }
153                 isMethodCall(node, Method.BINDER_RESTORE_CALLING_IDENTITY) -> {
154                     checkRestoreCallingIdentityCall(node)
155                 }
156                 isCallerAwareMethod(node) -> checkCallerAwareMethod(node)
157             }
158         }
159 
160         private fun checkClearCallingIdentityCall(node: UCallExpression) {
161             var firstNonQualifiedParent = getFirstNonQualifiedParent(node)
162             // if the call expression is inside a ternary, and the ternary is assigned
163             // to a variable, then we are still technically assigning
164             // any result of clearCallingIdentity to a variable
165             if (firstNonQualifiedParent is UIfExpression && firstNonQualifiedParent.isTernary) {
166                 firstNonQualifiedParent = firstNonQualifiedParent.uastParent
167             }
168             if (firstNonQualifiedParent !is ULocalVariable) {
169                 context.report(
170                     ISSUE_RESULT_OF_CLEAR_IDENTITY_CALL_NOT_STORED_IN_VARIABLE,
171                     context.getLocation(node),
172                     getIncidentMessageResultOfClearIdentityCallNotStoredInVariable(
173                         node.getQualifiedParentOrThis().asRenderString()
174                     )
175                 )
176             }
177         }
178 
179         private fun checkCallerAwareMethod(node: UCallExpression) {
180             val token = findFirstTokenInScope(node)
181             if (token != null) {
182                 context.report(
183                     ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
184                     context.getLocation(node),
185                     getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity(
186                         token.variableName,
187                         node.asRenderString()
188                     )
189                 )
190             }
191         }
192 
193         /**
194          * - Checks for restoreCallingIdentity() not in the finally block issue
195          * - Removes token from tokensMap if token is within the scope of the method
196          */
197         private fun checkRestoreCallingIdentityCall(node: UCallExpression) {
198             val arg = node.valueArguments[0] as? USimpleNameReferenceExpression ?: return
199             val variableName = arg.identifier
200             val originalScope = tokensMap[variableName]?.scope ?: return
201             val psi = arg.sourcePsi ?: return
202             // Checks if Binder.restoreCallingIdentity(token) is called within the scope of the
203             // token declaration. If not within the scope, no action is needed because the token is
204             // irrelevant i.e. not in the same scope or was not declared with clearCallingIdentity()
205             if (!PsiSearchScopeUtil.isInScope(originalScope, psi)) return
206             // We do not report "restore identity call not in finally" issue when there is no
207             // finally block because that case is already handled by "clear identity call not
208             // followed by try-finally" issue
209             if (tokensMap[variableName]?.finallyBlock != null &&
210                 getFirstNonQualifiedParent(node) !=
211                 tokensMap[variableName]?.finallyBlock
212             ) {
213                 context.report(
214                     ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
215                     context.getLocation(node),
216                     getIncidentMessageRestoreIdentityCallNotInFinallyBlock(variableName)
217                 )
218             }
219             tokensMap.remove(variableName)
220         }
221 
222         private fun getFirstNonQualifiedParent(expression: UCallExpression): UElement? {
223             // UCallExpression can be a child of UQualifiedReferenceExpression, i.e.
224             // receiver.selector, so to get the call's immediate parent we need to get the topmost
225             // parent qualified reference expression and access its parent
226             return skipParenthesizedExprUp(expression.getQualifiedParentOrThis().uastParent)
227         }
228 
229         private fun isCallerAwareMethod(expression: UCallExpression): Boolean =
230             callerAwareMethods.any { method -> isMethodCall(expression, method) }
231 
232         private fun isMethodCall(
233             expression: UCallExpression,
234             method: Method
235         ): Boolean {
236             val psiMethod = expression.resolve() ?: return false
237             return psiMethod.getName() == method.methodName &&
238                 context.evaluator.methodMatches(
239                     psiMethod,
240                     method.className,
241                     /* allowInherit */ true,
242                     *method.args
243                 )
244         }
245 
246         /**
247          * ULocalVariable in the file tree:
248          *
249          * UBlockExpression
250          *     UDeclarationsExpression
251          *         ULocalVariable
252          *         ULocalVariable
253          *     UTryStatement
254          *     etc.
255          *
256          * To get the next statement of ULocalVariable:
257          * - If there exists a next sibling in UDeclarationsExpression, return the sibling
258          * - If there exists a next sibling of UDeclarationsExpression in UBlockExpression, return
259          *   the sibling
260          * - Otherwise, return null
261          *
262          * Example 1 - the next sibling is in UDeclarationsExpression:
263          * Code:
264          * {
265          *     int num1 = 0, num2 = methodThatThrowsException();
266          * }
267          * Returns: num2 = methodThatThrowsException()
268          *
269          * Example 2 - the next sibling is in UBlockExpression:
270          * Code:
271          * {
272          *     int num1 = 0;
273          *     methodThatThrowsException();
274          * }
275          * Returns: methodThatThrowsException()
276          *
277          * Example 3 - no next sibling;
278          * Code:
279          * {
280          *     int num1 = 0;
281          * }
282          * Returns: null
283          */
284         private fun getNextStatementOfLocalVariable(node: ULocalVariable): UElement? {
285             val declarationsExpression = node.uastParent as? UDeclarationsExpression ?: return null
286             val declarations = declarationsExpression.declarations
287             val indexInDeclarations = declarations.indexOf(node)
288             if (indexInDeclarations != -1 && declarations.size > indexInDeclarations + 1) {
289                 return declarations[indexInDeclarations + 1]
290             }
291             val enclosingBlock = node
292                 .getParentOfType<UBlockExpression>(strict = true) ?: return null
293             val expressions = enclosingBlock.expressions
294             val indexInBlock = expressions.indexOf(declarationsExpression as UElement)
295             return if (indexInBlock == -1) null else expressions.getOrNull(indexInBlock + 1)
296         }
297     }
298 
299     private fun findFirstTokenInScope(node: UElement): Token? {
300         val psi = node.sourcePsi ?: return null
301         for (token in tokensMap.values) {
302             if (token.scope != null && PsiSearchScopeUtil.isInScope(token.scope, psi)) {
303                 return token
304             }
305         }
306         return null
307     }
308 
309     /**
310      * Creates a new instance of the primary location with the secondary location
311      *
312      * Here, secondary location is the helper location that shows where the issue originated
313      *
314      * The detector reports locations as objects, so when we add a secondary location to a location
315      * that has multiple issues, the secondary location gets displayed every time a location is
316      * referenced.
317      *
318      * Example:
319      * 1: final long token1 = Binder.clearCallingIdentity();
320      * 2: long token2 = Binder.clearCallingIdentity();
321      * 3: Binder.restoreCallingIdentity(token1);
322      * 4: Binder.restoreCallingIdentity(token2);
323      *
324      * Explanation:
325      * token2 has 2 issues: NonFinal and NestedCalls
326      *
327      *     Lint report without cloning                        Lint report with cloning
328      * line 2: [NonFinalIssue]                            line 2: [NonFinalIssue]
329      *     line 1: [NestedCallsIssue]
330      * line 2: [NestedCallsIssue]                            line 2: [NestedCallsIssue]
331      *     line 1: [NestedCallsIssue]                           line 1: [NestedCallsIssue]
332      */
333     private fun createNestedLocation(
334         firstCallToken: Token,
335         secondCallTokenLocation: Location
336     ): Location {
337         return cloneLocation(secondCallTokenLocation)
338             .withSecondary(
339                 cloneLocation(firstCallToken.location),
340                 getIncidentMessageNestedClearIdentityCallsSecondary(
341                     firstCallToken.variableName
342                 )
343             )
344     }
345 
346     private fun cloneLocation(location: Location): Location {
347         // smart cast of location.start to 'Position' is impossible, because 'location.start' is a
348         // public API property declared in different module
349         val locationStart = location.start
350         return if (locationStart == null) {
351             Location.create(location.file)
352         } else {
353             Location.create(location.file, locationStart, location.end)
354         }
355     }
356 
357     private enum class Method(
358         val className: String,
359         val methodName: String,
360         val args: Array<String>
361     ) {
362         BINDER_CLEAR_CALLING_IDENTITY(CLASS_BINDER, "clearCallingIdentity", emptyArray()),
363         BINDER_RESTORE_CALLING_IDENTITY(CLASS_BINDER, "restoreCallingIdentity", arrayOf("long")),
364         BINDER_GET_CALLING_PID(CLASS_BINDER, "getCallingPid", emptyArray()),
365         BINDER_GET_CALLING_UID(CLASS_BINDER, "getCallingUid", emptyArray()),
366         BINDER_GET_CALLING_UID_OR_THROW(CLASS_BINDER, "getCallingUidOrThrow", emptyArray()),
367         BINDER_GET_CALLING_USER_HANDLE(CLASS_BINDER, "getCallingUserHandle", emptyArray()),
368         USER_HANDLE_GET_CALLING_APP_ID(CLASS_USER_HANDLE, "getCallingAppId", emptyArray()),
369         USER_HANDLE_GET_CALLING_USER_ID(CLASS_USER_HANDLE, "getCallingUserId", emptyArray())
370     }
371 
372     private data class Token(
373         val variableName: String,
374         val scope: SearchScope?,
375         val location: Location,
376         val finallyBlock: UElement?
377     )
378 
379     companion object {
380         const val CLASS_BINDER = "android.os.Binder"
381         const val CLASS_USER_HANDLE = "android.os.UserHandle"
382 
383         private val callerAwareMethods = listOf(
384             Method.BINDER_GET_CALLING_PID,
385             Method.BINDER_GET_CALLING_UID,
386             Method.BINDER_GET_CALLING_UID_OR_THROW,
387             Method.BINDER_GET_CALLING_USER_HANDLE,
388             Method.USER_HANDLE_GET_CALLING_APP_ID,
389             Method.USER_HANDLE_GET_CALLING_USER_ID
390         )
391 
392         /** Issue: unused token from Binder.clearCallingIdentity() */
393         @JvmField
394         val ISSUE_UNUSED_TOKEN: Issue = Issue.create(
395             id = "UnusedTokenOfOriginalCallingIdentity",
396             briefDescription = "Unused token of Binder.clearCallingIdentity()",
397             explanation = """
398                     You cleared the original calling identity with \
399                     `Binder.clearCallingIdentity()`, but have not used the returned token to \
400                     restore the identity.
401 
402                     Call `Binder.restoreCallingIdentity(token)` in the `finally` block, at the end \
403                     of the method or when you need to restore the identity.
404 
405                     `token` is the result of `Binder.clearCallingIdentity()`
406                     """,
407             category = Category.SECURITY,
408             priority = 6,
409             severity = Severity.WARNING,
410             implementation = Implementation(
411                 CallingIdentityTokenDetector::class.java,
412                 Scope.JAVA_FILE_SCOPE
413             )
414         )
415 
416         private fun getIncidentMessageUnusedToken(variableName: String) = "`$variableName` has " +
417             "not been used to restore the calling identity. Introduce a `try`-`finally` " +
418             "after the declaration and call `Binder.restoreCallingIdentity($variableName)` " +
419             "in `finally` or remove `$variableName`."
420 
421         /** Issue: non-final token from Binder.clearCallingIdentity() */
422         @JvmField
423         val ISSUE_NON_FINAL_TOKEN: Issue = Issue.create(
424             id = "NonFinalTokenOfOriginalCallingIdentity",
425             briefDescription = "Non-final token of Binder.clearCallingIdentity()",
426             explanation = """
427                     You cleared the original calling identity with \
428                     `Binder.clearCallingIdentity()`, but have not made the returned token `final`.
429 
430                     The token should be `final` in order to prevent it from being overwritten, \
431                     which can cause problems when restoring the identity with \
432                     `Binder.restoreCallingIdentity(token)`.
433                     """,
434             category = Category.SECURITY,
435             priority = 6,
436             severity = Severity.WARNING,
437             implementation = Implementation(
438                 CallingIdentityTokenDetector::class.java,
439                 Scope.JAVA_FILE_SCOPE
440             )
441         )
442 
443         private fun getIncidentMessageNonFinalToken(variableName: String) = "`$variableName` is " +
444             "a non-final token from `Binder.clearCallingIdentity()`. Add `final` keyword to " +
445             "`$variableName`."
446 
447         /** Issue: nested calls of Binder.clearCallingIdentity() */
448         @JvmField
449         val ISSUE_NESTED_CLEAR_IDENTITY_CALLS: Issue = Issue.create(
450             id = "NestedClearCallingIdentityCalls",
451             briefDescription = "Nested calls of Binder.clearCallingIdentity()",
452             explanation = """
453                     You cleared the original calling identity with \
454                     `Binder.clearCallingIdentity()` twice without restoring identity with the \
455                     result of the first call.
456 
457                     Make sure to restore the identity after each clear identity call.
458                     """,
459             category = Category.SECURITY,
460             priority = 6,
461             severity = Severity.WARNING,
462             implementation = Implementation(
463                 CallingIdentityTokenDetector::class.java,
464                 Scope.JAVA_FILE_SCOPE
465             )
466         )
467 
468         private fun getIncidentMessageNestedClearIdentityCallsPrimary(
469             firstCallVariableName: String,
470             secondCallVariableName: String
471         ): String = "The calling identity has already been cleared and returned into " +
472             "`$firstCallVariableName`. Move `$secondCallVariableName` declaration after " +
473             "restoring the calling identity with " +
474             "`Binder.restoreCallingIdentity($firstCallVariableName)`."
475 
476         private fun getIncidentMessageNestedClearIdentityCallsSecondary(
477             firstCallVariableName: String
478         ): String = "Location of the `$firstCallVariableName` declaration."
479 
480         /** Issue: Binder.clearCallingIdentity() is not followed by `try-finally` statement */
481         @JvmField
482         val ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY: Issue = Issue.create(
483             id = "ClearIdentityCallNotFollowedByTryFinally",
484             briefDescription = "Binder.clearCallingIdentity() is not followed by try-finally " +
485                 "statement",
486             explanation = """
487                     You cleared the original calling identity with \
488                     `Binder.clearCallingIdentity()`, but the next statement is not a `try` \
489                     statement.
490 
491                     Use the following pattern for running operations with your own identity:
492 
493                     ```
494                     final long token = Binder.clearCallingIdentity();
495                     try {
496                         // Code using your own identity
497                     } finally {
498                         Binder.restoreCallingIdentity(token);
499                     }
500                     ```
501 
502                     Any calls/operations between `Binder.clearCallingIdentity()` and `try` \
503                     statement risk throwing an exception without doing a safe and unconditional \
504                     restore of the identity with `Binder.restoreCallingIdentity()` as an immediate \
505                     child of the `finally` block. If you do not follow the pattern, you may run \
506                     code with your identity that was originally intended to run with the calling \
507                     application's identity.
508                     """,
509             category = Category.SECURITY,
510             priority = 6,
511             severity = Severity.WARNING,
512             implementation = Implementation(
513                 CallingIdentityTokenDetector::class.java,
514                 Scope.JAVA_FILE_SCOPE
515             )
516         )
517 
518         private fun getIncidentMessageClearIdentityCallNotFollowedByTryFinally(
519             variableName: String
520         ): String = "You cleared the calling identity and returned the result into " +
521             "`$variableName`, but the next statement is not a `try`-`finally` statement. " +
522             "Define a `try`-`finally` block after `$variableName` declaration to ensure a " +
523             "safe restore of the calling identity by calling " +
524             "`Binder.restoreCallingIdentity($variableName)` and making it an immediate child " +
525             "of the `finally` block."
526 
527         /** Issue: Binder.restoreCallingIdentity() is not in finally block */
528         @JvmField
529         val ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK: Issue = Issue.create(
530             id = "RestoreIdentityCallNotInFinallyBlock",
531             briefDescription = "Binder.restoreCallingIdentity() is not in finally block",
532             explanation = """
533                     You are restoring the original calling identity with \
534                     `Binder.restoreCallingIdentity()`, but the call is not an immediate child of \
535                     the `finally` block of the `try` statement.
536 
537                     Use the following pattern for running operations with your own identity:
538 
539                     ```
540                     final long token = Binder.clearCallingIdentity();
541                     try {
542                         // Code using your own identity
543                     } finally {
544                         Binder.restoreCallingIdentity(token);
545                     }
546                     ```
547 
548                     If you do not surround the code using your identity with the `try` statement \
549                     and call `Binder.restoreCallingIdentity()` as an immediate child of the \
550                     `finally` block, you may run code with your identity that was originally \
551                     intended to run with the calling application's identity.
552                     """,
553             category = Category.SECURITY,
554             priority = 6,
555             severity = Severity.WARNING,
556             implementation = Implementation(
557                 CallingIdentityTokenDetector::class.java,
558                 Scope.JAVA_FILE_SCOPE
559             )
560         )
561 
562         private fun getIncidentMessageRestoreIdentityCallNotInFinallyBlock(
563             variableName: String
564         ): String = "`Binder.restoreCallingIdentity($variableName)` is not an immediate child of " +
565             "the `finally` block of the try statement after `$variableName` declaration. " +
566             "Surround the call with `finally` block and call it unconditionally."
567 
568         /** Issue: Use of caller-aware methods after Binder.clearCallingIdentity() */
569         @JvmField
570         val ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY: Issue = Issue.create(
571             id = "UseOfCallerAwareMethodsWithClearedIdentity",
572             briefDescription = "Use of caller-aware methods after " +
573                 "Binder.clearCallingIdentity()",
574             explanation = """
575                     You cleared the original calling identity with \
576                     `Binder.clearCallingIdentity()`, but used one of the methods below before \
577                     restoring the identity. These methods will use your own identity instead of \
578                     the caller's identity, so if this is expected replace them with methods that \
579                     explicitly query your own identity such as `Process.myUid()`, \
580                     `Process.myPid()` and `UserHandle.myUserId()`, otherwise move those methods \
581                     out of the `Binder.clearCallingIdentity()` / `Binder.restoreCallingIdentity()` \
582                     section.
583 
584                     ```
585                     Binder.getCallingPid()
586                     Binder.getCallingUid()
587                     Binder.getCallingUidOrThrow()
588                     Binder.getCallingUserHandle()
589                     UserHandle.getCallingAppId()
590                     UserHandle.getCallingUserId()
591                     ```
592                     """,
593             category = Category.SECURITY,
594             priority = 6,
595             severity = Severity.WARNING,
596             implementation = Implementation(
597                 CallingIdentityTokenDetector::class.java,
598                 Scope.JAVA_FILE_SCOPE
599             )
600         )
601 
602         private fun getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity(
603             variableName: String,
604             methodName: String
605         ): String = "You cleared the original identity with `Binder.clearCallingIdentity()` " +
606             "and returned into `$variableName`, so `$methodName` will be using your own " +
607             "identity instead of the caller's. Either explicitly query your own identity or " +
608             "move it after restoring the identity with " +
609             "`Binder.restoreCallingIdentity($variableName)`."
610 
611         /** Issue: Result of Binder.clearCallingIdentity() is not stored in a variable */
612         @JvmField
613         val ISSUE_RESULT_OF_CLEAR_IDENTITY_CALL_NOT_STORED_IN_VARIABLE: Issue = Issue.create(
614             id = "ResultOfClearIdentityCallNotStoredInVariable",
615             briefDescription = "Result of Binder.clearCallingIdentity() is not stored in a " +
616                 "variable",
617             explanation = """
618            You cleared the original calling identity with \
619            `Binder.clearCallingIdentity()`, but did not store the result of the method \
620            call in a variable. You need to store the result in a variable and restore it later.
621 
622            Use the following pattern for running operations with your own identity:
623 
624            ```
625            final long token = Binder.clearCallingIdentity();
626            try {
627                // Code using your own identity
628            } finally {
629                Binder.restoreCallingIdentity(token);
630            }
631            ```
632            """,
633             category = Category.SECURITY,
634             priority = 6,
635             severity = Severity.WARNING,
636             implementation = Implementation(
637                 CallingIdentityTokenDetector::class.java,
638                 Scope.JAVA_FILE_SCOPE
639             )
640         )
641 
642         private fun getIncidentMessageResultOfClearIdentityCallNotStoredInVariable(
643             methodName: String
644         ): String = "You cleared the original identity with `$methodName` but did not store the " +
645             "result in a variable. You need to store the result in a variable and restore it " +
646             "later."
647     }
648 }
649