Skip to content

App Domains

追踪 Android 17 应用域从 seapp_contexts selectors、PackageManager seInfo 到 Zygote domain/type、数据标签和隔离约束。

基于android-17.0.0_r1
AndroidSELinuxapp domainseapp_contexts源码阅读

App Domains ​

本文面向已经读过 seapp_contexts、MAC Permissions、Init Domain 和 Zygote Domain 的读者。本文讲 Android 17 应用进程域的真实匹配链路:PackageManager 计算 seInfo,Zygote 传递 uid/seInfo,libselinux 依据 seapp_contexts 选择 domain、data type 和 MLS level。

应用域不是简单的“信任等级从低到高”。最终域同时取决于 user、seinfo、包名、isPrivApp、target SDK、是否 isolated、是否 SDK sandbox 以及策略文件的来源优先级。读完后,读者应能解释普通 untrusted_app、平台签名 platform_app、system_app、priv_app、isolated_app 和 SDK sandbox 的 selector 差异,并定位匹配失败与运行时访问失败分别发生在哪一层。

1. 输入与输出 ​

1.1 selector ​

源码文件:system/sepolicy/private/seapp_contexts

yaml
# Input selectors include:
# isSystemServer, isEphemeralApp, user, seinfo, name, isPrivApp,
# minTargetSdkVersion, fromRunAs, isIsolatedComputeApp,
# isSdkSandboxNext and isSdkSandboxAudit.

所有指定 selector 必须同时匹配;user/name 以星号结尾时进行前缀匹配,字符串比较不区分大小写。未指定 selector 表示任意值,除非该字段有默认值。

1.2 输出 ​

源码文件:system/sepolicy/private/seapp_contexts

yaml
# Outputs:
# domain determines the process label.
# type determines the app data directory label.
# levelFrom and level determine MLS/MCS level.

domain 是进程最终 type,type 是 app data 目录 type;levelFrom 决定 category/sensitivity 如何从 UID/user 计算。进程 context 和数据目录 context 是两个消费者,命中同一条规则也不代表它们在同一时刻生效。

1.3 入口图 ​

2. 基础域 ​

2.1 appdomain基线 ​

源码文件:system/sepolicy/private/app.te、system/sepolicy/public/app.te

text
typeattribute appdomain mlstrustedsubject;
type appdomain_tmpfs, file_type;

源码文件:system/sepolicy/private/untrusted_app.te

text
app_domain(untrusted_app)
untrusted_app_domain(untrusted_app)
net_domain(untrusted_app)
bluetooth_domain(untrusted_app)

appdomain 是跨多个具体 app domain 的聚合属性;untrusted_app 在此基础上加入不可信应用限制。不要把 appdomain 当作一个运行中的 process context,它是 policy 规则匹配的 attribute。

2.2 四类常规域 ​

源码文件:system/sepolicy/public/untrusted_app.te、platform_app.te、priv_app.te、system_app.te

text
type untrusted_app, domain;
type platform_app, domain;
type priv_app, domain;
type system_app, domain;

