Android5.1.1源码 - zygote fork出的子进程如何权限降级

Android5.1.1源码 - zygote fork出的子进程如何权限降级

@(Android研究)[Android5.1.1|zygote|fork]


[TOC]


前言

本文公开首发于阿里聚安全博客:https://jaq.alibaba.com/community/index.htm?spm=0.0.0.0.ycEUXK

如果不知道zygote是什么,或者好奇zygote如何启动,可以去看老罗的文章: Android系统进程Zygote启动过程的源代码分析

所有Android应用进程都是zygote fork出来的,新fork出来的应用进程还保持着root权限,这显然是不被允许的,所以这个fork出来的子进程的权限需要被降级,本文说的就是Android源码在什么地方执行了权限降级的操作。

##执行路径

下面的runSelectLoop方法是类ZygoteInit的成员方法,它在文件"frameworks/base/core/java/com/android/internal/os/ZygoteInit.java"中,下面是它的源码:

/**
 * Runs the zygote process's select loop. Accepts new connections as
 * they happen, and reads commands from connections one spawn-request's
 * worth at a time.
 *
 * @throws MethodAndArgsCaller in a child process when a main() should
 * be executed.
 */
private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
    ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
    ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
    FileDescriptor[] fdArray = new FileDescriptor[4];

    fds.add(sServerSocket.getFileDescriptor());
    peers.add(null);

    int loopCount = GC_LOOP_COUNT;
    while (true) {
        int index;

        /*
         * Call gc() before we block in select().
         * It's work that has to be done anyway, and it's better
         * to avoid making every child do it.  It will also
         * madvise() any free memory as a side-effect.
         *
         * Don't call it every time, because walking the entire
         * heap is a lot of overhead to free a few hundred bytes.
         */
        if (loopCount <= 0) {
            gc();
            loopCount = GC_LOOP_COUNT;
        } else {
            loopCount--;
        }


        try {
            fdArray = fds.toArray(fdArray);
            index = selectReadable(fdArray);
        } catch (IOException ex) {
            throw new RuntimeException("Error in select()", ex);
        }

        if (index < 0) {
            throw new RuntimeException("Error in select()");
        } else if (index == 0) {
            ZygoteConnection newPeer = acceptCommandPeer(abiList);
            peers.add(newPeer);
            fds.add(newPeer.getFileDescriptor());
        } else {
            boolean done;
            done = peers.get(index).runOnce();

            if (done) {
                peers.remove(index);
                fds.remove(index);
            }
        }
    }
}

zygote会在这个方法中等待客户端通知启动一个新的应用程序,详情可以看前言部分列出的文章。现在我们关心的是**done = peers.get(index).runOnce();**语句,这个语句调用了runOnce方法启动了一个新的应用进程,runOnce方法是ZygoteConnection类的成员方法,下文从runOnce方法开始分析。

ZygoteConnection.runOnce方法在文件"frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java"中,下面是它的源码:

/**
 * Reads one start command from the command socket. If successful,
 * a child is forked and a {@link ZygoteInit.MethodAndArgsCaller}
 * exception is thrown in that child while in the parent process,
 * the method returns normally. On failure, the child is not
 * spawned and messages are printed to the log and stderr. Returns
 * a boolean status value indicating whether an end-of-file on the command
 * socket has been encountered.
 *
 * @return false if command socket should continue to be read from, or
 * true if an end-of-file has been encountered.
 * @throws ZygoteInit.MethodAndArgsCaller trampoline to invoke main()
 * method in child process
 */
boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {

    String args[];
    Arguments parsedArgs = null;
    FileDescriptor[] descriptors;

    ......

    try {
        ......

        pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
                parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
                parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,
                parsedArgs.appDataDir);
        checkTime(startTime, "zygoteConnection.runOnce: postForkAndSpecialize");
    } catch (IOException ex) {
        logAndPrintError(newStderr, "Exception creating pipe", ex);
    } catch (ErrnoException ex) {
        logAndPrintError(newStderr, "Exception creating pipe", ex);
    } catch (IllegalArgumentException ex) {
        logAndPrintError(newStderr, "Invalid zygote arguments", ex);
    } catch (ZygoteSecurityException ex) {
        logAndPrintError(newStderr,
                "Zygote security policy prevents request: ", ex);
    }

    ......
}

parsedArgs中保存了要启动的应用的信息,它的类型是Arguments,Arguments是ZygoteConnection的内部类。

runOnce方法中调用了Zygote.forkAndSpecialize方法,这个方法在文件"frameworks/base/core/java/com/android/internal/os/Zygote.java"中,下面是它的源码:

