Android5.1.1源码 - zygote fork出的子进程如何权限降级
@(Android研究)[Android5.1.1|zygote|fork]
[TOC]
前言
本文公开首发于阿里聚安全博客:
如果不知道zygote是什么,或者好奇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 { ArrayListfds = new ArrayList (); ArrayList peers = new ArrayList (); 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将自身权限降级
。