Skip to content

Zygote Domain

追踪 Android 17 zygote 域从 init 启动、预加载、fork/specialize 到 domain 切换、数据隔离和 seccomp。

基于android-17.0.0_r1
AndroidSELinuxzygoteseccomp源码阅读

Zygote Domain ​

本文面向已经读过 Init Domain、System Server Domain 和 seapp_contexts 的读者。本文聚焦 zygote 作为进程孵化器时的 SELinux owner、资源继承和子进程隔离,不把 Java 类预加载、应用启动协议和所有 app domain 的策略混为一谈。

读完后,读者应能从 zygote rc service 找到 init 到 zygote 的 transition,从 fork 请求追到 native specialize,再区分 system_server、普通 app、webview_zygote 和 app_zygote 的目标域;同时能解释为什么 zygote 需要 mount、execmem、setcurrent 和 seccomp 相关权限,却不能任意 transition 或 ptrace 其他进程。

1. 启动与职责 ​

1.1 zygote类型 ​

源码文件:system/sepolicy/public/zygote.te

text
type zygote, domain;
type zygote_exec, system_file_type, exec_type, file_type;
type zygote_tmpfs, file_type;

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

text
typeattribute zygote coredomain;
typeattribute zygote mlstrustedsubject;
init_daemon_domain(zygote)
tmpfs_domain(zygote)

zygote_exec 是 init execve 时使用的文件 type;zygote 是进程 domain;zygote_tmpfs 是 zygote 在 tmpfs 上创建或执行代码的对象 type。coredomain 和 mlstrustedsubject 只描述 policy 属性,不会自动授予 fork、mount 或 setuid 权限。

1.2 rc入口 ​

源码文件:system/core/rootdir/init.zygote64.rc

text
service zygote /system/bin/app_process64 -Xzygote /system/bin \
    --zygote --start-system-server --socket-name=zygote
    class main
    socket zygote stream 660 root system

init 的 Service 对象拥有该 service 的 class、命令行和 socket。init 执行 app_process64 时,file_contexts 返回 zygote_exec,init_daemon_domain(zygote) 提供 init 到 zygote 的自动 transition。服务进入 zygote 后,命令行由 ZygoteInit.main 解析。

1.3 Java主循环 ​

源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

java
if (!enableLazyPreload) {
    bootTimingsTraceLog.traceBegin("ZygotePreload");
    EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
            SystemClock.uptimeMillis());
    preload(bootTimingsTraceLog);
    EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
            SystemClock.uptimeMillis());
    bootTimingsTraceLog.traceEnd();
}

gcAndFinalize();
Zygote.initNativeState(isPrimaryZygote);
ZygoteHooks.stopZygoteNoThreadCreation();

zygoteServer = new ZygoteServer(isPrimaryZygote);
if (startSystemServer) {
    Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
    if (r != null) {
        r.run();
        return;
    }
}
caller = zygoteServer.runSelectLoop(abiList);

zygote 先预加载 classes/resources/shared libraries,再做 GC 和 native state 初始化,最后进入 command socket select loop。startSystemServer 分支在同一进程里 fork child;父 zygote 回到 select loop,child 返回 Runnable 并执行 SystemServer。

2. Fork目标 ​

2.1 目标域 ​

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

text
allow zygote self:process setcurrent;
allow zygote system_server_startup:process dyntransition;
allow zygote appdomain:process dyntransition;
allow zygote webview_zygote:process dyntransition;
allow zygote app_zygote:process dyntransition;

zygote 只能切到四类目标集合。system_server 先进入 system_server_startup;普通 app 进入 appdomain 的具体 seapp domain;WebView 和 child zygote 使用专用 domain。setcurrent 允许 zygote 在 native specialization 中调用 setcon,但目标 type 仍受 policy 集合约束。

2.2 system_server ​

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

cpp
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));
}

if (is_system_server) {
    env->CallStaticVoidMethod(
            gZygoteClass, gCallPostForkSystemServerHooks, runtime_flags);

    static const char* kSystemServerLabel = "u:r:system_server:s0";
    if (selinux_android_setcon(kSystemServerLabel) != 0) {
        fail_fn(CREATE_ERROR("selinux_android_setcon(%s)",
                             kSystemServerLabel));
    }
}

system_server 的最终标签是固定 u:r:system_server:s0;普通 app 则由 se_info、uid 和 seapp_contexts selector 计算。system_server_startup 负责 AOT/JIT 资源和 setcon 权限,最终 domain 通过显式 setcon 接管。

2.3 普通app分支 ​

源码文件: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 failed for uid %d", uid));
}

if (is_child_zygote) {
    initUnsolSocketToSystemServer();
}

env->CallStaticVoidMethod(
        gZygoteClass, gCallPostForkChildHooks, runtime_flags,
        is_system_server, is_child_zygote, managed_instruction_set);

普通 app 的 se_info 来自 PackageManager,包含 seinfo、target SDK、用户和其他 selector;selinux_android_setcontext 将它交给 libselinux,最终得到 untrusted_app、platform_app、isolated_app 等具体 domain。child hook 只在 context 设置成功后调用。

3. Native切换 ​

3.1 参数顺序 ​

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

java
argsForZygote.add("--runtime-args");
argsForZygote.add("--setuid=" + uid);
argsForZygote.add("--setgid=" + gid);
argsForZygote.add("--runtime-flags=" + runtimeFlags);

argsForZygote.add("--target-sdk-version=" + targetSdkVersion);

if (niceName != null) {
    argsForZygote.add("--nice-name=" + niceName);
}
if (seInfo != null) {
    argsForZygote.add("--seinfo=" + seInfo);
}
if (appDataDir != null) {
    argsForZygote.add("--app-data-dir=" + appDataDir);
}
if (startChildZygote) {
    argsForZygote.add("--start-child-zygote");
}

ActivityManager/ProcessList 通过 ZygoteProcess 把 uid、runtime flags、target SDK、seInfo、data dir 和 child-zygote 标志编码为 socket 请求。zygote 读取同一协议后,native 和 Java 两层共同决定 specialization 参数。

3.2 安全限制 ​

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

cpp
SetInheritable(permitted_capabilities, fail_fn);
DropCapabilitiesBoundingSet(fail_fn, bounding_capabilities);

if (getuid() == 0) {
    const int rc = createProcessGroup(uid, getpid());
    if (rc != 0) {
        fail_fn(CREATE_ERROR("createProcessGroup failed"));
    }
}

SetGids(env, gids, is_child_zygote, fail_fn);
SetRLimits(env, rlimits, fail_fn);

SetUpSeccompFilter(uid, is_child_zygote);
SetSchedulerPolicy(fail_fn, is_top_app);

if (setresuid(uid, uid, uid) == -1) {
    fail_fn(CREATE_ERROR("setresuid failed"));
}

specialize 的顺序有安全原因:先设置继承 capability、创建 process group、设置 groups/rlimits,再安装 seccomp 和 scheduler policy,最后 setresuid。丢弃 bounding set 后,子进程不能重新获得被移除的 capability。

3.3 seccomp选择 ​

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

cpp
static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) {
    if (!gIsSecurityEnforced) {
        ALOGI("seccomp disabled by setenforce 0");
        return;
    }

    if (uid >= AID_APP_START) {
        if (is_child_zygote) {
            set_app_zygote_seccomp_filter();
        } else {
            set_app_seccomp_filter();
        }
    } else {
        set_system_seccomp_filter();
    }
}

uid 决定 system/app filter,is_child_zygote 再区分 app zygote filter。setenforce 0 会跳过 seccomp 安装,但不等于 SELinux policy 被删除;这是运行模式条件,不能用 permissive 实验推断 enforcing 的系统调用限制。

4. 数据隔离 ​

4.1 zygote策略 ​

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

text
allow zygote {
    system_userdir_file
    system_data_file
    user_profile_root_file
    user_profile_data_file
    media_rw_data_file
    properties_device
}:dir { mounton search };

allow zygote mirror_data_file:dir search;
allow zygote tmpfs:dir { create_dir_perms mounton };
allow zygote tmpfs:{ dir lnk_file } relabelfrom;
allow zygote system_userdir_file:dir relabelto;
allow zygote system_data_file:{ dir lnk_file } relabelto;

zygote 在 fork 后为 app mount namespace 隔离 CE/DE、profile、media 和 property override 目录。它需要 mounton、search、relabelto 等权限,但这些动作发生在 child mount namespace;不会让 zygote domain 直接成为 app 对象的长期 owner。

4.2 native隔离调用 ​

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

cpp
ensureInAppMountNamespace(fail_fn);

if (mount_data_dirs) {
    appid_t appId = multiuser_get_app_id(uid);
    if (appId >= AID_SDK_SANDBOX_PROCESS_START &&
        appId <= AID_SDK_SANDBOX_PROCESS_END) {
        isolateSdkSandboxData(env, pkg_data_info_list, uid,
                              process_name, managed_nice_name, fail_fn);
    }
    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 来自 ZygoteProcess 的启动参数。普通 app 可能需要 app data、JIT profile 和 storage bind mount;system_server 不传 app data isolation list,因此不走这段 app 隔离逻辑。

4.3 资源继承 ​

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

text
allow zygote zygote:memfd_file execute;
allow zygote zygote_tmpfs:file execute;
allow zygote ashmem_libcutils_device:chr_file execute;
allow zygote idmap_exec:file rx_file_perms;
allow zygote dex2oat_exec:file rx_file_perms;
allow zygote apex_art_data_file:file { r_file_perms execute };

这些权限支持预加载代码、JIT memory、idmap/dex2oat 和 ART APEX 产物。子进程是否能继续执行这些对象取决于它自己的 domain;zygote 拥有 execute 不等于 appdomain 自动拥有同样 execute。

5. 安全约束 ​

5.1 目标域白名单 ​

源码文件:system/sepolicy/private/zygote.te、system/sepolicy/private/system_server.te

text
allow zygote system_server_startup:process dyntransition;
allow zygote appdomain:process dyntransition;
allow zygote webview_zygote:process dyntransition;
allow zygote app_zygote:process dyntransition;

neverallow system_server *:process dyntransition;
neverallow system_server {
    domain -clatd -crash_dump -perfetto -trace_redactor
}:process transition;

zygote 是切换发起者;system_server 切换完成后不能再随意切域。system_server 的少量 transition 例外由崩溃转储、profiling 或网络配置等明确消费者使用。

5.2 proc与进程控制 ​

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

text
allow zygote appdomain:dir { getattr search };
allow zygote appdomain:file { r_file_perms };
allow zygote system_server:process { getpgid setpgid };
allow zygote appdomain:process { getpgid setpgid };
allow zygote webview_zygote:process { getpgid setpgid };
allow zygote app_zygote:process { getpgid setpgid };

zygote 需要读取 app proc 目录、调整 child process group,并把 system_server 加入 peer group。这里没有对 appdomain 的 signal/kill 泛化权限;应用进程的进程管理由 system_server 等 domain 负责。

5.3 neverallow ​

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

text
neverallow zygote ~{
    self
    system_server_startup
    appdomain
    webview_zygote
    app_zygote
}:process dyntransition;

neverallow zygote app_data_file_type:dir ~getattr;

第一条把 dyntransition 目标限制在 allowlist,第二条限制 zygote 对 app data 目录的操作集合。neverallow 在策略编译时检查最终展开结果,运行时不会产生一条“拒绝日志”来替代编译失败。

6. 失败与测试 ​

6.1 fork失败 ​

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

cpp
pid_t pid = zygote::ForkCommon(env, true,
                               fds_to_close,
                               fds_to_ignore,
                               true);
if (pid == 0) {
    SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
                     permitted_capabilities, effective_capabilities,
                     0, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
                     false, nullptr, nullptr, false,
                     nullptr, nullptr, false, false, false);
} else if (pid > 0) {
    gSystemServerPid = pid;
}

