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
Change-Id: Id700fb74485e63c76bbdb163079dd90b08c100dc
diff --git a/tools/lint/utils/README.md b/tools/lint/utils/README.md
new file mode 100644
index 0000000..b5583c5
--- /dev/null
+++ b/tools/lint/utils/README.md
@@ -0,0 +1,11 @@
+# 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.
diff --git a/tools/lint/utils/checks/src/main/java/com/google/android/lint/AndroidUtilsIssueRegistry.kt b/tools/lint/utils/checks/src/main/java/com/google/android/lint/AndroidUtilsIssueRegistry.kt
index fa61c42..9842881 100644
--- a/tools/lint/utils/checks/src/main/java/com/google/android/lint/AndroidUtilsIssueRegistry.kt
+++ b/tools/lint/utils/checks/src/main/java/com/google/android/lint/AndroidUtilsIssueRegistry.kt
@@ -19,6 +19,7 @@
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 @@
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 @@
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"
)
}
diff --git a/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfacesGenerator.kt b/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfacesGenerator.kt
new file mode 100644
index 0000000..6ad223c
--- /dev/null
+++ b/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfacesGenerator.kt
@@ -0,0 +1,96 @@
+/*
+ * 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
+ )
+ )
+ }
+}
diff --git a/tools/lint/utils/checks/src/test/java/com/google/android/lint/aidl/ExemptAidlInterfacesGeneratorTest.kt b/tools/lint/utils/checks/src/test/java/com/google/android/lint/aidl/ExemptAidlInterfacesGeneratorTest.kt
new file mode 100644
index 0000000..9a17bb4
--- /dev/null
+++ b/tools/lint/utils/checks/src/test/java/com/google/android/lint/aidl/ExemptAidlInterfacesGeneratorTest.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+class ExemptAidlInterfacesGeneratorTest : LintDetectorTest() {
+ override fun getDetector(): Detector = ExemptAidlInterfacesGenerator()
+
+ override fun getIssues(): List<Issue> = listOf(
+ ExemptAidlInterfacesGenerator.ISSUE_PERMISSION_ANNOTATION_EXEMPT_AIDL_INTERFACES,
+ )
+
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+ fun testMultipleAidlInterfacesImplemented() {
+ lint()
+ .files(
+ java(
+ createVisitedPath("TestClass1.java"),
+ """
+ package com.android.server;
+ public class TestClass1 extends IFoo.Stub {
+ public void testMethod() {}
+ }
+ """
+ )
+ .indented(),
+ java(
+ createVisitedPath("TestClass2.java"),
+ """
+ package com.android.server;
+ public class TestClass2 extends IBar.Stub {
+ public void testMethod() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs,
+ )
+ .run()
+ .expect(
+ """
+ app: Information: "IFoo",
+ "IBar", [PermissionAnnotationExemptAidlInterfaces]
+ 0 errors, 0 warnings
+ """
+ )
+ }
+
+ fun testSingleAidlInterfaceRepeated() {
+ lint()
+ .files(
+ java(
+ createVisitedPath("TestClass1.java"),
+ """
+ package com.android.server;
+ public class TestClass1 extends IFoo.Stub {
+ public void testMethod() {}
+ }
+ """
+ )
+ .indented(),
+ java(
+ createVisitedPath("TestClass2.java"),
+ """
+ package com.android.server;
+ public class TestClass2 extends IFoo.Stub {
+ public void testMethod() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs,
+ )
+ .run()
+ .expect(
+ """
+ app: Information: "IFoo", [PermissionAnnotationExemptAidlInterfaces]
+ 0 errors, 0 warnings
+ """
+ )
+ }
+
+ fun testAnonymousClassExtendsAidlStub() {
+ lint()
+ .files(
+ java(
+ createVisitedPath("TestClass.java"),
+ """
+ package com.android.server;
+ public class TestClass {
+ private IBinder aidlImpl = new IFoo.Stub() {
+ public void testMethod() {}
+ };
+ }
+ """
+ )
+ .indented(),
+ *stubs,
+ )
+ .run()
+ .expect(
+ """
+ app: Information: "IFoo", [PermissionAnnotationExemptAidlInterfaces]
+ 0 errors, 0 warnings
+ """
+ )
+ }
+
+ fun testNoAidlInterfacesImplemented() {
+ lint()
+ .files(
+ java(
+ createVisitedPath("TestClass.java"),
+ """
+ package com.android.server;
+ public class TestClass {
+ public void testMethod() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ fun testAidlInterfaceImplementedInIgnoredDirectory() {
+ lint()
+ .files(
+ java(
+ ignoredPath,
+ """
+ package com.android.server;
+ public class TestClass1 extends IFoo.Stub {
+ public void testMethod() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs,
+ )
+ .run()
+ .expectClean()
+ }
+
+ private val interfaceIFoo: TestFile = java(
+ """
+ public interface IFoo extends android.os.IInterface {
+ public static abstract class Stub extends android.os.Binder implements IFoo {}
+ public void testMethod();
+ }
+ """
+ ).indented()
+
+ private val interfaceIBar: TestFile = java(
+ """
+ public interface IBar extends android.os.IInterface {
+ public static abstract class Stub extends android.os.Binder implements IBar {}
+ public void testMethod();
+ }
+ """
+ ).indented()
+
+ private val stubs = arrayOf(interfaceIFoo, interfaceIBar)
+
+ private fun createVisitedPath(filename: String) =
+ "src/frameworks/base/services/java/com/android/server/$filename"
+
+ private val ignoredPath = "src/test/pkg/TestClass.java"
+}
diff --git a/tools/lint/utils/generate-exempt-aidl-interfaces.sh b/tools/lint/utils/generate-exempt-aidl-interfaces.sh
new file mode 100755
index 0000000..44dcdd7
--- /dev/null
+++ b/tools/lint/utils/generate-exempt-aidl-interfaces.sh
@@ -0,0 +1,59 @@
+#
+# 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.
+#
+
+# Create a directory for the results and a nested temporary directory.
+mkdir -p $ANDROID_BUILD_TOP/out/soong/exempt_aidl_interfaces_generator_output/tmp
+
+# Create a copy of `AndroidGlobalLintChecker.jar` to restore it afterwards.
+cp $ANDROID_BUILD_TOP/prebuilts/cmdline-tools/AndroidGlobalLintChecker.jar \
+ $ANDROID_BUILD_TOP/out/soong/exempt_aidl_interfaces_generator_output/AndroidGlobalLintChecker.jar
+
+# Configure the environment variable required for running the lint check on the entire source tree.
+export ANDROID_LINT_CHECK=PermissionAnnotationExemptAidlInterfaces
+
+# Build the target corresponding to the lint checks present in the `utils` directory.
+m AndroidUtilsLintChecker
+
+# Replace `AndroidGlobalLintChecker.jar` with the newly built `jar` file.
+cp $ANDROID_BUILD_TOP/out/host/linux-x86/framework/AndroidUtilsLintChecker.jar \
+ $ANDROID_BUILD_TOP/prebuilts/cmdline-tools/AndroidGlobalLintChecker.jar;
+
+# Run the lint check on the entire source tree.
+m lint-check
+
+# Copy the archive containing the results of `lint-check` into the temporary directory.
+cp $ANDROID_BUILD_TOP/out/soong/lint-report-text.zip \
+ $ANDROID_BUILD_TOP/out/soong/exempt_aidl_interfaces_generator_output/tmp
+
+cd $ANDROID_BUILD_TOP/out/soong/exempt_aidl_interfaces_generator_output/tmp
+
+# Unzip the archive containing the results of `lint-check`.
+unzip lint-report-text.zip
+
+# Concatenate the results of `lint-check` into a single string.
+concatenated_reports=$(find . -type f | xargs cat)
+
+# Extract the fully qualified names of the AIDL Interfaces from the concatenated results. Output
+# this list into `out/soong/exempt_aidl_interfaces_generator_output/exempt_aidl_interfaces`.
+echo $concatenated_reports | grep -Eo '\"([a-zA-Z0-9_]*\.)+[a-zA-Z0-9_]*\",' | sort | uniq > ../exempt_aidl_interfaces
+
+# Remove the temporary directory.
+rm -rf $ANDROID_BUILD_TOP/out/soong/exempt_aidl_interfaces_generator_output/tmp
+
+# Restore the original copy of `AndroidGlobalLintChecker.jar` and delete the copy.
+cp $ANDROID_BUILD_TOP/out/soong/exempt_aidl_interfaces_generator_output/AndroidGlobalLintChecker.jar \
+ $ANDROID_BUILD_TOP/prebuilts/cmdline-tools/AndroidGlobalLintChecker.jar
+rm $ANDROID_BUILD_TOP/out/soong/exempt_aidl_interfaces_generator_output/AndroidGlobalLintChecker.jar