Merge "Add zygote native fork loop" am: 43a9a8089f am: dd81ed70c7

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1540230

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I11e1abaa50306262ec5b2a19d95b481ef64987cc
This commit is contained in:
Hans Boehm
2021-02-25 01:14:50 +00:00
committed by Automerger Merge Worker
13 changed files with 1446 additions and 543 deletions

View File

@@ -18,8 +18,8 @@ package com.android.internal.os;
import static android.system.OsConstants.O_CLOEXEC;
import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ApplicationInfo;
import android.net.Credentials;
import android.net.LocalServerSocket;
@@ -36,17 +36,16 @@ import android.util.Log;
import com.android.internal.net.NetworkUtilsInternal;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
import dalvik.system.ZygoteHooks;
import libcore.io.IoUtils;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStreamReader;
/** @hide */
public final class Zygote {
@@ -243,6 +242,8 @@ public final class Zygote {
*/
public static final String CHILD_ZYGOTE_UID_RANGE_END = "--uid-range-end=";
private static final String TAG = "Zygote";
/** Prefix prepended to socket names created by init */
private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
@@ -413,6 +414,10 @@ public final class Zygote {
// Note that this event ends at the end of handleChildProc.
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
if (gids != null && gids.length > 0) {
NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids));
}
// Set the Java Language thread priority to the default value for new apps.
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
@@ -585,114 +590,163 @@ public final class Zygote {
private static native int nativeGetUsapPoolEventFD();
/**
* Fork a new unspecialized app process from the zygote
* Fork a new unspecialized app process from the zygote. Adds the Usap to the native
* Usap table.
*
* @param usapPoolSocket The server socket the USAP will call accept on
* @param sessionSocketRawFDs Anonymous session sockets that are currently open
* @param isPriorityFork Value controlling the process priority level until accept is called
* @return In the Zygote process this function will always return null; in unspecialized app
* processes this function will return a Runnable object representing the new
* application that is passed up from usapMain.
* @param sessionSocketRawFDs Anonymous session sockets that are currently open.
* These are closed in the child.
* @param isPriorityFork Raise the initial process priority level because this is on the
* critical path for application startup.
* @return In the child process, this returns a Runnable that waits for specialization
* info to start an app process. In the sygote/parent process this returns null.
*/
static Runnable forkUsap(LocalServerSocket usapPoolSocket,
int[] sessionSocketRawFDs,
boolean isPriorityFork) {
FileDescriptor[] pipeFDs;
static @Nullable Runnable forkUsap(LocalServerSocket usapPoolSocket,
int[] sessionSocketRawFDs,
boolean isPriorityFork) {
FileDescriptor readFD;
FileDescriptor writeFD;
try {
pipeFDs = Os.pipe2(O_CLOEXEC);
FileDescriptor[] pipeFDs = Os.pipe2(O_CLOEXEC);
readFD = pipeFDs[0];
writeFD = pipeFDs[1];
} catch (ErrnoException errnoEx) {
throw new IllegalStateException("Unable to create USAP pipe.", errnoEx);
}
int pid =
nativeForkUsap(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(),
sessionSocketRawFDs, isPriorityFork);
int pid = nativeForkApp(readFD.getInt$(), writeFD.getInt$(),
sessionSocketRawFDs, /*argsKnown=*/ false, isPriorityFork);
if (pid == 0) {
IoUtils.closeQuietly(pipeFDs[0]);
return usapMain(usapPoolSocket, pipeFDs[1]);
IoUtils.closeQuietly(readFD);
return childMain(null, usapPoolSocket, writeFD);
} else if (pid == -1) {
// Fork failed.
return null;
} else {
// The read-end of the pipe will be closed by the native code.
// See removeUsapTableEntry();
IoUtils.closeQuietly(pipeFDs[1]);
// readFD will be closed by the native code. See removeUsapTableEntry();
IoUtils.closeQuietly(writeFD);
nativeAddUsapTableEntry(pid, readFD.getInt$());
return null;
}
}
private static native int nativeForkUsap(int readPipeFD,
int writePipeFD,
int[] sessionSocketRawFDs,
boolean isPriorityFork);
private static native int nativeForkApp(int readPipeFD,
int writePipeFD,
int[] sessionSocketRawFDs,
boolean argsKnown,
boolean isPriorityFork);
/**
* This function is used by unspecialized app processes to wait for specialization requests from
* the system server.
* Add an entry for a new Usap to the table maintained in native code.
*/
@CriticalNative
private static native void nativeAddUsapTableEntry(int pid, int readPipeFD);
/**
* Fork a new app process from the zygote. argBuffer contains a fork command that
* request neither a child zygote, nor a wrapped process. Continue to accept connections
* on the specified socket, use those to refill argBuffer, and continue to process
* sufficiently simple fork requests. We presume that the only open file descriptors
* requiring special treatment are the session socket embedded in argBuffer, and
* zygoteSocket.
* @param argBuffer containing initial command and the connected socket from which to
* read more
* @param zygoteSocket socket from which to obtain new connections when current argBuffer
* one is disconnected
* @param expectedUId Uid of peer for initial requests. Subsequent requests from a different
* peer will cause us to return rather than perform the requested fork.
* @param minUid Minimum Uid enforced for all but first fork request. The caller checks
* the Uid policy for the initial request.
* @param firstNiceName name of first created process. Used for error reporting only.
* @return A Runnable in each child process, null in the parent.
* If this returns in then argBuffer still contains a command needing to be executed.
*/
static @Nullable Runnable forkSimpleApps(@NonNull ZygoteCommandBuffer argBuffer,
@NonNull FileDescriptor zygoteSocket,
int expectedUid,
int minUid,
@Nullable String firstNiceName) {
boolean in_child =
argBuffer.forkRepeatedly(zygoteSocket, expectedUid, minUid, firstNiceName);
if (in_child) {
return childMain(argBuffer, /*usapPoolSocket=*/null, /*writePipe=*/null);
} else {
return null;
}
}
/**
* Specialize the current process into one described by argBuffer or the command read from
* usapPoolSocket. Exactly one of those must be null. If we are given an argBuffer, we close
* it. Used both for a specializing a USAP process, and for process creation without USAPs.
* In both cases, we specialize the process after first returning to Java code.
*
* @param writePipe The write end of the reporting pipe used to communicate with the poll loop
* of the ZygoteServer.
* @return A runnable oject representing the new application.
*/
private static Runnable usapMain(LocalServerSocket usapPoolSocket,
FileDescriptor writePipe) {
private static Runnable childMain(@Nullable ZygoteCommandBuffer argBuffer,
@Nullable LocalServerSocket usapPoolSocket,
FileDescriptor writePipe) {
final int pid = Process.myPid();
Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32");
LocalSocket sessionSocket = null;
DataOutputStream usapOutputStream = null;
Credentials peerCredentials = null;
ZygoteArguments args = null;
// Change the priority to max before calling accept so we can respond to new specialization
// requests as quickly as possible. This will be reverted to the default priority in the
// native specialization code.
boostUsapPriority();
// Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool.
blockSigTerm();
while (true) {
try {
sessionSocket = usapPoolSocket.accept();
LocalSocket sessionSocket = null;
if (argBuffer == null) {
// Read arguments from usapPoolSocket instead.
// Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool.
blockSigTerm();
Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32");
BufferedReader usapReader =
new BufferedReader(new InputStreamReader(sessionSocket.getInputStream()));
usapOutputStream =
new DataOutputStream(sessionSocket.getOutputStream());
// Change the priority to max before calling accept so we can respond to new
// specialization requests as quickly as possible. This will be reverted to the
// default priority in the native specialization code.
boostUsapPriority();
peerCredentials = sessionSocket.getPeerCredentials();
String[] argStrings = readArgumentList(usapReader);
if (argStrings != null) {
args = new ZygoteArguments(argStrings);
while (true) {
ZygoteCommandBuffer tmpArgBuffer = null;
try {
sessionSocket = usapPoolSocket.accept();
usapOutputStream =
new DataOutputStream(sessionSocket.getOutputStream());
Credentials peerCredentials = sessionSocket.getPeerCredentials();
tmpArgBuffer = new ZygoteCommandBuffer(sessionSocket);
args = ZygoteArguments.getInstance(argBuffer);
applyUidSecurityPolicy(args, peerCredentials);
// TODO (chriswailes): Should this only be run for debug builds?
validateUsapCommand(args);
break;
} else {
Log.e("USAP", "Truncated command received.");
IoUtils.closeQuietly(sessionSocket);
// Re-enable SIGTERM so the USAP can be flushed from the pool if necessary.
unblockSigTerm();
} catch (Exception ex) {
Log.e("USAP", ex.getMessage());
}
} catch (Exception ex) {
Log.e("USAP", ex.getMessage());
IoUtils.closeQuietly(sessionSocket);
// Re-enable SIGTERM so the USAP can be flushed from the pool if necessary.
unblockSigTerm();
IoUtils.closeQuietly(sessionSocket);
IoUtils.closeQuietly(tmpArgBuffer);
blockSigTerm();
}
} else {
try {
args = ZygoteArguments.getInstance(argBuffer);
} catch (Exception ex) {
Log.e("AppStartup", ex.getMessage());
throw new AssertionError("Failed to parse application start command", ex);
}
// peerCredentials were checked in parent.
}
if (args == null) {
throw new AssertionError("Empty command line");
}
try {
// SIGTERM is blocked on loop exit. This prevents a USAP that is specializing from
// being killed during a pool flush.
// SIGTERM is blocked here. This prevents a USAP that is specializing from being
// killed during a pool flush.
setAppProcessName(args, "USAP");
applyUidSecurityPolicy(args, peerCredentials);
applyDebuggerSystemProperty(args);
int[][] rlimits = null;
@@ -701,53 +755,57 @@ public final class Zygote {
rlimits = args.mRLimits.toArray(INT_ARRAY_2D);
}
// This must happen before the SELinux policy for this process is
// changed when specializing.
try {
// Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a
// Process.ProcessStartResult object.
usapOutputStream.writeInt(pid);
} catch (IOException ioEx) {
Log.e("USAP", "Failed to write response to session socket: "
+ ioEx.getMessage());
throw new RuntimeException(ioEx);
} finally {
IoUtils.closeQuietly(sessionSocket);
if (argBuffer == null) {
// This must happen before the SELinux policy for this process is
// changed when specializing.
try {
// This socket is closed using Os.close due to an issue with the implementation
// of LocalSocketImp.close(). Because the raw FD is created by init and then
// loaded from an environment variable (as opposed to being created by the
// LocalSocketImpl itself) the current implementation will not actually close
// the underlying FD.
//
// See b/130309968 for discussion of this issue.
Os.close(usapPoolSocket.getFileDescriptor());
} catch (ErrnoException ex) {
Log.e("USAP", "Failed to close USAP pool socket");
throw new RuntimeException(ex);
// Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a
// Process.ProcessStartResult object.
usapOutputStream.writeInt(pid);
} catch (IOException ioEx) {
Log.e("USAP", "Failed to write response to session socket: "
+ ioEx.getMessage());
throw new RuntimeException(ioEx);
} finally {
try {
// Since the raw FD is created by init and then loaded from an environment
// variable (as opposed to being created by the LocalSocketImpl itself),
// the LocalSocket/LocalSocketImpl does not own the Os-level socket. See
// the spec for LocalSocket.createConnectedLocalSocket(FileDescriptor fd).
// Thus closing the LocalSocket does not suffice. See b/130309968 for more
// discussion.
FileDescriptor fd = usapPoolSocket.getFileDescriptor();
usapPoolSocket.close();
Os.close(fd);
} catch (ErrnoException | IOException ex) {
Log.e("USAP", "Failed to close USAP pool socket");
throw new RuntimeException(ex);
}
}
}
try {
ByteArrayOutputStream buffer =
new ByteArrayOutputStream(Zygote.USAP_MANAGEMENT_MESSAGE_BYTES);
DataOutputStream outputStream = new DataOutputStream(buffer);
if (writePipe != null) {
try {
ByteArrayOutputStream buffer =
new ByteArrayOutputStream(Zygote.USAP_MANAGEMENT_MESSAGE_BYTES);
DataOutputStream outputStream = new DataOutputStream(buffer);
// This is written as a long so that the USAP reporting pipe and USAP pool event FD
// handlers in ZygoteServer.runSelectLoop can be unified. These two cases should
// both send/receive 8 bytes.
outputStream.writeLong(pid);
outputStream.flush();
Os.write(writePipe, buffer.toByteArray(), 0, buffer.size());
} catch (Exception ex) {
Log.e("USAP",
String.format("Failed to write PID (%d) to pipe (%d): %s",
pid, writePipe.getInt$(), ex.getMessage()));
throw new RuntimeException(ex);
} finally {
IoUtils.closeQuietly(writePipe);
// This is written as a long so that the USAP reporting pipe and USAP pool
// event FD handlers in ZygoteServer.runSelectLoop can be unified. These two
// cases should both send/receive 8 bytes.
// TODO: Needs tweaking to handle the non-Usap invoke-with case, which expects
// a different format.
outputStream.writeLong(pid);
outputStream.flush();
Os.write(writePipe, buffer.toByteArray(), 0, buffer.size());
} catch (Exception ex) {
Log.e("USAP",
String.format("Failed to write PID (%d) to pipe (%d): %s",
pid, writePipe.getInt$(), ex.getMessage()));
throw new RuntimeException(ex);
} finally {
IoUtils.closeQuietly(writePipe);
}
}
specializeAppProcess(args.mUid, args.mGid, args.mGids,
@@ -854,13 +912,29 @@ public final class Zygote {
return nativeRemoveUsapTableEntry(usapPID);
}
@CriticalNative
private static native boolean nativeRemoveUsapTableEntry(int usapPID);
/**
* uid 1000 (Process.SYSTEM_UID) may specify any uid > 1000 in normal
* Return the minimum child uid that the given peer is allowed to create.
* uid 1000 (Process.SYSTEM_UID) may specify any uid ≥ 1000 in normal
* operation. It may also specify any gid and setgroups() list it chooses.
* In factory test mode, it may specify any UID.
*
*/
static int minChildUid(Credentials peer) {
if (peer.getUid() == Process.SYSTEM_UID
&& FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF) {
/* In normal operation, SYSTEM_UID can only specify a restricted
* set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
*/
return Process.SYSTEM_UID;
} else {
return 0;
}
}
/*
* Adjust uid and gid arguments, ensuring that the security policy is satisfied.
* @param args non-null; zygote spawner arguments
* @param peer non-null; peer credentials
* @throws ZygoteSecurityException Indicates a security issue when applying the UID based
@@ -869,17 +943,10 @@ public final class Zygote {
static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer)
throws ZygoteSecurityException {
if (peer.getUid() == Process.SYSTEM_UID) {
/* In normal operation, SYSTEM_UID can only specify a restricted
* set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
*/
boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF;
if (uidRestricted && args.mUidSpecified && (args.mUid < Process.SYSTEM_UID)) {
throw new ZygoteSecurityException(
"System UID may not launch process with UID < "
+ Process.SYSTEM_UID);
}
if (args.mUidSpecified && (args.mUid < minChildUid(peer))) {
throw new ZygoteSecurityException(
"System UID may not launch process with UID < "
+ Process.SYSTEM_UID);
}
// If not otherwise specified, uid and gid are inherited from peer
@@ -964,45 +1031,6 @@ public final class Zygote {
}
}
/**
* Reads an argument list from the provided socket
* @return Argument list or null if EOF is reached
* @throws IOException passed straight through
*/
static String[] readArgumentList(BufferedReader socketReader) throws IOException {
int argc;
try {
String argc_string = socketReader.readLine();
if (argc_string == null) {
// EOF reached.
return null;
}
argc = Integer.parseInt(argc_string);
} catch (NumberFormatException ex) {
Log.e("Zygote", "Invalid Zygote wire format: non-int at argc");
throw new IOException("Invalid wire format");
}
// See bug 1092107: large argc can be used for a DOS attack
if (argc > MAX_ZYGOTE_ARGC) {
throw new IOException("Max arg count exceeded");
}
String[] args = new String[argc];
for (int arg_index = 0; arg_index < argc; arg_index++) {
args[arg_index] = socketReader.readLine();
if (args[arg_index] == null) {
// We got an unexpected EOF.
throw new IOException("Truncated request");
}
}
return args;
}
/**
* Creates a managed LocalServerSocket object using a file descriptor
* created by an init.rc script. The init scripts that specify the

View File

@@ -16,8 +16,8 @@
package com.android.internal.os;
import java.io.EOFException;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Handles argument parsing for args related to the zygote spawner.
@@ -245,20 +245,34 @@ class ZygoteArguments {
/**
* Constructs instance and parses args
*
* @param args zygote command-line args
* @param args zygote command-line args as ZygoteCommandBuffer, positioned after argument count.
*/
ZygoteArguments(String[] args) throws IllegalArgumentException {
parseArgs(args);
private ZygoteArguments(ZygoteCommandBuffer args, int argCount)
throws IllegalArgumentException, EOFException {
parseArgs(args, argCount);
}
/**
* Return a new ZygoteArguments reflecting the contents of the given ZygoteCommandBuffer. Return
* null if the ZygoteCommandBuffer was positioned at EOF. Assumes the buffer is initially
* positioned at the beginning of the command.
*/
public static ZygoteArguments getInstance(ZygoteCommandBuffer args)
throws IllegalArgumentException, EOFException {
int argCount = args.getCount();
return argCount == 0 ? null : new ZygoteArguments(args, argCount);
}
/**
* Parses the commandline arguments intended for the Zygote spawner (such as "--setuid=" and
* "--setgid=") and creates an array containing the remaining args.
* "--setgid=") and creates an array containing the remaining args. Return false if we were
* at EOF.
*
* Per security review bug #1112214, duplicate args are disallowed in critical cases to make
* injection harder.
*/
private void parseArgs(String[] args) throws IllegalArgumentException {
private void parseArgs(ZygoteCommandBuffer args, int argCount)
throws IllegalArgumentException, EOFException {
/*
* See android.os.ZygoteProcess.zygoteSendArgsAndGetResult()
* Presently the wire format to the zygote process is:
@@ -269,13 +283,13 @@ class ZygoteArguments {
* the child or -1 on failure.
*/
int curArg = 0;
String unprocessedArg = null;
int curArg = 0; // Index of arg
boolean seenRuntimeArgs = false;
boolean expectRuntimeArgs = true;
for ( /* curArg */ ; curArg < args.length; curArg++) {
String arg = args[curArg];
for ( /* curArg */ ; curArg < argCount; ++curArg) {
String arg = args.nextArg();
if (arg.equals("--")) {
curArg++;
@@ -367,7 +381,8 @@ class ZygoteArguments {
"Duplicate arg specified");
}
try {
mInvokeWith = args[++curArg];
++curArg;
mInvokeWith = args.nextArg();
} catch (IndexOutOfBoundsException ex) {
throw new IllegalArgumentException(
"--invoke-with requires argument");
@@ -405,12 +420,14 @@ class ZygoteArguments {
} else if (arg.startsWith("--app-data-dir=")) {
mAppDataDir = getAssignmentValue(arg);
} else if (arg.equals("--preload-app")) {
mPreloadApp = args[++curArg];
++curArg;
mPreloadApp = args.nextArg();
} else if (arg.equals("--preload-package")) {
mPreloadPackage = args[++curArg];
mPreloadPackageLibs = args[++curArg];
mPreloadPackageLibFileName = args[++curArg];
mPreloadPackageCacheKey = args[++curArg];
curArg += 4;
mPreloadPackage = args.nextArg();
mPreloadPackageLibs = args.nextArg();
mPreloadPackageLibFileName = args.nextArg();
mPreloadPackageCacheKey = args.nextArg();
} else if (arg.equals("--preload-default")) {
mPreloadDefault = true;
expectRuntimeArgs = false;
@@ -419,8 +436,11 @@ class ZygoteArguments {
} else if (arg.equals("--set-api-denylist-exemptions")) {
// consume all remaining args; this is a stand-alone command, never included
// with the regular fork command.
mApiDenylistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length);
curArg = args.length;
mApiDenylistExemptions = new String[argCount - curArg - 1];
++curArg;
for (int i = 0; curArg < argCount; ++curArg, ++i) {
mApiDenylistExemptions[i] = args.nextArg();
}
expectRuntimeArgs = false;
} else if (arg.startsWith("--hidden-api-log-sampling-rate=")) {
String rateStr = getAssignmentValue(arg);
@@ -470,35 +490,46 @@ class ZygoteArguments {
} else if (arg.equals(Zygote.BIND_MOUNT_APP_DATA_DIRS)) {
mBindMountAppDataDirs = true;
} else {
unprocessedArg = arg;
break;
}
}
// curArg is the index of the first unprocessed argument. That argument is either referenced
// by unprocessedArg or not read yet.
if (mBootCompleted) {
if (args.length - curArg > 0) {
if (argCount > curArg) {
throw new IllegalArgumentException("Unexpected arguments after --boot-completed");
}
} else if (mAbiListQuery || mPidQuery) {
if (args.length - curArg > 0) {
if (argCount > curArg) {
throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
}
} else if (mPreloadPackage != null) {
if (args.length - curArg > 0) {
if (argCount > curArg) {
throw new IllegalArgumentException(
"Unexpected arguments after --preload-package.");
}
} else if (mPreloadApp != null) {
if (args.length - curArg > 0) {
if (argCount > curArg) {
throw new IllegalArgumentException(
"Unexpected arguments after --preload-app.");
}
} else if (expectRuntimeArgs) {
if (!seenRuntimeArgs) {
throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
throw new IllegalArgumentException("Unexpected argument : "
+ (unprocessedArg == null ? args.nextArg() : unprocessedArg));
}
mRemainingArgs = new String[args.length - curArg];
System.arraycopy(args, curArg, mRemainingArgs, 0, mRemainingArgs.length);
mRemainingArgs = new String[argCount - curArg];
int i = 0;
if (unprocessedArg != null) {
mRemainingArgs[0] = unprocessedArg;
++i;
}
for (; i < argCount - curArg; ++i) {
mRemainingArgs[i] = args.nextArg();
}
}
if (mStartChildZygote) {

View File

@@ -0,0 +1,187 @@
/*
* Copyright (C) 2020 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 com.android.internal.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.LocalSocket;
import java.io.FileDescriptor;
import java.lang.ref.Reference; // For reachabilityFence.
/**
* A native-accessible buffer for Zygote commands. Designed to support repeated forking
* of applications without intervening memory allocation, thus keeping zygote memory
* as stable as possible.
* A ZygoteCommandBuffer may have an associated socket from which it can be refilled.
* Otherwise the contents are explicitly set by getInstance().
*
* NOT THREAD-SAFE. No methods may be called concurrently from multiple threads.
*
* Only one ZygoteCommandBuffer can exist at a time.
* Must be explicitly closed before being dropped.
* @hide
*/
class ZygoteCommandBuffer implements AutoCloseable {
private long mNativeBuffer; // Not final so that we can clear it in close().
/**
* The command socket.
*
* mSocket is retained in the child process in "peer wait" mode, so
* that it closes when the child process terminates. In other cases,
* it is closed in the peer.
*/
private final LocalSocket mSocket;
private final int mNativeSocket;
/**
* Constructs instance from file descriptor from which the command will be read.
* Only a single instance may be live in a given process. The native code checks.
*
* @param fd file descriptor to read from. The setCommand() method may be used if and only if
* fd is null.
*/
ZygoteCommandBuffer(@Nullable LocalSocket socket) {
mSocket = socket;
if (socket == null) {
mNativeSocket = -1;
} else {
mNativeSocket = mSocket.getFileDescriptor().getInt$();
}
mNativeBuffer = getNativeBuffer(mNativeSocket);
}
/**
* Constructs an instance with explicitly supplied arguments and an invalid
* file descriptor. Can only be used for a single command.
*/
ZygoteCommandBuffer(@NonNull String[] args) {
this((LocalSocket) null);
setCommand(args);
}
private static native long getNativeBuffer(int fd);
/**
* Deallocate native resources associated with the one and only command buffer, and prevent
* reuse. Subsequent calls to getInstance() will yield a new buffer.
* We do not close the associated socket, if any.
*/
@Override
public void close() {
freeNativeBuffer(mNativeBuffer);
mNativeBuffer = 0;
}
private static native void freeNativeBuffer(long /* NativeCommandBuffer* */ nbuffer);
/**
* Read at least the first line of the next command into the buffer, return the argument count
* from that line. Assumes we are initially positioned at the beginning of the first line of
* the command. Leave the buffer positioned at the beginning of the second command line, i.e.
* the first argument. If the buffer has no associated file descriptor, we just reposition to
* the beginning of the buffer, and reread existing contents. Returns zero if we started out
* at EOF.
*/
int getCount() {
try {
return nativeGetCount(mNativeBuffer);
} finally {
// Make sure the mNativeSocket doesn't get closed due to early finalization.
Reference.reachabilityFence(mSocket);
}
}
private static native int nativeGetCount(long /* NativeCommandBuffer* */ nbuffer);
/*
* Set the buffer to contain the supplied sequence of arguments.
*/
private void setCommand(String[] command) {
int nArgs = command.length;
insert(mNativeBuffer, Integer.toString(nArgs));
for (String s: command) {
insert(mNativeBuffer, s);
}
// Native code checks there is no socket; hence no reachabilityFence.
}
private static native void insert(long /* NativeCommandBuffer* */ nbuffer, String s);
/**
* Retrieve the next argument/line from the buffer, filling the buffer as necessary.
*/
String nextArg() {
try {
return nativeNextArg(mNativeBuffer);
} finally {
Reference.reachabilityFence(mSocket);
}
}
private static native String nativeNextArg(long /* NativeCommandBuffer* */ nbuffer);
void readFullyAndReset() {
try {
nativeReadFullyAndReset(mNativeBuffer);
} finally {
Reference.reachabilityFence(mSocket);
}
}
private static native void nativeReadFullyAndReset(long /* NativeCommandBuffer* */ nbuffer);
/**
* Fork a child as specified by the current command in the buffer, and repeat this process
* after refilling the buffer, so long as the buffer clearly contains another fork command.
*
* @param zygoteSocket socket from which to obtain new connections when current one is
* disconnected
* @param expectedUid Peer UID for current connection. We refuse to deal with requests from
* a different UID.
* @param minUid the smallest uid that may be request for the child process.
* @param firstNiceName The name for the initial process to be forked. Used only for error
* reporting.
*
* @return true in the child, false in the parent. In the parent case, the buffer is positioned
* at the beginning of a command that still needs to be processed.
*/
boolean forkRepeatedly(FileDescriptor zygoteSocket, int expectedUid, int minUid,
String firstNiceName) {
try {
return nativeForkRepeatedly(mNativeBuffer, zygoteSocket.getInt$(),
expectedUid, minUid, firstNiceName);
} finally {
Reference.reachabilityFence(mSocket);
Reference.reachabilityFence(zygoteSocket);
}
}
/*
* Repeatedly fork children as above. It commonly does not return in the parent, but it may.
* @return true in the chaild, false in the parent if we encounter a command we couldn't handle.
*/
private static native boolean nativeForkRepeatedly(long /* NativeCommandBuffer* */ nbuffer,
int zygoteSocketRawFd,
int expectedUid,
int minUid,
String firstNiceName);
}

View File

@@ -36,16 +36,15 @@ import android.system.StructPollfd;
import android.util.Log;
import dalvik.system.VMRuntime;
import dalvik.system.ZygoteHooks;
import libcore.io.IoUtils;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
@@ -67,7 +66,6 @@ class ZygoteConnection {
private final LocalSocket mSocket;
@UnsupportedAppUsage
private final DataOutputStream mSocketOutStream;
private final BufferedReader mSocketReader;
@UnsupportedAppUsage
private final Credentials peer;
private final String abiList;
@@ -85,9 +83,6 @@ class ZygoteConnection {
this.abiList = abiList;
mSocketOutStream = new DataOutputStream(socket.getOutputStream());
mSocketReader =
new BufferedReader(
new InputStreamReader(socket.getInputStream()), Zygote.SOCKET_BUFFER_SIZE);
mSocket.setSoTimeout(CONNECTION_TIMEOUT_MILLIS);
@@ -111,178 +106,216 @@ class ZygoteConnection {
}
/**
* Reads one start command from the command socket. If successful, a child is forked and a
* Reads a command from the command socket. If a child is successfully forked, a
* {@code Runnable} that calls the childs main method (or equivalent) is returned in the child
* process. {@code null} is always returned in the parent process (the zygote).
* If multipleOK is set, we may keep processing additional fork commands before returning.
*
* If the client closes the socket, an {@code EOF} condition is set, which callers can test
* for by calling {@code ZygoteConnection.isClosedByPeer}.
*/
Runnable processOneCommand(ZygoteServer zygoteServer) {
String[] args;
Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) {
ZygoteArguments parsedArgs;
try {
args = Zygote.readArgumentList(mSocketReader);
} catch (IOException ex) {
throw new IllegalStateException("IOException on command socket", ex);
try (ZygoteCommandBuffer argBuffer = new ZygoteCommandBuffer(mSocket)) {
while (true) {
try {
parsedArgs = ZygoteArguments.getInstance(argBuffer);
// Keep argBuffer around, since we need it to fork.
} catch (IOException ex) {
throw new IllegalStateException("IOException on command socket", ex);
}
if (parsedArgs == null) {
isEof = true;
return null;
}
int pid;
FileDescriptor childPipeFd = null;
FileDescriptor serverPipeFd = null;
if (parsedArgs.mBootCompleted) {
handleBootCompleted();
return null;
}
if (parsedArgs.mAbiListQuery) {
handleAbiListQuery();
return null;
}
if (parsedArgs.mPidQuery) {
handlePidQuery();
return null;
}
if (parsedArgs.mUsapPoolStatusSpecified) {
// Handle this once we've released the argBuffer, to avoid opening a second one.
break;
}
if (parsedArgs.mPreloadDefault) {
handlePreload();
return null;
}
if (parsedArgs.mPreloadPackage != null) {
handlePreloadPackage(parsedArgs.mPreloadPackage,
parsedArgs.mPreloadPackageLibs,
parsedArgs.mPreloadPackageLibFileName,
parsedArgs.mPreloadPackageCacheKey);
return null;
}
if (canPreloadApp() && parsedArgs.mPreloadApp != null) {
byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp);
Parcel appInfoParcel = Parcel.obtain();
appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length);
appInfoParcel.setDataPosition(0);
ApplicationInfo appInfo =
ApplicationInfo.CREATOR.createFromParcel(appInfoParcel);
appInfoParcel.recycle();
if (appInfo != null) {
handlePreloadApp(appInfo);
} else {
throw new IllegalArgumentException("Failed to deserialize --preload-app");
}
return null;
}
if (parsedArgs.mApiDenylistExemptions != null) {
return handleApiDenylistExemptions(zygoteServer,
parsedArgs.mApiDenylistExemptions);
}
if (parsedArgs.mHiddenApiAccessLogSampleRate != -1
|| parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) {
return handleHiddenApiAccessLogSampleRate(zygoteServer,
parsedArgs.mHiddenApiAccessLogSampleRate,
parsedArgs.mHiddenApiAccessStatslogSampleRate);
}
if (parsedArgs.mPermittedCapabilities != 0
|| parsedArgs.mEffectiveCapabilities != 0) {
throw new ZygoteSecurityException("Client may not specify capabilities: "
+ "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities)
+ ", effective=0x"
+ Long.toHexString(parsedArgs.mEffectiveCapabilities));
}
Zygote.applyUidSecurityPolicy(parsedArgs, peer);
Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer);
Zygote.applyDebuggerSystemProperty(parsedArgs);
Zygote.applyInvokeWithSystemProperty(parsedArgs);
int[][] rlimits = null;
if (parsedArgs.mRLimits != null) {
rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D);
}
int[] fdsToIgnore = null;
if (parsedArgs.mInvokeWith != null) {
try {
FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
childPipeFd = pipeFds[1];
serverPipeFd = pipeFds[0];
Os.fcntlInt(childPipeFd, F_SETFD, 0);
fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()};
} catch (ErrnoException errnoEx) {
throw new IllegalStateException("Unable to set up pipe for invoke-with",
errnoEx);
}
}
/*
* In order to avoid leaking descriptors to the Zygote child,
* the native code must close the two Zygote socket descriptors
* in the child process before it switches from Zygote-root to
* the UID and privileges of the application being launched.
*
* In order to avoid "bad file descriptor" errors when the
* two LocalSocket objects are closed, the Posix file
* descriptors are released via a dup2() call which closes
* the socket and substitutes an open descriptor to /dev/null.
*/
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$();
}
if (parsedArgs.mInvokeWith != null || parsedArgs.mStartChildZygote
|| !multipleOK || peer.getUid() != Process.SYSTEM_UID) {
// Continue using old code for now. TODO: Handle these cases in the other path.
pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid,
parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits,
parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName,
fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
parsedArgs.mInstructionSet, parsedArgs.mAppDataDir,
parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList,
parsedArgs.mWhitelistedDataInfoList, parsedArgs.mBindMountAppDataDirs,
parsedArgs.mBindMountAppStorageDirs);
try {
if (pid == 0) {
// in child
zygoteServer.setForkChild();
zygoteServer.closeServerSocket();
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
return handleChildProc(parsedArgs, childPipeFd,
parsedArgs.mStartChildZygote);
} else {
// In the parent. A pid < 0 indicates a failure and will be handled in
// handleParentProc.
IoUtils.closeQuietly(childPipeFd);
childPipeFd = null;
handleParentProc(pid, serverPipeFd);
return null;
}
} finally {
IoUtils.closeQuietly(childPipeFd);
IoUtils.closeQuietly(serverPipeFd);
}
} else {
ZygoteHooks.preFork();
Runnable result = Zygote.forkSimpleApps(argBuffer,
zygoteServer.getZygoteSocketFileDescriptor(),
peer.getUid(), Zygote.minChildUid(peer), parsedArgs.mNiceName);
if (result == null) {
// parent; we finished some number of forks. Result is Boolean.
// We already did the equivalent of handleParentProc().
ZygoteHooks.postForkCommon();
// argBuffer contains a command not understood by forksimpleApps.
continue;
} else {
// child; result is a Runnable.
zygoteServer.setForkChild();
Zygote.setAppProcessName(parsedArgs, TAG); // ??? Necessary?
return result;
}
}
}
}
// readArgumentList returns null only when it has reached EOF with no available
// data to read. This will only happen when the remote socket has disconnected.
if (args == null) {
isEof = true;
return null;
}
int pid;
FileDescriptor childPipeFd = null;
FileDescriptor serverPipeFd = null;
ZygoteArguments parsedArgs = new ZygoteArguments(args);
if (parsedArgs.mBootCompleted) {
handleBootCompleted();
return null;
}
if (parsedArgs.mAbiListQuery) {
handleAbiListQuery();
return null;
}
if (parsedArgs.mPidQuery) {
handlePidQuery();
return null;
}
if (parsedArgs.mUsapPoolStatusSpecified) {
// Now that we've released argBuffer:
return handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled);
}
if (parsedArgs.mPreloadDefault) {
handlePreload();
return null;
}
if (parsedArgs.mPreloadPackage != null) {
handlePreloadPackage(parsedArgs.mPreloadPackage, parsedArgs.mPreloadPackageLibs,
parsedArgs.mPreloadPackageLibFileName, parsedArgs.mPreloadPackageCacheKey);
return null;
}
if (canPreloadApp() && parsedArgs.mPreloadApp != null) {
byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp);
Parcel appInfoParcel = Parcel.obtain();
appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length);
appInfoParcel.setDataPosition(0);
ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(appInfoParcel);
appInfoParcel.recycle();
if (appInfo != null) {
handlePreloadApp(appInfo);
} else {
throw new IllegalArgumentException("Failed to deserialize --preload-app");
}
return null;
}
if (parsedArgs.mApiDenylistExemptions != null) {
return handleApiDenylistExemptions(zygoteServer, parsedArgs.mApiDenylistExemptions);
}
if (parsedArgs.mHiddenApiAccessLogSampleRate != -1
|| parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) {
return handleHiddenApiAccessLogSampleRate(zygoteServer,
parsedArgs.mHiddenApiAccessLogSampleRate,
parsedArgs.mHiddenApiAccessStatslogSampleRate);
}
if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) {
throw new ZygoteSecurityException("Client may not specify capabilities: "
+ "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities)
+ ", effective=0x" + Long.toHexString(parsedArgs.mEffectiveCapabilities));
}
Zygote.applyUidSecurityPolicy(parsedArgs, peer);
Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer);
Zygote.applyDebuggerSystemProperty(parsedArgs);
Zygote.applyInvokeWithSystemProperty(parsedArgs);
int[][] rlimits = null;
if (parsedArgs.mRLimits != null) {
rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D);
}
int[] fdsToIgnore = null;
if (parsedArgs.mInvokeWith != null) {
try {
FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
childPipeFd = pipeFds[1];
serverPipeFd = pipeFds[0];
Os.fcntlInt(childPipeFd, F_SETFD, 0);
fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()};
} catch (ErrnoException errnoEx) {
throw new IllegalStateException("Unable to set up pipe for invoke-with", errnoEx);
}
}
/*
* In order to avoid leaking descriptors to the Zygote child,
* the native code must close the two Zygote socket descriptors
* in the child process before it switches from Zygote-root to
* the UID and privileges of the application being launched.
*
* In order to avoid "bad file descriptor" errors when the
* two LocalSocket objects are closed, the Posix file
* descriptors are released via a dup2() call which closes
* the socket and substitutes an open descriptor to /dev/null.
*/
int [] fdsToClose = { -1, -1 };
FileDescriptor fd = mSocket.getFileDescriptor();
if (fd != null) {
fdsToClose[0] = fd.getInt$();
}
fd = zygoteServer.getZygoteSocketFileDescriptor();
if (fd != null) {
fdsToClose[1] = fd.getInt$();
}
pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids,
parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mIsTopApp,
parsedArgs.mPkgDataInfoList, parsedArgs.mWhitelistedDataInfoList,
parsedArgs.mBindMountAppDataDirs, parsedArgs.mBindMountAppStorageDirs);
try {
if (pid == 0) {
// in child
zygoteServer.setForkChild();
zygoteServer.closeServerSocket();
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
return handleChildProc(parsedArgs, childPipeFd, parsedArgs.mStartChildZygote);
} else {
// In the parent. A pid < 0 indicates a failure and will be handled in
// handleParentProc.
IoUtils.closeQuietly(childPipeFd);
childPipeFd = null;
handleParentProc(pid, serverPipeFd);
return null;
}
} finally {
IoUtils.closeQuietly(childPipeFd);
IoUtils.closeQuietly(serverPipeFd);
}
throw new AssertionError("Shouldn't get here");
}
private void handleAbiListQuery() {
@@ -557,7 +590,7 @@ class ZygoteConnection {
if (res > 0) {
if ((fds[0].revents & POLLIN) != 0) {
// Only read one byte, so as not to block.
// Only read one byte, so as not to block. Really needed?
int readBytes = android.system.Os.read(pipeFd, data, dataIndex, 1);
if (readBytes < 0) {
throw new RuntimeException("Some error");

View File

@@ -31,9 +31,6 @@ public class ZygoteConnectionConstants {
*/
public static final int CONNECTION_TIMEOUT_MILLIS = 1000;
/** max number of arguments that a connection can specify */
public static final int MAX_ZYGOTE_ARGC = 1024;
/**
* Wait time for a wrapped app to report back its pid.
*

View File

@@ -65,6 +65,7 @@ import dalvik.system.ZygoteHooks;
import libcore.io.IoUtils;
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -779,7 +780,13 @@ public class ZygoteInit {
int pid;
try {
parsedArgs = new ZygoteArguments(args);
ZygoteCommandBuffer commandBuffer = new ZygoteCommandBuffer(args);
try {
parsedArgs = ZygoteArguments.getInstance(commandBuffer);
} catch (EOFException e) {
throw new AssertionError("Unexpected argument error for forking system server", e);
}
commandBuffer.close();
Zygote.applyDebuggerSystemProperty(parsedArgs);
Zygote.applyInvokeWithSystemProperty(parsedArgs);
@@ -858,7 +865,7 @@ public class ZygoteInit {
* into new processes are required to either set the priority to the default value or terminate
* before executing any non-system code. The native side of this occurs in SpecializeCommon,
* while the Java Language priority is changed in ZygoteInit.handleSystemServerProcess,
* ZygoteConnection.handleChildProc, and Zygote.usapMain.
* ZygoteConnection.handleChildProc, and Zygote.childMain.
*
* @param argv Command line arguments used to specify the Zygote's configuration.
*/

View File

@@ -337,7 +337,7 @@ class ZygoteServer {
* @param sessionSocketRawFDs Anonymous session sockets that are currently open
* @return In the Zygote process this function will always return null; in unspecialized app
* processes this function will return a Runnable object representing the new
* application that is passed up from usapMain.
* application that is passed up from childMain (the usap's main wait loop).
*/
Runnable fillUsapPool(int[] sessionSocketRawFDs, boolean isPriorityRefill) {
@@ -420,6 +420,7 @@ class ZygoteServer {
* 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.
* @param abiList list of ABIs supported by this zygote.
*/
Runnable runSelectLoop(String abiList) {
ArrayList<FileDescriptor> socketFDs = new ArrayList<>();
@@ -537,22 +538,23 @@ class ZygoteServer {
if (pollIndex == 0) {
// Zygote server socket
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
socketFDs.add(newPeer.getFileDescriptor());
} else if (pollIndex < usapPoolEventFDIndex) {
// Session socket accepted from the Zygote server socket
try {
ZygoteConnection connection = peers.get(pollIndex);
final Runnable command = connection.processOneCommand(this);
boolean multipleForksOK = !isUsapPoolEnabled()
&& ZygoteHooks.indefiniteThreadSuspensionOK();
final Runnable command =
connection.processCommand(this, multipleForksOK);
// TODO (chriswailes): Is this extra check necessary?
if (mIsForkChild) {
// We're in the child. We should always have a command to run at
// this stage if processOneCommand hasn't called "exec".
// this stage if processCommand hasn't called "exec".
if (command == null) {
throw new IllegalStateException("command == null");
}
@@ -565,7 +567,7 @@ class ZygoteServer {
}
// We don't know whether the remote side of the socket was closed or
// not until we attempt to read from it from processOneCommand. This
// not until we attempt to read from it from processCommand. This
// shows up as a regular POLLIN event in our regular processing
// loop.
if (connection.isClosedByPeer()) {