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