强制执行产品分区接口

Android 11 取消捆绑了 product 分区,使其独立于 systemvendor 分区。作为这些变更的一部分,您现在可以控制 product 分区对原生和 Java 接口的访问权限(这类似于 vendor 分区的接口强制执行的工作方式)。

强制执行原生接口

要启用原生接口强制执行,请将 PRODUCT_PRODUCT_VNDK_VERSION 设置为 current。(当目标的发布 API 级别大于 29 时,版本会自动设置为 current。)强制执行允许

  • product 分区中的原生模块链接到
    • 静态或动态链接到 product 分区中的其他模块,这些模块包括静态库、共享库或标头库。
    • 动态链接到 system 分区中的 VNDK 库。
  • product 分区中未捆绑 APK 中的 JNI 库链接到 /product/lib/product/lib64 中的库(这是对 NDK 库的补充)。

强制执行不允许链接到 product 分区以外的分区。

构建时强制执行 (Android.bp)

在 Android 11 中,系统模块除了核心和供应商映像变体外,还可以创建产品映像变体。当启用原生接口强制执行时(PRODUCT_PRODUCT_VNDK_VERSION 设置为 current

  • product 分区中的原生模块位于产品变体中,而不是核心变体中。

  • 在其 Android.bp 文件中具有 product_available: true 的模块可用于产品变体。

  • 指定 product_specific: true 的库或二进制文件可以链接到其他库,这些库在其 Android.bp 文件中指定 product_specific: trueproduct_available: true

  • VNDK 库必须在其 Android.bp 文件中具有 product_available: true,以便 product 二进制文件可以链接到 VNDK 库。

下表总结了用于创建映像变体的 Android.bp 属性。

Android.bp 中的属性 创建的变体
强制执行前 强制执行后
默认 (无) 核心
(包括 /system/system_ext/product
核心
(包括 /system/system_ext,但不包括 /product
system_ext_specific: true 核心 核心
product_specific: true 核心 产品
vendor: true 供应商 供应商
vendor_available: true 核心、供应商 核心、供应商
product_available: true 不适用 核心、产品
vendor_available: true AND product_available: true 不适用 核心、产品、供应商
system_ext_specific: true AND vendor_available: true 核心、供应商 核心、供应商
product_specific: true AND vendor_available: true 核心、供应商 产品、供应商

编译时强制执行 (Android.mk)

当原生接口强制执行启用后,安装到 product 分区的原生模块具有 native:product 链接类型,该类型只能链接到其他 native:productnative:vndk 模块。尝试链接到这些模块之外的任何模块都会导致构建系统生成链接类型检查错误。

运行时强制执行

当原生接口强制执行启用后,bionic 链接器的链接器配置不允许系统进程使用 product 库,从而为 product 进程创建一个 product 区段,该区段无法链接到 product 分区之外的库(但是,此类进程可以链接到 VNDK 库)。尝试违反运行时链接配置会导致进程失败并生成 CANNOT LINK EXECUTABLE 错误消息。

强制执行 Java 接口

要启用 Java 接口强制执行,请将 PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE 设置为 true。(当目标的发布 API 级别高于 29 时,该值会自动设置为 true。)启用后,强制执行允许或禁止以下访问

API /system /system_ext /product /vendor /data
公共 API
@SystemApi
@hide API

vendor 分区中一样,product 分区中的应用或 Java 库仅允许使用公共 API 和系统 API;不允许链接到使用隐藏 API 的库。此限制包括编译时链接和运行时反射。

编译时强制执行

在编译时,Make 和 Soong 通过检查 platform_apissdk_version 字段来验证 product 分区中的 Java 模块是否未使用隐藏 API。product 分区中应用的 sdk_version 必须填充 currentsystem_current 或 API 的数字版本,并且 platform_apis 字段必须为空。

运行时强制执行

Android 运行时验证 product 分区中的应用是否未使用隐藏 API,包括反射。有关详细信息,请参阅非 SDK 接口的限制

启用产品接口强制执行

使用本节中的步骤启用产品接口强制执行。

步骤 任务 必需
1 定义您自己的系统 makefile,指定 system 分区的软件包,然后在 device.mk 中设置工件路径要求检查(以防止非系统模块安装到 system 分区)。
2 清理允许列表。
3 强制执行原生接口并识别运行时链接失败(可以与 Java 强制执行并行运行)。
4 强制执行 Java 接口并验证运行时行为(可以与原生强制执行并行运行)。
5 检查运行时行为。
6 使用产品接口强制执行更新 device.mk

步骤 1:创建 makefile 并启用工件路径检查

在此步骤中,您定义 system makefile。

  1. 创建一个 makefile,用于定义 system 分区的软件包。例如,创建一个具有以下内容的 oem_system.mk 文件

    $(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_system.mk)
    $(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system.mk)
    
    # Applications
    PRODUCT_PACKAGES += \
        CommonSystemApp1 \
        CommonSystemApp2 \
        CommonSystemApp3 \
    
    # Binaries
    PRODUCT_PACKAGES += \
        CommonSystemBin1 \
        CommonSystemBin2 \
        CommonSystemBin3 \
    
    # Libraries
    PRODUCT_PACKAGES += \
        CommonSystemLib1 \
        CommonSystemLib2 \
        CommonSystemLib3 \
    
    PRODUCT_SYSTEM_NAME := oem_system
    PRODUCT_SYSTEM_BRAND := Android
    PRODUCT_SYSTEM_MANUFACTURER := Android
    PRODUCT_SYSTEM_MODEL := oem_system
    PRODUCT_SYSTEM_DEVICE := generic
    
    # For system-as-root devices, system.img should be mounted at /, so we
    # include ROOT here.
    _my_paths := \
     $(TARGET_COPY_OUT_ROOT)/ \
     $(TARGET_COPY_OUT_SYSTEM)/ \
    
    $(call require-artifacts-in-path, $(_my_paths),)
    
  2. device.mk 文件中,继承 system 分区的通用 makefile,并启用工件路径要求检查。例如

    $(call inherit-product, $(SRC_TARGET_DIR)/product/oem_system.mk)
    
    # Enable artifact path requirements checking
    PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := strict
    

关于工件路径要求

PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS 设置为 truestrict 时,构建系统会阻止在其他 makefile 中定义的软件包安装到 require-artifacts-in-path 中定义的路径并且阻止在当前 makefile 中定义的软件包安装到 require-artifacts-in-path 中定义的路径之外。

在上面的示例中,当 PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS 设置为 strict 时,oem_system.mk 之外的 makefile 无法包含安装到 rootsystem 分区的模块。要包含这些模块,您必须在 oem_system.mk 文件本身或包含的 makefile 中定义它们。尝试将模块安装到不允许的路径会导致构建中断。要修复中断,请执行以下操作之一

  • 选项 1:oem_system.mk 中包含的 makefile 中包含系统模块。这使得工件路径要求得到满足(因为模块现在存在于包含的 makefile 中),从而允许安装到 `require-artifacts-in-path` 中的路径集。

  • 选项 2: 将模块安装到 system_extproduct 分区(并且不要将模块安装到 system 分区)。

  • 选项 3: 将模块添加到 PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST。这列出了允许安装的模块。

步骤 2:清空允许列表

在此步骤中,您将 PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST 清空,以便共享 oem_system.mk 的所有设备也可以共享单个 system 镜像。要清空允许列表,请将列表中的任何模块移动到 system_extproduct 分区,或将它们添加到 system make 文件。此步骤是可选的,因为定义通用 system 镜像不是启用产品接口强制执行的必要条件。但是,清空允许列表有助于使用 system_ext 定义 system 边界。

步骤 3:强制执行原生接口

在此步骤中,您设置 PRODUCT_PRODUCT_VNDK_VERSION := current,然后查找构建和运行时错误并解决它们。要检查设备启动和日志,并查找和修复运行时链接失败

  1. 设置 PRODUCT_PRODUCT_VNDK_VERSION := current

  2. 构建设备并查找构建错误。您可能会看到一些构建中断,原因是缺少产品变体或核心变体。常见的错误包括

    • 任何具有 product_specific: truehidl_interface 模块都将对系统模块不可用。要修复,请将 product_specific: true 替换为 system_ext_specific: true
    • 模块可能缺少产品模块所需的产品变体。要修复,请通过设置 product_available: true 使该模块对 product 分区可用,或通过设置 product_specific: true 将模块移动到 product 分区。
  3. 解决构建错误并确保设备构建成功。

  4. 刷写镜像并查找设备启动和日志中的运行时错误。

    • 如果测试用例日志中的 linker 标记显示 CANNOT LINK EXECUTABLE 消息,则 make 文件缺少依赖项(并且在构建时未捕获)。
    • 要从构建系统检查它,请将所需的库添加到 shared_libs:required: 字段。
  5. 使用上面给出的指导解决缺少的依赖项。

步骤 4:强制执行 Java 接口

在此步骤中,您设置 PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE := true,然后查找并修复由此产生的构建错误。查找两种特定类型的错误

  • 链接类型错误。 此错误指示应用链接到具有更广泛 sdk_version 的 Java 模块。要修复,您可以扩大应用的 sdk_version 或限制库的 sdk_version。示例错误

    error: frameworks/base/packages/SystemUI/Android.bp:138:1: module "SystemUI" variant "android_common": compiles against system API, but dependency "telephony-common" is compiling against private API.Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.
    
  • 符号错误。 此错误指示找不到符号,因为它在隐藏 API 中。要修复,请使用可见(非隐藏)API 或找到替代方案。示例错误

    frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java:1051: error: cannot find symbol
                ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader(
                                               ^
      symbol:   class ProxyAuthenticate
      location: class SipSessionGroup.SipSessionImpl
    

步骤 5:检查运行时行为

在此步骤中,您验证运行时行为是否符合预期。对于可调试的应用,您可以使用 StrictMode.detectNonSdkApiUsage 通过日志监控隐藏 API 的使用情况(当应用使用隐藏 API 时,它会生成日志)。或者,您可以使用 veridex 静态分析工具来获取使用类型(链接或反射)、限制级别和调用堆栈。

  • Veridex 语法

    ./art/tools/veridex/appcompat.sh --dex-file={apk file}
  • Veridex 结果示例

    #1: Linking greylist-max-o Landroid/animation/AnimationHandler;-><init>()V use(s):
           Lcom/android/systemui/pip/phone/PipMotionHelper;-><init>(Landroid/content/Context;Landroid/app/IActivityManager;Landroid/app/IActivityTaskManager;Lcom/android/systemui/pip/phone/PipMenuActivityController;Lcom/android/internal/policy/PipSnapAlgorithm;Lcom/android/systemui/statusbar/FlingAnimationUtils;)V
    
    #1332: Reflection greylist Landroid/app/Activity;->mMainThread use(s):
           Landroidx/core/app/ActivityRecreator;->getMainThreadField()Ljava/lang/reflect/Field;
    

有关 veridex 用法的详细信息,请参阅使用 veridex 工具进行测试

步骤 6:更新 device.mk

在修复所有构建和运行时失败,并验证运行时行为是否符合预期后,在 device.mk 中设置以下内容

  • PRODUCT_PRODUCT_VNDK_VERSION := current
  • PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE := true