Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 16ec882a authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "EnforcePermissionHelperDetector: @EP can only be used in Stub ancestors"

parents d53be76a 5dd60db5
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -36,7 +36,7 @@ abstract class AidlImplementationDetector : Detector(), SourceCodeScanner {

    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
        override fun visitMethod(node: UMethod) {
            val interfaceName = getContainingAidlInterface(node)
            val interfaceName = getContainingAidlInterface(context, node)
                    .takeUnless(EXCLUDED_CPP_INTERFACES::contains) ?: return
            val body = (node.uastBody as? UBlockExpression) ?: return
            visitAidlMethod(context, node, interfaceName, body)
+1 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ val AIDL_PERMISSION_ANNOTATIONS = listOf(
        ANNOTATION_PERMISSION_MANUALLY_ENFORCED
)

const val BINDER_CLASS = "android.os.Binder"
const val IINTERFACE_INTERFACE = "android.os.IInterface"

/**
+14 −53
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import com.intellij.psi.PsiMethod
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.toUElement

/**
 * Lint Detector that ensures that any method overriding a method annotated
@@ -55,9 +56,6 @@ import org.jetbrains.uast.UMethod
 */
class EnforcePermissionDetector : Detector(), SourceCodeScanner {

    val BINDER_CLASS = "android.os.Binder"
    val JAVA_OBJECT = "java.lang.Object"

    override fun applicableAnnotations(): List<String> {
        return listOf(ANNOTATION_ENFORCE_PERMISSION)
    }
@@ -123,6 +121,11 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
        overriddenMethod: PsiMethod,
        checkEquivalence: Boolean = true
    ) {
        // If method is not from a Stub subclass, this method shouldn't use @EP at all.
        // This is handled by EnforcePermissionHelperDetector.
        if (!isContainedInSubclassOfStub(context, overridingMethod.toUElement() as? UMethod)) {
            return
        }
        val overridingAnnotation = overridingMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
        val overriddenAnnotation = overriddenMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
        val location = context.getLocation(element)
@@ -131,13 +134,6 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
        val overridingName = "${overridingClass.name}.${overridingMethod.name}"
        val overriddenName = "${overriddenClass.name}.${overriddenMethod.name}"
        if (overridingAnnotation == null) {
            if (shouldIgnoreGeneratedMethod(
                            context,
                            overriddenClass = overriddenClass,
                            overridingClass = overridingClass)
            ) {
                return
            }
            val msg = "The method $overridingName overrides the method $overriddenName which " +
                "is annotated with @EnforcePermission. The same annotation must be used " +
                "on $overridingName"
@@ -177,8 +173,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
                if (node.qualifiedName != ANNOTATION_ENFORCE_PERMISSION) {
                    return
                }
                val method = node.uastParent as? UMethod
                if (method != null) {
                val method = node.uastParent as? UMethod ?: return
                val overridingMethod = method as PsiMethod
                val parents = overridingMethod.findSuperMethods()
                for (overriddenMethod in parents) {
@@ -190,40 +185,6 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
            }
        }
    }
    }

    /**
     * since this lint runs globally, it will also run against generated
     * test code e.g.
     * system/tools/aidl/tests/golden_output/aidl-test-interface-permission-java-source/gen/android/aidl/tests/permission/IProtected.java
     * system/tools/aidl/tests/golden_output/aidl-test-interface-permission-java-source/gen/android/aidl/tests/permission/IProtectedInterface.java
     * we do not want to report errors against generated `Stub` and `Proxy` classes in those files
     */
    private fun shouldIgnoreGeneratedMethod(
            context: JavaContext,
            overriddenClass: PsiClass,
            overridingClass: PsiClass,

    ): Boolean {
        if (isInterfaceAndExtendsIInterface(overriddenClass) &&
                context.evaluator.isStatic(overridingClass)) {
            if (overridingClass.name == "Default") return true
            if (overridingClass.name == "Proxy") {
                val shouldBeStub = overridingClass.parent as? PsiClass ?: return false
                return shouldBeStub.name == "Stub" &&
                        context.evaluator.isAbstract(shouldBeStub) &&
                        context.evaluator.isStatic(shouldBeStub) &&
                        shouldBeStub.extendsList?.referenceElements
                        ?.any { it.qualifiedName == BINDER_CLASS } == true
            }
        }
        return false
    }

    private fun isInterfaceAndExtendsIInterface(overriddenClass: PsiClass): Boolean =
            overriddenClass.isInterface &&
                    overriddenClass.extendsList?.referenceElements
                    ?.any { it.qualifiedName == IINTERFACE_INTERFACE } == true

    companion object {
        val EXPLANATION = """
+30 −5
Original line number Diff line number Diff line
@@ -45,8 +45,19 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
            if (context.evaluator.isAbstract(node)) return
            if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return

            if (!isContainedInSubclassOfStub(context, node)) {
                context.report(
                    ISSUE_MISUSING_ENFORCE_PERMISSION,
                    node,
                    context.getLocation(node),
                    "The class of ${node.name} does not inherit from an AIDL generated Stub class"
                )
                return
            }

            val targetExpression = "${node.name}$HELPER_SUFFIX()"
            val message = "Method must start with $targetExpression or super.${node.name}()"
            val message =
                "Method must start with $targetExpression or super.${node.name}(), if applicable"

            val firstExpression = (node.uastBody as? UBlockExpression)
                    ?.expressions?.firstOrNull()
@@ -99,11 +110,12 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
        private const val HELPER_SUFFIX = "_enforcePermission"

        private const val EXPLANATION = """
            When @EnforcePermission is applied, the AIDL compiler generates a Stub method to do the
            permission check called yourMethodName$HELPER_SUFFIX.
            The @EnforcePermission annotation can only be used on methods whose class extends from
            the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the
            AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX.

            yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can
            either call it directly or indirectly via super.yourMethodName().
            either call it directly, or call it indirectly via super.yourMethodName().
            """

        val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
@@ -120,6 +132,19 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
                )
        )

        val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create(
                id = "MisusingEnforcePermissionAnnotation",
                briefDescription = "@EnforcePermission cannot be used here",
                explanation = EXPLANATION,
                category = Category.SECURITY,
                priority = 6,
                severity = Severity.ERROR,
                implementation = Implementation(
                        EnforcePermissionDetector::class.java,
                        Scope.JAVA_FILE_SCOPE
                )
        )

        /**
         * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
         * resulting in an incorrect Location if used directly
+35 −12
Original line number Diff line number Diff line
@@ -16,17 +16,18 @@

package com.google.android.lint.aidl

import com.google.android.lint.CLASS_STUB
import com.intellij.psi.PsiAnonymousClass
import com.android.tools.lint.detector.api.JavaContext
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiReferenceList
import org.jetbrains.uast.UMethod

/**
 * Given a UMethod, determine if this method is
 * an entrypoint to an interface generated by AIDL,
 * returning the interface name if so
 * the entrypoint to an interface generated by AIDL,
 * returning the interface name if so, otherwise returning null
 */
fun getContainingAidlInterface(node: UMethod): String? {
    if (!isInClassCalledStub(node)) return null
fun getContainingAidlInterface(context: JavaContext, node: UMethod): String? {
    if (!isContainedInSubclassOfStub(context, node)) return null
    for (superMethod in node.findSuperMethods()) {
        for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
            ?: continue) {
@@ -38,11 +39,33 @@ fun getContainingAidlInterface(node: UMethod): String? {
    return null
}

private fun isInClassCalledStub(node: UMethod): Boolean {
    (node.containingClass as? PsiAnonymousClass)?.let {
        return it.baseClassReference.referenceName == CLASS_STUB
fun isContainedInSubclassOfStub(context: JavaContext, node: UMethod?): Boolean {
    var superClass = node?.containingClass?.superClass
    while (superClass != null) {
        if (isStub(context, superClass)) return true
        superClass = superClass.superClass
    }
    return node.containingClass?.extendsList?.referenceElements?.any {
        it.referenceName == CLASS_STUB
    } ?: false
    return false
}

fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean {
    if (psiClass == null) return false
    if (psiClass.name != "Stub") return false
    if (!context.evaluator.isStatic(psiClass)) return false
    if (!context.evaluator.isAbstract(psiClass)) return false

    if (!hasSingleAncestor(psiClass.extendsList, BINDER_CLASS)) return false

    val parent = psiClass.parent as? PsiClass ?: return false
    if (!hasSingleAncestor(parent.extendsList, IINTERFACE_INTERFACE)) return false

    val parentName = parent.qualifiedName ?: return false
    if (!hasSingleAncestor(psiClass.implementsList, parentName)) return false

    return true
}

private fun hasSingleAncestor(references: PsiReferenceList?, qualifiedName: String) =
        references != null &&
                references.referenceElements.size == 1 &&
                references.referenceElements[0].qualifiedName == qualifiedName
Loading