Skip to content

Zygote安全限制

追踪 Zygote 命令校验、uid/gid、文件描述符关闭、SELinux context、capability 和 seccomp 的生效顺序。

基于android-17.0.0_r1
AndroidZygoteSELinuxSeccomp源码阅读

Zygote安全限制 ​

Zygote 的安全模型不是一次 setuid()。一个 spawn 请求先由 ZygoteConnection.processCommand() 根据 Unix peer credentials 检查 uid、invoke-with 和 capability 参数,再进入 Zygote.forkAndSpecialize();native SpecializeCommon 在子进程中按顺序关闭继承 fd、设置 rlimit、切换 SELinux context、裁剪 capability、安装 seccomp,并最后把进程变成目标身份。

1. Java命令校验 ​

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

java
if (parsedArgs.mPermittedCapabilities != 0
        || parsedArgs.mEffectiveCapabilities != 0) {
    throw new ZygoteSecurityException(
            "Client may not specify capabilities: permitted="
            + Long.toHexString(parsedArgs.mPermittedCapabilities)
            + ", effective="
            + Long.toHexString(parsedArgs.mEffectiveCapabilities));
}

Zygote.applyUidSecurityPolicy(parsedArgs, peer);
Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer);
Zygote.applyDebuggerSystemProperty(parsedArgs);
Zygote.applyInvokeWithSystemProperty(parsedArgs);

普通客户端不能在 spawn 请求里直接指定 capability;uid/gid 和 invoke-with 先根据 socket peer credentials 校验。debugger system properties 只调整允许的 runtime flags,不是给请求方任意加权限。

2. UID/GID策略 ​

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

java
static void applyUidSecurityPolicy(
        ZygoteArguments args, Credentials peer)
        throws ZygoteSecurityException {
    if (args.mUidSpecified
            && args.mUid < minChildUid(peer)) {
        throw new ZygoteSecurityException(
                "System UID may not launch process with UID < "
                + Process.SYSTEM_UID);
    }
    if (!args.mUidSpecified) {
        args.mUid = peer.getUid();
        args.mUidSpecified = true;
    }
    if (!args.mGidSpecified) {
        args.mGid = peer.getGid();
        args.mGidSpecified = true;
    }
}

static void applyInvokeWithSecurityPolicy(
        ZygoteArguments args, Credentials peer)
        throws ZygoteSecurityException {
    int peerUid = peer.getUid();
    if (args.mInvokeWith != null && peerUid != 0
            && (args.mRuntimeFlags
                & (Zygote.DEBUG_ENABLE_JDWP
                   | Zygote.DEBUG_ENABLE_PTRACE)) == 0) {
        throw new ZygoteSecurityException(
                "Peer is permitted to specify an explicit invoke-with "
                + "wrapper command only for debuggable applications.");
    }
}

未显式指定 uid/gid 时继承发起者 credentials;显式 uid 不能低于允许的最小 child UID。invoke-with 允许 root,非 root 只有在 JDWP/ptrace 调试标志已启用时才能使用。这个限制在 fork 前生效,拒绝时不会产生子进程。

3. 继承FD与rlimit ​

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

java
int[] fdsToClose = { -1, -1 };
FileDescriptor fd = mSocket.getFileDescriptor();
if (fd != null) fdsToClose[0] = fd.getInt$();
FileDescriptor zygoteFd =
        zygoteServer.getZygoteSocketFileDescriptor();
if (zygoteFd != null) fdsToClose[1] = zygoteFd.getInt$();

pid = Zygote.forkAndSpecialize(
        parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids,
        parsedArgs.mRuntimeFlags, rlimits,
        parsedArgs.mMountExternal, parsedArgs.mSeInfo,
        parsedArgs.mNiceName, fdsToClose, fdsToIgnore, ...);

子进程必须在切换到应用 UID 和权限前关闭 session socket 与 Zygote server socket,防止应用继承控制面 fd。invoke-with 还会创建 CLOEXEC 管道,并把 pipe fd 放入 fdsToIgnore,避免 fd 表检查误判。rlimit 则作为二维数组传给 native specialization,不是由 Java 进程事后补设。

4. Native特化 ​

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

cpp
static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid,
        jintArray gids, jint runtime_flags, jobjectArray rlimits,
        jlong permitted_capabilities, jlong effective_capabilities,
        ...)
{
    // 解析并关闭/替换子进程不应继承的 fd。
    ...
    // 设置 uid/gid、groups、rlimit、SELinux context、capability。
    ...
    // 根据 runtime flags 安装 app/system seccomp policy。
    ...
}

SpecializeCommon 是 native 安全状态的 owner。源码把大量动作集中在 fork 后的 child 路径;任何不可恢复的安全设置失败通过 fail_fn/RuntimeAbort 终止子进程,而不是让它以部分特权继续运行。

5. SELinux与能力 ​

源码文件: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));
}

SELinux context 由 uid、是否 SystemServer、seInfo 和进程名共同决定;不是 Java --nice-name 单独决定。capability 路径先设置 keepcaps、裁剪 bounding set,再调用 capset 写 permitted/effective/inheritable 集合。Zygote 的父进程保留创建能力,目标子进程必须完成裁剪后才进入正常代码。

6. Seccomp策略 ​

cpp
if ((runtime_flags & RuntimeFlags::DEBUG_ENABLE_JDWP) != 0) {
    ...
}

if (is_system_server) {
    set_system_seccomp_filter();
} else if (is_app_zygote) {
    set_app_zygote_seccomp_filter();
} else {
    set_app_seccomp_filter();
}

不同进程角色选择不同 seccomp policy;setenforce 0 等调试环境可能禁用过滤,但不能把调试设备行为当作生产默认。seccomp 安装失败会让 specialization 失败,生效时机在子进程执行目标 Java main/native main 之前。

7. 失败边界 ​

安全设置有明确的先后关系:Java 拒绝发生在 fork 前;fd 关闭和 uid/gid 变更发生在 child;SELinux、capability、seccomp 失败不能返回给父进程后继续使用半安全 child。ZygoteConnection 在父端异常时关闭控制 socket,让客户端立即得到失败,不把错误留成超时。

8. 导航与后续 ​

bash
# Java 侧命令校验、uid/invoke-with、fd 列表和 fork 调用。
rg -n "applyUidSecurityPolicy|applyInvokeWithSecurityPolicy|fdsToClose|forkAndSpecialize" \
  frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java \
  frameworks/base/core/java/com/android/internal/os/Zygote.java

# Native specialization、SELinux、capability、seccomp 和失败终止。
rg -n "SpecializeCommon|selinux_android_setcontext|capset|PR_CAPBSET_DROP|seccomp|RuntimeAbort|fail_fn" \
  frameworks/base/core/jni/com_android_internal_os_Zygote.cpp

下一篇将进入 SystemServer 的 fork 后处理和 RuntimeInit 接缝;不会重复本文的 uid/gid、SELinux、capability 和 seccomp 细节。