这些 public type 只声明可被 policy 引用的域名。具体权限分散在 private/*.te 和 appdomain 宏中;同一应用域可能还会按 target SDK 得到 untrusted_app_XX 或 platform_app_XX 变体。

2.3 特殊域 ​

源码文件:system/sepolicy/private/seapp_contexts

yaml
user=_isolated domain=isolated_app
user=_isolated isIsolatedComputeApp=true domain=isolated_compute_app
user=_sdksandbox domain=sdk_sandbox
user=_pcc_component domain=pcc_comp_app

isolated UID、SDK sandbox 和 PCC component 不是普通 APK selector 的别名。它们有专用 user 值和 neverallow,不能通过给普通 app 增加 seinfo 来伪造。

3. 匹配顺序 ​

3.1 precedence ​

源码文件:system/sepolicy/private/seapp_contexts

yaml
# Precedence:
# isSystemServer
# specified isEphemeralApp
# specified user, then more specific user
# specified seinfo
# specified name, then more specific name
# specified isPrivApp
# higher minTargetSdkVersion
# fromRunAs
# platform files before vendor files

规则按 precedence 排序后匹配,文件中的物理顺序不是唯一决定因素。固定 name 比前缀更具体,更长的前缀比更短的前缀更具体;首条匹配成功后停止。

3.2 system server ​

源码文件:system/sepolicy/private/seapp_contexts

yaml
neverallow isSystemServer=false domain=system_server
neverallow isSystemServer=false domain=system_server_startup
isSystemServer=true domain=system_server_startup

只有 system server 的 selector 可以命中 system_server_startup;普通 app 即使伪造 seinfo 也会被 neverallow 排除。最终 system_server 再由 native setcon 切换到固定 system_server context。

3.3 system app保护 ​

源码文件:system/sepolicy/private/seapp_contexts

yaml
neverallow user=((?!system).)* domain=system_app
neverallow user=((?!system).)* type=system_app_data_file

system_app 绑定 system UID;user=_app 的平台签名应用不会因此进入 system_app,而会匹配 platform_app 或其 target SDK 变体。安装路径和 UID/user 是独立 selector。

3.4 isolated保护 ​

源码文件:system/sepolicy/private/seapp_contexts

yaml
neverallow user=_isolated isIsolatedComputeApp=false domain=((?!isolated_app).)*
neverallow user=((?!_isolated).)* domain=isolated_app
neverallow user=_isolated isIsolatedComputeApp=true domain=((?!isolated_compute_app).)*

isolated UID 只能进入 isolated_app;标记为 isolated compute 时只能进入 isolated_compute_app。两条规则同时保护正向和反向路径,防止普通 UID 获得 isolated domain,也防止 isolated compute 逃逸到普通域。

4. 具体规则 ​

4.1 platform与普通域 ​

源码文件:system/sepolicy/private/seapp_contexts

yaml
user=_app seinfo=platform minTargetSdkVersion=37 \
    domain=platform_app type=app_data_file levelFrom=user
user=_app seinfo=platform \
    domain=platform_app_36 type=app_data_file levelFrom=user
user=_app \
    domain=untrusted_app type=app_data_file levelFrom=all

platform seinfo 只是候选输入,还要满足 user、target SDK 和更具体的 name/isPrivApp 规则。无 seinfo 的普通 app 会落到 untrusted_app,但如果指定了 name,相关 neverallow 可能要求 non-default seinfo。

4.2 privileged路径 ​

源码文件:system/sepolicy/private/seapp_contexts

yaml
user=_app seinfo=platform isPrivApp=true \
    domain=priv_app type=privapp_data_file levelFrom=all
user=_app seinfo=media isPrivApp=true \
    domain=mediaprovider_app type=privapp_data_file levelFrom=all

isPrivApp 由 PackageManager 根据预装路径/状态提供,不是 APK 签名本身。priv_app 的 data type 也与普通 app_data_file 不同;AppDataHelper 传给 Installer 的 seInfo 会影响目录标签。

4.3 SDK sandbox ​

源码文件:system/sepolicy/private/seapp_contexts

yaml
user=_sdksandbox domain=sdk_sandbox levelFrom=user
user=_sdksandbox isSdkSandboxNext=true domain=sdk_sandbox_next levelFrom=user
user=_sdksandbox isSdkSandboxAudit=true domain=sdk_sandbox_audit levelFrom=user

SDK sandbox 的 next/audit selector 由运行时 flag 选择,且 seapp precedence 将其作为特殊用户处理。不能用普通 app 的 seinfo=platform 规则覆盖 sandbox neverallow。

4.4 vendor规则优先级 ​

源码文件:system/sepolicy/private/seapp_contexts

yaml
# Platform seapp_contexts files are compared before vendor and odm files.
# A fixed selector is more specific than a prefix.

vendor/odm 可以增加自己的 seapp_contexts,但平台规则在同等条件下先比较。新增 vendor 规则时必须说明 user、seinfo、name、isPrivApp 和 minTargetSdkVersion,否则可能被平台规则抢先匹配。

5. PackageManager ​

5.1 seInfo计算 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/SELinuxMMAC.java

java
public static String getSeInfo(
        PackageState packageState, AndroidPackage pkg,
        boolean isPrivileged, int targetSdkVersion) {
    String seInfo = null;
    synchronized (sPolicies) {
        if (sPolicyRead) {
            for (Policy policy : sPolicies) {
                seInfo = policy.getMatchedSeInfo(pkg);
                if (seInfo != null) {
                    break;
                }
            }
        }
    }
    if (seInfo == null) {
        seInfo = "default";
    }
    if (isPrivileged) {
        seInfo += ":privapp";
    }
    seInfo += ":targetSdkVersion=" + targetSdkVersion;
    String partition = getPartition(packageState);
    if (!partition.isEmpty()) {
        seInfo += ":partition=" + partition;
    }
    return seInfo;
}

SELinuxMMAC 输出基础 seinfo、privapp、targetSdkVersion 和 partition 后缀。seapp_contexts 的冒号分隔 selector 正是消费这个完整字符串的语法;不能只把 XML 的 value 当作最终输入。

5.2 PackageState ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ScanPackageUtils.java

java
pkgSetting.getTransientState().setSeInfo(
        SELinuxMMAC.getSeInfo(
                pkgSetting, parsedPackage, sharedUserSetting,
                injector.getCompatibility()));

扫描/安装阶段把 seInfo 写入 PackageSetting transient state。后续进程启动和 app data 创建读取的是这个状态,而不是重新遍历 XML。

5.3 shared UID ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/SELinuxMMAC.java

java
if (sharedUser != null && sharedUser.getPackages().size() != 0) {
    // All packages in a shared UID must use one target SDK selector.
    return sharedUser.getSeInfoTargetSdkVersion();
}

shared UID 的 target SDK selector 由 shared user owner 统一维护,避免同一 UID 的包进入不同 domain。安装新包不会立即改变既有 shared user selector,直到下一次状态重算。

6. Zygote消费者 ​

6.1 参数编码 ​

源码文件:frameworks/base/core/java/android/os/ZygoteProcess.java

java
argsForZygote.add("--setuid=" + uid);
argsForZygote.add("--setgid=" + gid);
argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
if (seInfo != null) {
    argsForZygote.add("--seinfo=" + seInfo);
}
if (appDataDir != null) {
    argsForZygote.add("--app-data-dir=" + appDataDir);
}

ProcessList 传入的 seInfo、UID、target SDK 和 app data dir 在 socket 协议中一起到达 zygote。缺失 seInfo 不会自动变成 platform;libselinux 会按空/默认 selector 匹配。

6.2 Native切换 ​

源码文件:frameworks/base/core/jni/com_android_internal_os_Zygote.cpp

cpp
const char* se_info_ptr =
        se_info.has_value() ? se_info.value().c_str() : nullptr;
if (selinux_android_setcontext(
        uid, is_system_server, se_info_ptr, nice_name_ptr) == -1) {
    fail_fn(CREATE_ERROR(
        "selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed",
        uid, is_system_server, se_info_ptr, nice_name_ptr));
}

selinux_android_setcontext 负责把 uid、system-server 标志、seInfo 和进程名交给 Android libselinux;失败通过 fail_fn 结束 child。它是 domain/type 选择的直接消费者,但 app data mount isolation 在同一 specialization 中是另一条资源路径。

6.3 app data隔离 ​

源码文件:frameworks/base/core/jni/com_android_internal_os_Zygote.cpp

cpp
if (mount_data_dirs) {
    isolateAppData(env, pkg_data_info_list,
                   allowlisted_data_info_list, uid,
                   process_name, managed_nice_name, fail_fn);
    isolateJitProfile(env, pkg_data_info_list, uid,
                      process_name, managed_nice_name, fail_fn);
}
if (mount_storage_dirs) {
    BindMountStorageDirs(env, pkg_data_info_list,
                         uid, process_name, managed_nice_name, fail_fn);
}

mount_data_dirs 和 mount_storage_dirs 决定 mount namespace 中可见的目录集合;它们不决定 process domain。一个 app 可能 domain 匹配正确但因 bind mount 参数错误看不到自己的数据。

7. 权限与隔离 ​

7.1 untrusted限制 ​

源码文件:system/sepolicy/private/untrusted_app_all.te、system/sepolicy/private/untrusted_app.te

text
allow { untrusted_app_all -isolated_app_all } self:global_capability_class_set { ... };
neverallow untrusted_app_all {
    domain
    -untrusted_app_all
    -crash_dump
}:process ptrace;

untrusted_app_all 聚合不同 target SDK 变体,允许规则和 neverallow 以聚合属性覆盖它们。ptrace、设备节点、系统属性和跨应用数据访问都由 app 基线和具体域的规则共同限制。

7.2 app data type ​

源码文件:system/sepolicy/private/seapp_contexts

yaml
user=_app domain=untrusted_app type=app_data_file levelFrom=all
user=_app isPrivApp=true domain=priv_app type=privapp_data_file levelFrom=all
user=system seinfo=platform domain=system_app type=system_app_data_file levelFrom=user

type 输出被 Installer 和 restorecon 使用;domain 输出被 Zygote/libselinux 使用。排查目录访问 denial 时应先看 tcontext 是否为 app_data_file/privapp_data_file,而不是只看进程 scontext。

7.3 变体策略 ​

源码文件:system/sepolicy/private/untrusted_app_32.te、untrusted_app_33.te、untrusted_app_34.te

text
type untrusted_app_32, domain;
type untrusted_app_33, domain;
type untrusted_app_34, domain;

target SDK 变化可以让同一基础 app 类进入不同变体域;这些域共享 untrusted_app_all 的部分规则,但可在各自文件中收紧新平台行为。不要把 untrusted_app_34 当成“另一个签名等级”。

8. 失败与测试 ​

8.1 neverallow测试 ​

源码文件:system/sepolicy/private/seapp_contexts

yaml
neverallow user=_app isPrivApp=false name=.* seinfo=""
neverallow user=_app isPrivApp=false name=.* seinfo=default

输入是普通 app、非特权、指定包名但缺少有效 seinfo;断言要求策略编译拒绝该 rule。它验证 selector 安全约束,不验证 APK 签名摘要或 Zygote 实际启动。

8.2 AppData恢复 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/AppDataHelper.java

java
return batch.createAppData(args).whenComplete((createAppDataResult, e) -> {
    if (e != null) {
        Slog.w(TAG, "Failed to create app data for " + packageName
                + ", but trying to recover: " + e);
        destroyAppDataLeafLIF(packageName, volumeUuid, userId, flags);
        try {
            createAppDataResult = mInstaller.createAppData(args);
        } catch (Exception retryError) {
            Slog.e(TAG, "Recovery createAppData failed", retryError);
        }
    }
});

输入是包含 seInfo 的 CreateAppDataArgs;第一次 Installer 创建失败时,代码先 destroy app data leaf,再重试创建。它证明 app data consumer 有恢复路径,不证明进程 domain 已经切换成功。

8.3 设备诊断 ​

sh
# Show process domain and MLS categories.
adb shell 'ps -AZ | grep <package.name>'

# Show package-level and per-user seInfo values.
adb shell 'dumpsys package <package.name> | grep -E "seinfo|seInfo"'

# Show app data directory type.
adb shell 'ls -Zd /data/user/0/<package.name>'

# Locate selectors and domain outputs for a target.
rg -n 'user=_app|user=_isolated|seinfo=|isPrivApp=|domain=' \
  system/sepolicy/private/seapp_contexts

# Read AVC fields for process or data labels.
adb shell 'dmesg | grep "avc: denied" | grep <package.name>'

ps -AZ 证明进程 scontext,dumpsys package 证明 PackageState 输入,ls -Zd 证明目录 tcontext,源码搜索定位 selector,AVC 再显示实际拒绝的 source/target/class/permission。单条命令不能证明完整链路。

9. 源码导航 ​

  1. system/sepolicy/private/seapp_contexts:selector、precedence、输出和 neverallow。
  2. system/sepolicy/public/app.te、private/app.te:具体域声明、聚合属性和权限。
  3. frameworks/base/services/core/java/com/android/server/pm/SELinuxMMAC.java:签名匹配、seInfo 后缀和 shared UID。
  4. frameworks/base/services/core/java/com/android/server/pm/ScanPackageUtils.java:PackageState 写入。
  5. frameworks/base/services/core/java/com/android/server/pm/AppDataHelper.java:目录创建、seInfo 传递和恢复。
  6. frameworks/base/core/java/android/os/ZygoteProcess.java:启动参数编码。
  7. frameworks/base/core/jni/com_android_internal_os_Zygote.cpp:specialize、mount isolation 和 setcontext。
  8. external/selinux/libselinux/src/android/android_platform.c:seapp_context_lookup 的 native 消费者。

使用一个 platform 签名的普通 UID 应用做练习:先确认 PackageManager 的基础 seInfo,再检查 isPrivApp 和 target SDK 后缀;然后在 Zygote 参数中找到完整 seInfo,最后用 ps -AZ 与 ls -Zd 分别验证进程和数据目录。若只改变签名、只改变安装路径、只改变 target SDK 或只改变 isolated 标志,应能指出是哪个 selector、输出 type 或消费者发生了变化。