diff options
| -rw-r--r-- | core/java/android/os/ChildZygoteProcess.java | 44 | ||||
| -rw-r--r-- | core/java/android/os/ZygoteProcess.java | 55 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/RuntimeInit.java | 2 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/WebViewZygoteInit.java | 2 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/Zygote.java | 19 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/ZygoteConnection.java | 43 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/ZygoteInit.java | 13 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/ZygoteServer.java | 41 | ||||
| -rw-r--r-- | core/jni/com_android_internal_os_Zygote.cpp | 24 | ||||
| -rw-r--r-- | core/jni/fd_utils.cpp | 8 |
10 files changed, 222 insertions, 29 deletions
diff --git a/core/java/android/os/ChildZygoteProcess.java b/core/java/android/os/ChildZygoteProcess.java new file mode 100644 index 000000000000..337a3e279a1a --- /dev/null +++ b/core/java/android/os/ChildZygoteProcess.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.net.LocalSocketAddress; + +/** + * Represents a connection to a child-zygote process. A child-zygote is spawend from another + * zygote process using {@link startChildZygote()}. + * + * {@hide} + */ +public class ChildZygoteProcess extends ZygoteProcess { + /** + * The PID of the child zygote process. + */ + private final int mPid; + + ChildZygoteProcess(LocalSocketAddress socketAddress, int pid) { + super(socketAddress, null); + mPid = pid; + } + + /** + * Returns the PID of the child-zygote process. + */ + public int getPid() { + return mPid; + } +} diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 4a976403e7a8..57418c8b9879 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -33,6 +33,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.UUID; /*package*/ class ZygoteStartFailedEx extends Exception { ZygoteStartFailedEx(String s) { @@ -217,7 +218,8 @@ public class ZygoteProcess { try { return startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, invokeWith, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */, + zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); @@ -333,6 +335,8 @@ public class ZygoteProcess { * @param abi the ABI the process should use. * @param instructionSet null-ok the instruction set to use. * @param appDataDir null-ok the data directory of the app. + * @param startChildZygote Start a sub-zygote. This creates a new zygote process + * that has its state cloned from this zygote process. * @param extraArgs Additional arguments to supply to the zygote process. * @return An object that describes the result of the attempt to start the process. * @throws ZygoteStartFailedEx if process start failed for any reason @@ -348,6 +352,7 @@ public class ZygoteProcess { String instructionSet, String appDataDir, String invokeWith, + boolean startChildZygote, String[] extraArgs) throws ZygoteStartFailedEx { ArrayList<String> argsForZygote = new ArrayList<String>(); @@ -404,6 +409,10 @@ public class ZygoteProcess { argsForZygote.add(invokeWith); } + if (startChildZygote) { + argsForZygote.add("--start-child-zygote"); + } + argsForZygote.add(processClass); if (extraArgs != null) { @@ -418,6 +427,18 @@ public class ZygoteProcess { } /** + * Closes the connections to the zygote, if they exist. + */ + public void close() { + if (primaryZygoteState != null) { + primaryZygoteState.close(); + } + if (secondaryZygoteState != null) { + secondaryZygoteState.close(); + } + } + + /** * Tries to establish a connection to the zygote that handles a given {@code abi}. Might block * and retry if the zygote is unresponsive. This method is a no-op if a connection is * already open. @@ -549,4 +570,36 @@ public class ZygoteProcess { } Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + address.getName()); } + + /** + * Starts a new zygote process as a child of this zygote. This is used to create + * secondary zygotes that inherit data from the zygote that this object + * communicates with. This returns a new ZygoteProcess representing a connection + * to the newly created zygote. Throws an exception if the zygote cannot be started. + */ + public ChildZygoteProcess startChildZygote(final String processClass, + final String niceName, + int uid, int gid, int[] gids, + int runtimeFlags, + String seInfo, + String abi, + String instructionSet) { + // Create an unguessable address in the global abstract namespace. + final LocalSocketAddress serverAddress = new LocalSocketAddress( + processClass + "/" + UUID.randomUUID().toString()); + + final String[] extraArgs = {Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG + serverAddress.getName()}; + + Process.ProcessStartResult result; + try { + result = startViaZygote(processClass, niceName, uid, gid, + gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo, + abi, instructionSet, null /* appDataDir */, null /* invokeWith */, + true /* startChildZygote */, extraArgs); + } catch (ZygoteStartFailedEx ex) { + throw new RuntimeException("Starting child-zygote through Zygote failed", ex); + } + + return new ChildZygoteProcess(serverAddress, result.pid); + } } diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 895be082c679..bb5a0ad86dd4 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -230,7 +230,7 @@ public class RuntimeInit { * @param argv Argument vector for main() * @param classLoader the classLoader to load {@className} with */ - private static Runnable findStaticMain(String className, String[] argv, + protected static Runnable findStaticMain(String className, String[] argv, ClassLoader classLoader) { Class<?> cl; diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java index cadb66ae6e08..b38c851e5101 100644 --- a/core/java/com/android/internal/os/WebViewZygoteInit.java +++ b/core/java/com/android/internal/os/WebViewZygoteInit.java @@ -129,7 +129,7 @@ class WebViewZygoteInit { final Runnable caller; try { - sServer.registerServerSocket("webview_zygote"); + sServer.registerServerSocketFromEnv("webview_zygote"); // The select loop returns early in the child process after a fork and // loops forever in the zygote. caller = sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS)); diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index fac6b230bff2..28a7c1204071 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -69,6 +69,13 @@ public final class Zygote { private static final ZygoteHooks VM_HOOKS = new ZygoteHooks(); + /** + * An extraArg passed when a zygote process is forking a child-zygote, specifying a name + * in the abstract socket namespace. This socket name is what the new child zygote + * should listen for connections on. + */ + public static final String CHILD_ZYGOTE_SOCKET_NAME_ARG = "--zygote-socket="; + private Zygote() {} /** Called for some security initialization before any fork. */ @@ -100,6 +107,8 @@ public final class Zygote { * @param fdsToIgnore null-ok an array of ints, either null or holding * one or more POSIX file descriptor numbers that are to be ignored * in the file descriptor table check. + * @param startChildZygote if true, the new child process will itself be a + * new zygote process. * @param instructionSet null-ok the instruction set to use. * @param appDataDir null-ok the data directory of the app. * @@ -108,13 +117,13 @@ public final class Zygote { */ public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, - int[] fdsToIgnore, String instructionSet, String appDataDir) { + int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir) { VM_HOOKS.preFork(); // Resets nice priority for zygote process. resetNicePriority(); int pid = nativeForkAndSpecialize( uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, - fdsToIgnore, instructionSet, appDataDir); + fdsToIgnore, startChildZygote, instructionSet, appDataDir); // Enable tracing as soon as possible for the child process. if (pid == 0) { Trace.setTracingEnabled(true, runtimeFlags); @@ -128,7 +137,7 @@ public final class Zygote { native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, - int[] fdsToIgnore, String instructionSet, String appDataDir); + int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir); /** * Called to do any initialization before starting an application. @@ -188,8 +197,8 @@ public final class Zygote { native protected static void nativeUnmountStorageOnInit(); private static void callPostForkChildHooks(int runtimeFlags, boolean isSystemServer, - String instructionSet) { - VM_HOOKS.postForkChild(runtimeFlags, isSystemServer, instructionSet); + boolean isZygote, String instructionSet) { + VM_HOOKS.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet); } /** diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 6a87b1f4d3fd..a32fb4316d12 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -221,8 +221,8 @@ class ZygoteConnection { pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, - parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet, - parsedArgs.appDataDir); + parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote, + parsedArgs.instructionSet, parsedArgs.appDataDir); try { if (pid == 0) { @@ -233,7 +233,8 @@ class ZygoteConnection { IoUtils.closeQuietly(serverPipeFd); serverPipeFd = null; - return handleChildProc(parsedArgs, descriptors, childPipeFd); + return handleChildProc(parsedArgs, descriptors, childPipeFd, + parsedArgs.startChildZygote); } else { // In the parent. A pid < 0 indicates a failure and will be handled in // handleParentProc. @@ -415,6 +416,14 @@ class ZygoteConnection { boolean preloadDefault; /** + * Whether this is a request to start a zygote process as a child of this zygote. + * Set with --start-child-zygote. The remaining arguments must include the + * CHILD_ZYGOTE_SOCKET_NAME_ARG flag to indicate the abstract socket name that + * should be used for communication. + */ + boolean startChildZygote; + + /** * Constructs instance and parses args * @param args zygote command-line args * @throws IllegalArgumentException @@ -565,6 +574,8 @@ class ZygoteConnection { preloadPackageCacheKey = args[++curArg]; } else if (arg.equals("--preload-default")) { preloadDefault = true; + } else if (arg.equals("--start-child-zygote")) { + startChildZygote = true; } else { break; } @@ -587,6 +598,20 @@ class ZygoteConnection { remainingArgs = new String[args.length - curArg]; System.arraycopy(args, curArg, remainingArgs, 0, remainingArgs.length); } + + if (startChildZygote) { + boolean seenChildSocketArg = false; + for (String arg : remainingArgs) { + if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) { + seenChildSocketArg = true; + break; + } + } + if (!seenChildSocketArg) { + throw new IllegalArgumentException("--start-child-zygote specified " + + "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG); + } + } } } @@ -739,9 +764,10 @@ class ZygoteConnection { * @param parsedArgs non-null; zygote args * @param descriptors null-ok; new file descriptors for stdio if available. * @param pipeFd null-ok; pipe for communication back to Zygote. + * @param isZygote whether this new child process is itself a new Zygote. */ private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors, - FileDescriptor pipeFd) { + FileDescriptor pipeFd, boolean isZygote) { /** * By the time we get here, the native code has closed the two actual Zygote * socket connections, and substituted /dev/null in their place. The LocalSocket @@ -778,8 +804,13 @@ class ZygoteConnection { // Should not get here. throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned"); } else { - return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, - null /* classLoader */); + if (!isZygote) { + return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, + null /* classLoader */); + } else { + return ZygoteInit.childZygoteInit(parsedArgs.targetSdkVersion, + parsedArgs.remainingArgs, null /* classLoader */); + } } } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 30d81a7216a3..c5d0a04b5deb 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -756,7 +756,7 @@ public class ZygoteInit { throw new RuntimeException("No ABI list supplied."); } - zygoteServer.registerServerSocket(socketName); + zygoteServer.registerServerSocketFromEnv(socketName); // In some configurations, we avoid preloading resources and classes eagerly. // In such cases, we will preload things prior to our first fork. if (!enableLazyPreload) { @@ -871,5 +871,16 @@ public class ZygoteInit { return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader); } + /** + * The main function called when starting a child zygote process. This is used as an + * alternative to zygoteInit(), which skips calling into initialization routines that + * start the Binder threadpool. + */ + static final Runnable childZygoteInit( + int targetSdkVersion, String[] argv, ClassLoader classLoader) { + RuntimeInit.Arguments args = new RuntimeInit.Arguments(argv); + return RuntimeInit.findStaticMain(args.startClass, args.startArgs, classLoader); + } + private static final native void nativeZygoteInit(); } diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java index 8baa15a058de..fecf9b9da5dd 100644 --- a/core/java/com/android/internal/os/ZygoteServer.java +++ b/core/java/com/android/internal/os/ZygoteServer.java @@ -44,9 +44,21 @@ class ZygoteServer { private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_"; + /** + * Listening socket that accepts new server connections. + */ private LocalServerSocket mServerSocket; /** + * Whether or not mServerSocket's underlying FD should be closed directly. + * If mServerSocket is created with an existing FD, closing the socket does + * not close the FD and it must be closed explicitly. If the socket is created + * with a name instead, then closing the socket will close the underlying FD + * and it should not be double-closed. + */ + private boolean mCloseSocketFd; + + /** * Set by the child process, immediately after a call to {@code Zygote.forkAndSpecialize}. */ private boolean mIsForkChild; @@ -59,11 +71,12 @@ class ZygoteServer { } /** - * Registers a server socket for zygote command connections + * Registers a server socket for zygote command connections. This locates the server socket + * file descriptor through an ANDROID_SOCKET_ environment variable. * * @throws RuntimeException when open fails */ - void registerServerSocket(String socketName) { + void registerServerSocketFromEnv(String socketName) { if (mServerSocket == null) { int fileDesc; final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName; @@ -78,6 +91,7 @@ class ZygoteServer { FileDescriptor fd = new FileDescriptor(); fd.setInt$(fileDesc); mServerSocket = new LocalServerSocket(fd); + mCloseSocketFd = true; } catch (IOException ex) { throw new RuntimeException( "Error binding to local socket '" + fileDesc + "'", ex); @@ -86,6 +100,22 @@ class ZygoteServer { } /** + * Registers a server socket for zygote command connections. This opens the server socket + * at the specified name in the abstract socket namespace. + */ + void registerServerSocketAtAbstractName(String socketName) { + if (mServerSocket == null) { + try { + mServerSocket = new LocalServerSocket(socketName); + mCloseSocketFd = false; + } catch (IOException ex) { + throw new RuntimeException( + "Error binding to abstract socket '" + socketName + "'", ex); + } + } + } + + /** * Waits for and accepts a single command connection. Throws * RuntimeException on failure. */ @@ -112,7 +142,7 @@ class ZygoteServer { if (mServerSocket != null) { FileDescriptor fd = mServerSocket.getFileDescriptor(); mServerSocket.close(); - if (fd != null) { + if (fd != null && mCloseSocketFd) { Os.close(fd); } } @@ -219,6 +249,11 @@ class ZygoteServer { Log.e(TAG, "Caught post-fork exception in child process.", e); throw e; } + } finally { + // Reset the child flag, in the event that the child process is a child- + // zygote. The flag will not be consulted this loop pass after the Runnable + // is returned. + mIsForkChild = false; } } } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 32945bfbceb1..d6fe5687edb9 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -516,7 +516,7 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra jint mount_external, jstring java_se_info, jstring java_se_name, bool is_system_server, jintArray fdsToClose, - jintArray fdsToIgnore, + jintArray fdsToIgnore, bool is_child_zygote, jstring instructionSet, jstring dataDir) { SetSignalHandlers(); @@ -699,7 +699,7 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra UnsetChldSignalHandler(); env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags, - is_system_server, instructionSet); + is_system_server, is_child_zygote, instructionSet); if (env->ExceptionCheck()) { RuntimeAbort(env, __LINE__, "Error calling post fork hooks."); } @@ -748,8 +748,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring se_name, - jintArray fdsToClose, - jintArray fdsToIgnore, + jintArray fdsToClose, jintArray fdsToIgnore, jboolean is_child_zygote, jstring instructionSet, jstring appDataDir) { jlong capabilities = 0; @@ -786,13 +785,22 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( capabilities |= (1LL << CAP_BLOCK_SUSPEND); } + // If forking a child zygote process, that zygote will need to be able to change + // the UID and GID of processes it forks, as well as drop those capabilities. + if (is_child_zygote) { + capabilities |= (1LL << CAP_SETUID); + capabilities |= (1LL << CAP_SETGID); + capabilities |= (1LL << CAP_SETPCAP); + } + // Containers run without some capabilities, so drop any caps that are not // available. capabilities &= GetEffectiveCapabilityMask(env); return ForkAndSpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities, mount_external, se_info, - se_name, false, fdsToClose, fdsToIgnore, instructionSet, appDataDir); + se_name, false, fdsToClose, fdsToIgnore, is_child_zygote == JNI_TRUE, + instructionSet, appDataDir); } static jint com_android_internal_os_Zygote_nativeForkSystemServer( @@ -803,7 +811,7 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( runtime_flags, rlimits, permittedCapabilities, effectiveCapabilities, MOUNT_EXTERNAL_DEFAULT, NULL, NULL, true, NULL, - NULL, NULL, NULL); + NULL, false, NULL, NULL); if (pid > 0) { // The zygote process checks whether the child process has died or not. ALOGI("System server process %d has been created", pid); @@ -880,7 +888,7 @@ static const JNINativeMethod gMethods[] = { { "nativeSecurityInit", "()V", (void *) com_android_internal_os_Zygote_nativeSecurityInit }, { "nativeForkAndSpecialize", - "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I", + "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I", (void *) com_android_internal_os_Zygote_nativeForkAndSpecialize }, { "nativeForkSystemServer", "(II[II[[IJJ)I", (void *) com_android_internal_os_Zygote_nativeForkSystemServer }, @@ -895,7 +903,7 @@ static const JNINativeMethod gMethods[] = { int register_com_android_internal_os_Zygote(JNIEnv* env) { gZygoteClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, kZygoteClassName)); gCallPostForkChildHooks = GetStaticMethodIDOrDie(env, gZygoteClass, "callPostForkChildHooks", - "(IZLjava/lang/String;)V"); + "(IZZLjava/lang/String;)V"); return RegisterMethodsOrDie(env, "com/android/internal/os/Zygote", gMethods, NELEM(gMethods)); } diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 3b7b14c58d0a..2e6058268115 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -317,10 +317,12 @@ bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) { return false; } - // This is a local socket with an abstract address, we do not accept it. + // This is a local socket with an abstract address. Remove the leading NUL byte and + // add a human-readable "ABSTRACT/" prefix. if (unix_addr->sun_path[0] == '\0') { - LOG(ERROR) << "Unsupported AF_UNIX socket (fd=" << fd << ") with abstract address."; - return false; + *result = "ABSTRACT/"; + result->append(&unix_addr->sun_path[1], path_len - 1); + return true; } // If we're here, sun_path must refer to a null terminated filesystem |