1 /*
2  * Copyright (C) 2024 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.detector.api.Category
20 import com.android.tools.lint.detector.api.Implementation
21 import com.android.tools.lint.detector.api.Issue
22 import com.android.tools.lint.detector.api.JavaContext
23 import com.android.tools.lint.detector.api.Scope
24 import com.android.tools.lint.detector.api.Severity
25 import org.jetbrains.uast.UastCallKind
26 import org.jetbrains.uast.UBlockExpression
27 import org.jetbrains.uast.UCallExpression
28 import org.jetbrains.uast.UElement
29 import org.jetbrains.uast.UMethod
30 import org.jetbrains.uast.visitor.AbstractUastVisitor
31 
32 /**
33  * Ensures all AIDL implementations hosted by system_server which don't call other methods are
34  * annotated with @RequiresNoPermission. AIDL Interfaces part of `exemptAidlInterfaces` are skipped
35  * during this search to ensure the detector targets only new AIDL Interfaces.
36  */
37 class SimpleRequiresNoPermissionDetector : AidlImplementationDetector() {
visitAidlMethodnull38     override fun visitAidlMethod(
39         context: JavaContext,
40         node: UMethod,
41         interfaceName: String,
42         body: UBlockExpression
43     ) {
44         if (!isSystemServicePath(context)) return
45         if (context.evaluator.isAbstract(node)) return
46 
47         val fullyQualifiedInterfaceName =
48             getContainingAidlInterfaceQualified(context, node) ?: return
49         if (exemptAidlInterfaces.contains(fullyQualifiedInterfaceName)) return
50 
51         if (node.hasAnnotation(ANNOTATION_REQUIRES_NO_PERMISSION)) return
52 
53         if (!isCallingMethod(node)) {
54             context.report(
55                 ISSUE_SIMPLE_REQUIRES_NO_PERMISSION,
56                 node,
57                 context.getLocation(node),
58                 """
59                     Method ${node.name} doesn't perform any permission checks, meaning it should \
60                     be annotated with @RequiresNoPermission.
61                 """.trimMargin()
62             )
63         }
64     }
65 
isCallingMethodnull66     private fun isCallingMethod(node: UMethod): Boolean {
67         val uCallExpressionVisitor = UCallExpressionVisitor()
68         node.accept(uCallExpressionVisitor)
69 
70         return uCallExpressionVisitor.isCallingMethod
71     }
72 
73     /**
74      * Visits the body of a `UMethod` and determines if it encounters a `UCallExpression` which is
75      * a `UastCallKind.METHOD_CALL`. `isCallingMethod` will hold the result of the search procedure.
76      */
77     private class UCallExpressionVisitor : AbstractUastVisitor() {
78         var isCallingMethod = false
79 
visitElementnull80         override fun visitElement(node: UElement): Boolean {
81             // Stop the search early when a method call has been found.
82             return isCallingMethod
83         }
84 
visitCallExpressionnull85         override fun visitCallExpression(node: UCallExpression): Boolean {
86             if (node.kind != UastCallKind.METHOD_CALL) return false
87 
88             isCallingMethod = true
89             return true
90         }
91     }
92 
93     companion object {
94 
95         private val EXPLANATION = """
96             Method implementations of AIDL Interfaces hosted by the `system_server` which do not
97             call any other methods should be annotated with @RequiresNoPermission. That is because
98             not calling any other methods implies that the method does not perform any permission
99             checking.
100 
101             Please migrate to an @RequiresNoPermission annotation.
102         """.trimIndent()
103 
104         @JvmField
105         val ISSUE_SIMPLE_REQUIRES_NO_PERMISSION = Issue.create(
106             id = "SimpleRequiresNoPermission",
107             briefDescription = "System Service APIs not calling other methods should use @RNP",
108             explanation = EXPLANATION,
109             category = Category.SECURITY,
110             priority = 5,
111             severity = Severity.ERROR,
112             implementation = Implementation(
113                 SimpleRequiresNoPermissionDetector::class.java,
114                 Scope.JAVA_FILE_SCOPE
115             ),
116         )
117     }
118 }
119