/**
 * fork出一个新的VM实例。当前VM的启动选项中必须有-Xzygote标志。
 * 注意:新实例有所有的root能力(capabilities)。这个新进程被期望调用capset()。
 *
 * @param uid 新进程的UNIX uid应当在fork()之后且产生任何线程之前调用setuid()。
 * @param gid 新进程的UNIX gid应当在fork()之后且产生任何线程之前调用setgid()。
 * @param gids null-ok; a list of UNIX gids that the new process should
 * setgroups() to after fork and before spawning any threads.
 * @param debugFlags bit flags that enable debugging features.
 * @param rlimits null-ok an array of rlimit tuples, with the second
 * dimension having a length of 3 and representing
 * (resource, rlim_cur, rlim_max). These are set via the posix
 * setrlimit(2) call.
 * @param seInfo 可以为null,字符串指定新进程的SELinux信息。
 * @param niceName null-ok a string specifying the process name.
 * @param fdsToClose an array of ints, holding one or more POSIX
 * file descriptor numbers that are to be closed by the child
 * (and replaced by /dev/null) after forking.  An integer value
 * of -1 in any entry in the array means "ignore this one".
 * @param instructionSet null-ok the instruction set to use.
 * @param appDataDir null-ok the data directory of the app.
 *
 * @return 如果这是一个子进程,则返回0;如果这是一个父进程,则返回子进程pid;如果出错则返回-1。
 */
public static int forkAndSpecialize(int uid, int gid, int[] gids, int debugFlags,
      int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
      String instructionSet, String appDataDir) {
    long startTime = SystemClock.elapsedRealtime();
    VM_HOOKS.preFork();
    checkTime(startTime, "Zygote.preFork");
    int pid = nativeForkAndSpecialize(
              uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
              instructionSet, appDataDir);
    checkTime(startTime, "Zygote.nativeForkAndSpecialize");
    VM_HOOKS.postForkCommon();
    checkTime(startTime, "Zygote.postForkCommon");
    return pid;
}

在这个方法中调用了nativeForkAndSpecialize方法。

nativeForkAndSpecialize是一个native方法,在native代码中它的函数名是com_android_internal_os_Zygote_nativeForkAndSpecialize,这个函数在文件"frameworks/base/core/jni/com_android_internal_os_Zygote.cpp"中,下面是它的源码:

static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
        JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
        jint debug_flags, jobjectArray rlimits,
        jint mount_external, jstring se_info, jstring se_name,
        jintArray fdsToClose, jstring instructionSet, jstring appDataDir) {
    // Grant CAP_WAKE_ALARM to the Bluetooth process.
    jlong capabilities = 0;
    if (uid == AID_BLUETOOTH) {
        capabilities |= (1LL << CAP_WAKE_ALARM);
    }

    return ForkAndSpecializeCommon(env, uid, gid, gids, debug_flags,
            rlimits, capabilities, capabilities, mount_external, se_info,
            se_name, false, fdsToClose, instructionSet, appDataDir);
}

在这个函数中调用了ForkAndSpecializeCommon函数。

子进程权限降级函数

ForkAndSpecializeCommon函数在文件"frameworks/base/core/jni/com_android_internal_os_Zygote.cpp"中,在这个函数中调用了fork函数,并且fork出的子进程将自身权限降级,下面是它的源码:

// Utility routine to fork zygote and specialize the child process.
static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids,
                                     jint debug_flags, jobjectArray javaRlimits,
                                     jlong permittedCapabilities, jlong effectiveCapabilities,
                                     jint mount_external,
                                     jstring java_se_info, jstring java_se_name,
                                     bool is_system_server, jintArray fdsToClose,
                                     jstring instructionSet, jstring dataDir) {
  ......

  pid_t pid = fork();

  if (pid == 0) {
    // 子进程
    gMallocLeakZygoteChild = 1;

    // Clean up any descriptors which must be closed immediately
    DetachDescriptors(env, fdsToClose);

    ckTime(start, "ForkAndSpecializeCommon:Fork and detach");

    // Keep capabilities across UID change, unless we're staying root.
    if (uid != 0) {
      EnableKeepCapabilities(env);
    }

    ......

    SetGids(env, javaGids);

    SetRLimits(env, javaRlimits);

    ......

    int rc = setresgid(gid, gid, gid);
    if (rc == -1) {
      ALOGE("setresgid(%d) failed", gid);
      RuntimeAbort(env);
    }

    rc = setresuid(uid, uid, uid);
    if (rc == -1) {
      ALOGE("setresuid(%d) failed", uid);
      RuntimeAbort(env);
    }

    ......

    env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, debug_flags,
                              is_system_server ? NULL : instructionSet);
    ckTime(start, "ForkAndSpecializeCommon:PostForkChildHooks returns");
    if (env->ExceptionCheck()) {
      ALOGE("Error calling post fork hooks.");
      RuntimeAbort(env);
    }
  } else if (pid > 0) {
    // the parent process
  }
  return pid;
}

在这个函数中子进程分别调用了SetGids、SetRLimits、setresgid、setresuid,设置了组ID和用户ID将自身权限降级

转载于:https://my.oschina.net/ibuwai/blog/527801

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值