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.AnnotationInfo 21 import com.android.tools.lint.detector.api.AnnotationOrigin 22 import com.android.tools.lint.detector.api.AnnotationUsageInfo 23 import com.android.tools.lint.detector.api.AnnotationUsageType 24 import com.android.tools.lint.detector.api.Category 25 import com.android.tools.lint.detector.api.ConstantEvaluator 26 import com.android.tools.lint.detector.api.Detector 27 import com.android.tools.lint.detector.api.Implementation 28 import com.android.tools.lint.detector.api.Issue 29 import com.android.tools.lint.detector.api.JavaContext 30 import com.android.tools.lint.detector.api.Scope 31 import com.android.tools.lint.detector.api.Severity 32 import com.android.tools.lint.detector.api.SourceCodeScanner 33 import com.intellij.psi.PsiAnnotation 34 import com.intellij.psi.PsiArrayInitializerMemberValue 35 import com.intellij.psi.PsiClass 36 import com.intellij.psi.PsiElement 37 import com.intellij.psi.PsiMethod 38 import org.jetbrains.uast.UAnnotation 39 import org.jetbrains.uast.UElement 40 import org.jetbrains.uast.UMethod 41 import org.jetbrains.uast.toUElement 42 43 /** 44 * Lint Detector that ensures that any method overriding a method annotated 45 * with @EnforcePermission is also annotated with the exact same annotation. 46 * The intent is to surface the effective permission checks to the service 47 * implementations. 48 * 49 * This is done with 2 mechanisms: 50 * 1. Visit any annotation usage, to ensure that any derived class will have 51 * the correct annotation on each methods. This is for the top to bottom 52 * propagation. 53 * 2. Visit any annotation, to ensure that if a method is annotated, it has 54 * its ancestor also annotated. This is to avoid having an annotation on a 55 * Java method without the corresponding annotation on the AIDL interface. 56 */ 57 class EnforcePermissionDetector : Detector(), SourceCodeScanner { 58 59 override fun applicableAnnotations(): List<String> { 60 return listOf(ANNOTATION_ENFORCE_PERMISSION) 61 } 62 63 override fun getApplicableUastTypes(): List<Class<out UElement>> { 64 return listOf(UAnnotation::class.java) 65 } 66 67 private fun annotationValueGetChildren(elem: PsiElement): Array<PsiElement> { 68 if (elem is PsiArrayInitializerMemberValue) 69 return elem.getInitializers().map { it as PsiElement }.toTypedArray() 70 return elem.getChildren() 71 } 72 73 private fun areAnnotationsEquivalent( 74 context: JavaContext, 75 anno1: PsiAnnotation, 76 anno2: PsiAnnotation 77 ): Boolean { 78 if (anno1.qualifiedName != anno2.qualifiedName) { 79 return false 80 } 81 val attr1 = anno1.parameterList.attributes 82 val attr2 = anno2.parameterList.attributes 83 if (attr1.size != attr2.size) { 84 return false 85 } 86 for (i in attr1.indices) { 87 if (attr1[i].name != attr2[i].name) { 88 return false 89 } 90 val value1 = attr1[i].value ?: return false 91 val value2 = attr2[i].value ?: return false 92 // Try to compare values directly with each other. 93 val v1 = ConstantEvaluator.evaluate(context, value1) 94 val v2 = ConstantEvaluator.evaluate(context, value2) 95 if (v1 != null && v2 != null) { 96 if (v1 != v2) { 97 return false 98 } 99 } else { 100 val children1 = annotationValueGetChildren(value1) 101 val children2 = annotationValueGetChildren(value2) 102 if (children1.size != children2.size) { 103 return false 104 } 105 for (j in children1.indices) { 106 val c1 = ConstantEvaluator.evaluate(context, children1[j]) 107 val c2 = ConstantEvaluator.evaluate(context, children2[j]) 108 if (c1 != c2) { 109 return false 110 } 111 } 112 } 113 } 114 return true 115 } 116 117 private fun compareMethods( 118 context: JavaContext, 119 element: UElement, 120 overridingMethod: PsiMethod, 121 overriddenMethod: PsiMethod, 122 checkEquivalence: Boolean = true 123 ) { 124 // If method is not from a Stub subclass, this method shouldn't use @EP at all. 125 // This is handled by EnforcePermissionHelperDetector. 126 if (!isContainedInSubclassOfStub(context, overridingMethod.toUElement() as? UMethod)) { 127 return 128 } 129 val overridingAnnotation = overridingMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION) 130 val overriddenAnnotation = overriddenMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION) 131 val location = context.getLocation(element) 132 val overridingClass = overridingMethod.parent as PsiClass 133 val overriddenClass = overriddenMethod.parent as PsiClass 134 val overridingName = "${overridingClass.name}.${overridingMethod.name}" 135 val overriddenName = "${overriddenClass.name}.${overriddenMethod.name}" 136 if (overridingAnnotation == null) { 137 val msg = "The method $overridingName overrides the method $overriddenName which " + 138 "is annotated with @EnforcePermission. The same annotation must be used " + 139 "on $overridingName" 140 context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg) 141 } else if (overriddenAnnotation == null) { 142 val msg = "The method $overridingName overrides the method $overriddenName which " + 143 "is not annotated with @EnforcePermission. The same annotation must be " + 144 "used on $overriddenName. Did you forget to annotate the AIDL definition?" 145 context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg) 146 } else if (checkEquivalence && !areAnnotationsEquivalent( 147 context, overridingAnnotation, overriddenAnnotation)) { 148 val msg = "The method $overridingName is annotated with " + 149 "${overridingAnnotation.text} which differs from the overridden " + 150 "method $overriddenName: ${overriddenAnnotation.text}. The same " + 151 "annotation must be used for both methods." 152 context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg) 153 } 154 } 155 156 override fun visitAnnotationUsage( 157 context: JavaContext, 158 element: UElement, 159 annotationInfo: AnnotationInfo, 160 usageInfo: AnnotationUsageInfo 161 ) { 162 if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE && 163 annotationInfo.origin == AnnotationOrigin.METHOD) { 164 val overridingMethod = element.sourcePsi as PsiMethod 165 val overriddenMethod = usageInfo.referenced as PsiMethod 166 compareMethods(context, element, overridingMethod, overriddenMethod) 167 } 168 } 169 170 override fun createUastHandler(context: JavaContext): UElementHandler { 171 return object : UElementHandler() { 172 override fun visitAnnotation(node: UAnnotation) { 173 if (node.qualifiedName != ANNOTATION_ENFORCE_PERMISSION) { 174 return 175 } 176 val method = node.uastParent as? UMethod ?: return 177 val overridingMethod = method as PsiMethod 178 val parents = overridingMethod.findSuperMethods() 179 for (overriddenMethod in parents) { 180 // The equivalence check can be skipped, if both methods are 181 // annotated, it will be verified by visitAnnotationUsage. 182 compareMethods(context, method, overridingMethod, 183 overriddenMethod, checkEquivalence = false) 184 } 185 } 186 } 187 } 188 189 companion object { 190 val EXPLANATION = """ 191 The @EnforcePermission annotation is used to indicate that the underlying binder code 192 has already verified the caller's permissions before calling the appropriate method. The 193 verification code is usually generated by the AIDL compiler, which also takes care of 194 annotating the generated Java code. 195 196 In order to surface that information to platform developers, the same annotation must be 197 used on the implementation class or methods. 198 """ 199 200 val ISSUE_MISSING_ENFORCE_PERMISSION: Issue = Issue.create( 201 id = "MissingEnforcePermissionAnnotation", 202 briefDescription = "Missing @EnforcePermission annotation on Binder method", 203 explanation = EXPLANATION, 204 category = Category.SECURITY, 205 priority = 6, 206 severity = Severity.ERROR, 207 implementation = Implementation( 208 EnforcePermissionDetector::class.java, 209 Scope.JAVA_FILE_SCOPE 210 ) 211 ) 212 213 val ISSUE_MISMATCHING_ENFORCE_PERMISSION: Issue = Issue.create( 214 id = "MismatchingEnforcePermissionAnnotation", 215 briefDescription = "Incorrect @EnforcePermission annotation on Binder method", 216 explanation = EXPLANATION, 217 category = Category.SECURITY, 218 priority = 6, 219 severity = Severity.ERROR, 220 implementation = Implementation( 221 EnforcePermissionDetector::class.java, 222 Scope.JAVA_FILE_SCOPE 223 ) 224 ) 225 } 226 } 227