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

Commit 4f522ff2 authored by Tudor Magirescu's avatar Tudor Magirescu Committed by Android Build Cherrypicker Worker
Browse files

Add ExemptAidlInterfacesGenerator for PermissionAnnotationDetector

This CL is a prerequisite for migrating PermissionAnnotationDetector to
a global lint check. This will enforce newly added system services to
use @EnforcePermission annotations.

To determine the newly added system services, the global lint check will
use a pre-computed set of existing, i.e. exempt, AIDL interfaces in
AOSP. To compute the set, the CL introduces the following:
* ExemptAidlInterfacesGenerator, the Android Lint check that creates a
  set of AIDL Interfaces for a build target.
* generate-exempt-aidl-interfaces.sh, a Bash script that runs the lint
  check on the entire source tree and aggregates the results.

Bug: 363248121
Test: ExemptAidlInterfacesGeneratorTest
Flag: EXEMPT lint check
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:9f5ec6218a11fcd9bc40e94c431338a9870a9e60)
Merged-In: Id700fb74485e63c76bbdb163079dd90b08c100dc
Change-Id: Id700fb74485e63c76bbdb163079dd90b08c100dc
parent c3d49d55
Loading
Loading
Loading
Loading
+19 −8
Original line number Diff line number Diff line
@@ -24,20 +24,31 @@ import com.intellij.psi.PsiReferenceList
import org.jetbrains.uast.UMethod

/**
 * Given a UMethod, determine if this method is the entrypoint to an interface
 * generated by AIDL, returning the interface name if so, otherwise returning
 * null
 * Given a UMethod, determine if this method is the entrypoint to an interface generated by AIDL,
 * returning the interface name if so, otherwise returning null.
 */
fun getContainingAidlInterface(context: JavaContext, node: UMethod): String? {
    return containingAidlInterfacePsiClass(context, node)?.name
}

/**
 * Given a UMethod, determine if this method is the entrypoint to an interface generated by AIDL,
 * returning the fully qualified interface name if so, otherwise returning null.
 */
fun getContainingAidlInterfaceQualified(context: JavaContext, node: UMethod): String? {
    return containingAidlInterfacePsiClass(context, node)?.qualifiedName
}

private fun containingAidlInterfacePsiClass(context: JavaContext, node: UMethod): PsiClass? {
    val containingStub = containingStub(context, node) ?: return null
    val superMethod = node.findSuperMethods(containingStub)
    if (superMethod.isEmpty()) return null
    return containingStub.containingClass?.name
    return containingStub.containingClass
}

/* Returns the containing Stub class if any. This is not sufficient to infer
 * that the method itself extends an AIDL generated method. See
 * getContainingAidlInterface for that purpose.
/**
 * Returns the containing Stub class if any. This is not sufficient to infer that the method itself
 * extends an AIDL generated method. See getContainingAidlInterface for that purpose.
 */
fun containingStub(context: JavaContext, node: UMethod?): PsiClass? {
    var superClass = node?.containingClass?.superClass
@@ -48,7 +59,7 @@ fun containingStub(context: JavaContext, node: UMethod?): PsiClass? {
    return null
}

private fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean {
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
+774 −0

File added.

Preview size limit exceeded, changes collapsed.

+11 −0
Original line number Diff line number Diff line
# Utility Android Lint Checks for AOSP

This directory contains scripts that execute utility Android Lint Checks for AOSP, specifically:
* `enforce_permission_counter.py`: Provides statistics regarding the percentage of annotated/not
  annotated `AIDL` methods with `@EnforcePermission` annotations.
* `generate-exempt-aidl-interfaces.sh`: Provides a list of all `AIDL` interfaces in the entire
  source tree.

When adding a new utility Android Lint check to this directory, consider adding any utility or
data processing tool you might require. Make sure that your contribution is documented in this
README file.
+3 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.google.android.lint
import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.client.api.Vendor
import com.android.tools.lint.detector.api.CURRENT_API
import com.google.android.lint.aidl.ExemptAidlInterfacesGenerator
import com.google.android.lint.aidl.AnnotatedAidlCounter
import com.google.auto.service.AutoService

@@ -27,6 +28,7 @@ import com.google.auto.service.AutoService
class AndroidUtilsIssueRegistry : IssueRegistry() {
    override val issues = listOf(
        AnnotatedAidlCounter.ISSUE_ANNOTATED_AIDL_COUNTER,
        ExemptAidlInterfacesGenerator.ISSUE_PERMISSION_ANNOTATION_EXEMPT_AIDL_INTERFACES,
    )

    override val api: Int
@@ -38,6 +40,6 @@ class AndroidUtilsIssueRegistry : IssueRegistry() {
    override val vendor: Vendor = Vendor(
        vendorName = "Android",
        feedbackUrl = "http://b/issues/new?component=315013",
        contact = "tweek@google.com"
        contact = "android-platform-abuse-prevention-withfriends@google.com"
    )
}
+96 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.lint.aidl

import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Context
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import org.jetbrains.uast.UBlockExpression
import org.jetbrains.uast.UMethod

/**
 * Generates a set of fully qualified AIDL Interface names present in the entire source tree with
 * the following requirement: their implementations have to be inside directories whose path
 * prefixes match `systemServicePathPrefixes`.
 */
class ExemptAidlInterfacesGenerator : AidlImplementationDetector() {
    private val targetExemptAidlInterfaceNames = mutableSetOf<String>()
    private val systemServicePathPrefixes = setOf(
        "frameworks/base/services",
        "frameworks/base/apex",
        "frameworks/opt/wear",
        "packages/modules"
    )

    // We could've improved performance by visiting classes rather than methods, however, this lint
    // check won't be run regularly, hence we've decided not to add extra overrides to
    // AidlImplementationDetector.
    override fun visitAidlMethod(
        context: JavaContext,
        node: UMethod,
        interfaceName: String,
        body: UBlockExpression
    ) {
        val filePath = context.file.path

        // We perform `filePath.contains` instead of `filePath.startsWith` since getting the
        // relative path of a source file is non-trivial. That is because `context.file.path`
        // returns the path to where soong builds the file (i.e. /out/soong/...). Moreover, the
        // logic to extract the relative path would need to consider several /out/soong/...
        // locations patterns.
        if (systemServicePathPrefixes.none { filePath.contains(it) }) return

        val fullyQualifiedInterfaceName =
            getContainingAidlInterfaceQualified(context, node) ?: return

        targetExemptAidlInterfaceNames.add("\"$fullyQualifiedInterfaceName\",")
    }

    override fun afterCheckEachProject(context: Context) {
        if (targetExemptAidlInterfaceNames.isEmpty()) return

        val message = targetExemptAidlInterfaceNames.joinToString("\n")

        context.report(
            ISSUE_PERMISSION_ANNOTATION_EXEMPT_AIDL_INTERFACES,
            context.getLocation(context.project.dir),
            "\n" + message + "\n",
        )
    }

    companion object {
        @JvmField
        val ISSUE_PERMISSION_ANNOTATION_EXEMPT_AIDL_INTERFACES = Issue.create(
            id = "PermissionAnnotationExemptAidlInterfaces",
            briefDescription = "Returns a set of all AIDL interfaces",
            explanation = """
                Produces the exemptAidlInterfaces set used by PermissionAnnotationDetector
            """.trimIndent(),
            category = Category.SECURITY,
            priority = 5,
            severity = Severity.INFORMATIONAL,
            implementation = Implementation(
                ExemptAidlInterfacesGenerator::class.java,
                Scope.JAVA_FILE_SCOPE
            )
        )
    }
}
Loading