1 /*
2  * Copyright (C) 2022 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.aidl
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.Detector
22 import com.android.tools.lint.detector.api.Implementation
23 import com.android.tools.lint.detector.api.Issue
24 import com.android.tools.lint.detector.api.JavaContext
25 import com.android.tools.lint.detector.api.Scope
26 import com.android.tools.lint.detector.api.Severity
27 import com.android.tools.lint.detector.api.SourceCodeScanner
28 import com.google.android.lint.findCallExpression
29 import com.intellij.psi.PsiElement
30 import org.jetbrains.uast.UBlockExpression
31 import org.jetbrains.uast.UDeclarationsExpression
32 import org.jetbrains.uast.UElement
33 import org.jetbrains.uast.UExpression
34 import org.jetbrains.uast.UMethod
35 import org.jetbrains.uast.skipParenthesizedExprDown
36 
37 class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
38     override fun getApplicableUastTypes(): List<Class<out UElement?>> =
39             listOf(UMethod::class.java)
40 
41     override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
42 
43     private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
44         override fun visitMethod(node: UMethod) {
45             if (context.evaluator.isAbstract(node)) return
46             if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
47 
48             if (!isContainedInSubclassOfStub(context, node)) {
49                 context.report(
50                     ISSUE_MISUSING_ENFORCE_PERMISSION,
51                     node,
52                     context.getLocation(node),
53                     "The class of ${node.name} does not inherit from an AIDL generated Stub class"
54                 )
55                 return
56             }
57 
58             val targetExpression = getHelperMethodCallSourceString(node)
59             val message =
60                 "Method must start with $targetExpression or super.${node.name}(), if applicable"
61 
62             val firstExpression = (node.uastBody as? UBlockExpression)
63                     ?.expressions?.firstOrNull()
64 
65             if (firstExpression == null) {
66                 context.report(
67                     ISSUE_ENFORCE_PERMISSION_HELPER,
68                     context.getLocation(node),
69                     message,
70                 )
71                 return
72             }
73 
74             val firstExpressionSource = firstExpression.skipParenthesizedExprDown()
75               .asSourceString()
76               .filterNot(Char::isWhitespace)
77 
78             if (firstExpressionSource != targetExpression &&
79                   firstExpressionSource != "super.$targetExpression") {
80                 // calling super.<methodName>() is also legal
81                 val directSuper = context.evaluator.getSuperMethod(node)
82                 val firstCall = findCallExpression(firstExpression)?.resolve()
83                 if (directSuper != null && firstCall == directSuper) return
84 
85                 val locationTarget = getLocationTarget(firstExpression)
86                 val expressionLocation = context.getLocation(locationTarget)
87 
88                 context.report(
89                     ISSUE_ENFORCE_PERMISSION_HELPER,
90                     context.getLocation(node),
91                     message,
92                     getHelperMethodFix(node, expressionLocation),
93                 )
94             }
95         }
96     }
97 
98     companion object {
99         private const val HELPER_SUFFIX = "_enforcePermission"
100 
101         private const val EXPLANATION = """
102             The @EnforcePermission annotation can only be used on methods whose class extends from
103             the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the
104             AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX.
105 
106             yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can
107             either call it directly, or call it indirectly via super.yourMethodName().
108             """
109 
110         val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
111                 id = "MissingEnforcePermissionHelper",
112                 briefDescription = """Missing permission-enforcing method call in AIDL method
113                     |annotated with @EnforcePermission""".trimMargin(),
114                 explanation = EXPLANATION,
115                 category = Category.SECURITY,
116                 priority = 6,
117                 severity = Severity.ERROR,
118                 implementation = Implementation(
119                         EnforcePermissionHelperDetector::class.java,
120                         Scope.JAVA_FILE_SCOPE
121                 )
122         )
123 
124         val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create(
125                 id = "MisusingEnforcePermissionAnnotation",
126                 briefDescription = "@EnforcePermission cannot be used here",
127                 explanation = EXPLANATION,
128                 category = Category.SECURITY,
129                 priority = 6,
130                 severity = Severity.ERROR,
131                 implementation = Implementation(
132                         EnforcePermissionDetector::class.java,
133                         Scope.JAVA_FILE_SCOPE
134                 )
135         )
136 
137         /**
138          * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
139          * resulting in an incorrect Location if used directly
140          */
141         private fun getLocationTarget(firstExpression: UExpression): PsiElement? {
142             if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi
143             if (firstExpression is UDeclarationsExpression) {
144                 return firstExpression.declarations.firstOrNull()?.sourcePsi
145             }
146             return null
147         }
148     }
149 }
150