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