fork 返回小于零时由上层 fail_fn/异常路径处理;child 在 specialization 任一阶段失败会 abort。父 zygote 只有确认 pid 后继续监听,不会把 child 的 partial state 当成已启动服务。

6.2 Java测试 ​

源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java

java
private Runnable handleChildProc(ZygoteArguments parsedArgs,
        FileDescriptor pipeFd, boolean isZygote) {
    closeSocket();
    Zygote.setAppProcessName(parsedArgs, TAG);
    if (parsedArgs.mInvokeWith != null) {
        WrapperInit.execApplication(parsedArgs.mInvokeWith,
                parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion,
                VMRuntime.getCurrentInstructionSet(),
                pipeFd, parsedArgs.mRemainingArgs);
        throw new IllegalStateException(
                "WrapperInit.execApplication unexpectedly returned");
    } else {
        if (!isZygote) {
            return ZygoteInit.zygoteInit(
                    parsedArgs.mTargetSdkVersion,
                    parsedArgs.mDisabledCompatChanges,
                    parsedArgs.mEnabledCompatChanges,
                    parsedArgs.mRemainingArgs, null);
        } else {
            return ZygoteInit.childZygoteInit(
                    parsedArgs.mRemainingArgs);
        }
    }
}

输入是已由 native fork/specialize 处理的 ZygoteArguments;普通 child 进入 zygoteInit,child zygote 进入 childZygoteInit,wrapper 返回属于异常。该测试/代码路径证明 Java 后处理分支,不证明 kernel dyntransition 已允许。

6.3 设备诊断 ​

sh
# Confirm the parent and child process contexts.
adb shell 'ps -AZ | grep -E "zygote|system_server|webview_zygote|app_zygote"'

# Verify executable labels and init transition input.
adb shell 'ls -Z /system/bin/app_process32 /system/bin/app_process64'
rg -n 'init_daemon_domain\(zygote\)|zygote_exec|dyntransition' \
  system/sepolicy/public system/sepolicy/private

# Check seccomp and SELinux failures separately.
adb shell 'logcat -b all | grep -E "seccomp|setcontext|setcon|SpecializeCommon"'

# Inspect process-group and data-isolation symptoms.
adb shell 'dumpsys activity processes | grep -E "pid=|processName="'

ps -AZ 验证最终 domain;ls -Z 与 policy 搜索验证 init→zygote 输入;日志区分 seccomp/setcontext;ActivityManager 输出用于关联 pid 和 app process。任何一条命令都不能单独证明全部 specialization 步骤成功。

7. 源码导航 ​

  1. system/sepolicy/public/zygote.te、system/sepolicy/private/zygote.te:类型、dyntransition、数据隔离、seccomp 前置权限和 neverallow。
  2. frameworks/base/core/java/com/android/internal/os/ZygoteInit.java:预加载、forkSystemServer 和 select loop。
  3. frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java:socket 请求后的 child/parent 处理。
  4. frameworks/base/core/java/android/os/ZygoteProcess.java:启动参数、seInfo、mount isolation 和 socket 协议。
  5. frameworks/base/core/jni/com_android_internal_os_Zygote.cpp:SpecializeCommon、能力、seccomp、setcontext 和 child hooks。
  6. system/sepolicy/private/system_server_startup.te、system_server.te:startup → system_server 交接和最终约束。

以普通 app 启动为练习:从 ZygoteProcess 的 seInfo、uid、data isolation 参数开始,追到 SpecializeCommon 的 seccomp、mount、setresuid 和 selinux_android_setcontext,再对照 zygote 的 appdomain dyntransition 与目标 app 的 seapp_contexts 规则。若只改了 file_contexts、只删除 dyntransition 或只关闭 seccomp,应能分别预测 init transition、kernel domain switch 和系统调用过滤哪一层发生变化。