ZygoteConnectionpublic class ZygoteConnection extends Object A connection that can make spawn requests. |
Fields Summary |
---|
private static final String | TAG | private static final int[] | intArray2da prototype instance for a future List.toArray() | private static final int | CONNECTION_TIMEOUT_MILLIS{@link android.net.LocalSocket#setSoTimeout} value for connections.
Effectively, the amount of time a requestor has between the start of
the request and the completed request. The select-loop mode Zygote
doesn't have the logic to return to the select loop in the middle of
a request, so we need to time out here to avoid being denial-of-serviced. | private static final int | MAX_ZYGOTE_ARGCmax number of arguments that a connection can specify | private final android.net.LocalSocket | mSocketThe 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 DataOutputStream | mSocketOutStream | private final BufferedReader | mSocketReader | private final android.net.Credentials | peer | private final String | peerSecurityContext | private final String | abiList |
Constructors Summary |
---|
ZygoteConnection(android.net.LocalSocket socket, String abiList)Constructs instance from connected socket.
mSocket = socket;
this.abiList = abiList;
mSocketOutStream
= new DataOutputStream(socket.getOutputStream());
mSocketReader = new BufferedReader(
new InputStreamReader(socket.getInputStream()), 256);
mSocket.setSoTimeout(CONNECTION_TIMEOUT_MILLIS);
try {
peer = mSocket.getPeerCredentials();
} catch (IOException ex) {
Log.e(TAG, "Cannot read peer credentials", ex);
throw ex;
}
peerSecurityContext = SELinux.getPeerContext(mSocket.getFileDescriptor());
|
Methods Summary |
---|
public static void | applyDebuggerSystemProperty(com.android.internal.os.ZygoteConnection$Arguments args)Applies debugger system properties to the zygote arguments.
If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
the debugger state is specified via the "--enable-debugger" flag
in the spawn request.
if ("1".equals(SystemProperties.get("ro.debuggable"))) {
args.debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
}
| private static void | applyInvokeWithSecurityPolicy(com.android.internal.os.ZygoteConnection$Arguments args, android.net.Credentials peer, java.lang.String peerSecurityContext)Applies zygote security policy.
Based on the credentials of the process issuing a zygote command:
- uid 0 (root) may specify --invoke-with to launch Zygote with a
wrapper command.
- Any other uid may not specify any invoke-with argument.
int peerUid = peer.getUid();
if (args.invokeWith != null && peerUid != 0) {
throw new ZygoteSecurityException("Peer is not permitted to specify "
+ "an explicit invoke-with wrapper command");
}
if (args.invokeWith != null) {
boolean allowed = SELinux.checkSELinuxAccess(peerSecurityContext,
peerSecurityContext,
"zygote",
"specifyinvokewith");
if (!allowed) {
throw new ZygoteSecurityException("Peer is not permitted to specify "
+ "an explicit invoke-with wrapper command");
}
}
| public static void | applyInvokeWithSystemProperty(com.android.internal.os.ZygoteConnection$Arguments args)Applies invoke-with system properties to the zygote arguments.
if (args.invokeWith == null && args.niceName != null) {
if (args.niceName != null) {
String property = "wrap." + args.niceName;
if (property.length() > 31) {
// Avoid creating an illegal property name when truncating.
if (property.charAt(30) != '.") {
property = property.substring(0, 31);
} else {
property = property.substring(0, 30);
}
}
args.invokeWith = SystemProperties.get(property);
if (args.invokeWith != null && args.invokeWith.length() == 0) {
args.invokeWith = null;
}
}
}
| private static void | applyRlimitSecurityPolicy(com.android.internal.os.ZygoteConnection$Arguments args, android.net.Credentials peer, java.lang.String peerSecurityContext)Applies zygote security policy per bug #1042973. Based on the credentials
of the process issuing a zygote command:
- peers of uid 0 (root) and uid 1000 (Process.SYSTEM_UID)
may specify any rlimits.
- All other uids may not specify rlimits.
int peerUid = peer.getUid();
if (!(peerUid == 0 || peerUid == Process.SYSTEM_UID)) {
// All peers with UID other than root or SYSTEM_UID
if (args.rlimits != null) {
throw new ZygoteSecurityException(
"This UID may not specify rlimits.");
}
}
if (args.rlimits != null) {
boolean allowed = SELinux.checkSELinuxAccess(peerSecurityContext,
peerSecurityContext,
"zygote",
"specifyrlimits");
if (!allowed) {
throw new ZygoteSecurityException(
"Peer may not specify rlimits");
}
}
| private static void | applyUidSecurityPolicy(com.android.internal.os.ZygoteConnection$Arguments args, android.net.Credentials peer, java.lang.String peerSecurityContext)Applies zygote security policy per bugs #875058 and #1082165.
Based on the credentials of the process issuing a zygote command:
- uid 0 (root) may specify any uid, gid, and setgroups() list
- 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.
- Any other uid may not specify any uid, gid, or setgroups list. The
uid and gid will be inherited from the requesting process.
int peerUid = peer.getUid();
if (peerUid == 0) {
// Root can do what it wants
} else if (peerUid == Process.SYSTEM_UID ) {
// System UID is restricted, except in factory test mode
String factoryTest = SystemProperties.get("ro.factorytest");
boolean uidRestricted;
/* In normal operation, SYSTEM_UID can only specify a restricted
* set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
*/
uidRestricted
= !(factoryTest.equals("1") || factoryTest.equals("2"));
if (uidRestricted
&& args.uidSpecified && (args.uid < Process.SYSTEM_UID)) {
throw new ZygoteSecurityException(
"System UID may not launch process with UID < "
+ Process.SYSTEM_UID);
}
} else {
// Everything else
if (args.uidSpecified || args.gidSpecified
|| args.gids != null) {
throw new ZygoteSecurityException(
"App UIDs may not specify uid's or gid's");
}
}
if (args.uidSpecified || args.gidSpecified || args.gids != null) {
boolean allowed = SELinux.checkSELinuxAccess(peerSecurityContext,
peerSecurityContext,
"zygote",
"specifyids");
if (!allowed) {
throw new ZygoteSecurityException(
"Peer may not specify uid's or gid's");
}
}
// If not otherwise specified, uid and gid are inherited from peer
if (!args.uidSpecified) {
args.uid = peer.getUid();
args.uidSpecified = true;
}
if (!args.gidSpecified) {
args.gid = peer.getGid();
args.gidSpecified = true;
}
| private static void | applyseInfoSecurityPolicy(com.android.internal.os.ZygoteConnection$Arguments args, android.net.Credentials peer, java.lang.String peerSecurityContext)Applies zygote security policy for SELinux information.
int peerUid = peer.getUid();
if (args.seInfo == null) {
// nothing to check
return;
}
if (!(peerUid == 0 || peerUid == Process.SYSTEM_UID)) {
// All peers with UID other than root or SYSTEM_UID
throw new ZygoteSecurityException(
"This UID may not specify SELinux info.");
}
boolean allowed = SELinux.checkSELinuxAccess(peerSecurityContext,
peerSecurityContext,
"zygote",
"specifyseinfo");
if (!allowed) {
throw new ZygoteSecurityException(
"Peer may not specify SELinux info");
}
return;
| private void | checkTime(long startTime, java.lang.String where)Temporary hack: check time since start time and log if over a fixed threshold.
long now = SystemClock.elapsedRealtime();
if ((now-startTime) > 1000) {
// If we are taking more than a second, log about it.
Slog.w(TAG, "Slow operation: " + (now-startTime) + "ms so far, now at " + where);
}
| void | closeSocket()Closes socket associated with this connection.
try {
mSocket.close();
} catch (IOException ex) {
Log.e(TAG, "Exception while closing command "
+ "socket in parent", ex);
}
| java.io.FileDescriptor | getFileDescriptor()Returns the file descriptor of the associated socket.
return mSocket.getFileDescriptor();
| private boolean | handleAbiListQuery()
try {
final byte[] abiListBytes = abiList.getBytes(StandardCharsets.US_ASCII);
mSocketOutStream.writeInt(abiListBytes.length);
mSocketOutStream.write(abiListBytes);
return false;
} catch (IOException ioe) {
Log.e(TAG, "Error writing to command socket", ioe);
return true;
}
| private void | handleChildProc(com.android.internal.os.ZygoteConnection$Arguments parsedArgs, java.io.FileDescriptor[] descriptors, java.io.FileDescriptor pipeFd, java.io.PrintStream newStderr)Handles post-fork setup of child proc, closing sockets as appropriate,
reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller
if successful or returning if failed.
/**
* 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
* objects still need to be closed properly.
*/
closeSocket();
ZygoteInit.closeServerSocket();
if (descriptors != null) {
try {
ZygoteInit.reopenStdio(descriptors[0],
descriptors[1], descriptors[2]);
for (FileDescriptor fd: descriptors) {
IoUtils.closeQuietly(fd);
}
newStderr = System.err;
} catch (IOException ex) {
Log.e(TAG, "Error reopening stdio", ex);
}
}
if (parsedArgs.niceName != null) {
Process.setArgV0(parsedArgs.niceName);
}
if (parsedArgs.runtimeInit) {
if (parsedArgs.invokeWith != null) {
WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, parsedArgs.targetSdkVersion,
pipeFd, parsedArgs.remainingArgs);
} else {
RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
parsedArgs.remainingArgs, null /* classLoader */);
}
} else {
String className;
try {
className = parsedArgs.remainingArgs[0];
} catch (ArrayIndexOutOfBoundsException ex) {
logAndPrintError(newStderr,
"Missing required class name argument", null);
return;
}
String[] mainArgs = new String[parsedArgs.remainingArgs.length - 1];
System.arraycopy(parsedArgs.remainingArgs, 1,
mainArgs, 0, mainArgs.length);
if (parsedArgs.invokeWith != null) {
WrapperInit.execStandalone(parsedArgs.invokeWith,
parsedArgs.classpath, className, mainArgs);
} else {
ClassLoader cloader;
if (parsedArgs.classpath != null) {
cloader = new PathClassLoader(parsedArgs.classpath,
ClassLoader.getSystemClassLoader());
} else {
cloader = ClassLoader.getSystemClassLoader();
}
try {
ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
} catch (RuntimeException ex) {
logAndPrintError(newStderr, "Error starting.", ex);
}
}
}
| private boolean | handleParentProc(int pid, java.io.FileDescriptor[] descriptors, java.io.FileDescriptor pipeFd, com.android.internal.os.ZygoteConnection$Arguments parsedArgs)Handles post-fork cleanup of parent proc
if (pid > 0) {
setChildPgid(pid);
}
if (descriptors != null) {
for (FileDescriptor fd: descriptors) {
IoUtils.closeQuietly(fd);
}
}
boolean usingWrapper = false;
if (pipeFd != null && pid > 0) {
DataInputStream is = new DataInputStream(new FileInputStream(pipeFd));
int innerPid = -1;
try {
innerPid = is.readInt();
} catch (IOException ex) {
Log.w(TAG, "Error reading pid from wrapped process, child may have died", ex);
} finally {
try {
is.close();
} catch (IOException ex) {
}
}
// Ensure that the pid reported by the wrapped process is either the
// child process that we forked, or a descendant of it.
if (innerPid > 0) {
int parentPid = innerPid;
while (parentPid > 0 && parentPid != pid) {
parentPid = Process.getParentPid(parentPid);
}
if (parentPid > 0) {
Log.i(TAG, "Wrapped process has pid " + innerPid);
pid = innerPid;
usingWrapper = true;
} else {
Log.w(TAG, "Wrapped process reported a pid that is not a child of "
+ "the process that we forked: childPid=" + pid
+ " innerPid=" + innerPid);
}
}
}
try {
mSocketOutStream.writeInt(pid);
mSocketOutStream.writeBoolean(usingWrapper);
} catch (IOException ex) {
Log.e(TAG, "Error writing to command socket", ex);
return true;
}
return false;
| private static void | logAndPrintError(java.io.PrintStream newStderr, java.lang.String message, java.lang.Throwable ex)Logs an error message and prints it to the specified stream, if
provided
Log.e(TAG, message, ex);
if (newStderr != null) {
newStderr.println(message + (ex == null ? "" : ex));
}
| private java.lang.String[] | readArgumentList()Reads an argument list from the command socket/
/**
* See android.os.Process.zygoteSendArgsAndGetPid()
* Presently the wire format to the zygote process is:
* a) a count of arguments (argc, in essence)
* b) a number of newline-separated argument strings equal to count
*
* After the zygote process reads these it will write the pid of
* the child or -1 on failure.
*/
int argc;
try {
String s = mSocketReader.readLine();
if (s == null) {
// EOF reached.
return null;
}
argc = Integer.parseInt(s);
} catch (NumberFormatException ex) {
Log.e(TAG, "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[] result = new String[argc];
for (int i = 0; i < argc; i++) {
result[i] = mSocketReader.readLine();
if (result[i] == null) {
// We got an unexpected EOF.
throw new IOException("truncated request");
}
}
return result;
| boolean | runOnce()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.
String args[];
Arguments parsedArgs = null;
FileDescriptor[] descriptors;
long startTime = SystemClock.elapsedRealtime();
try {
args = readArgumentList();
descriptors = mSocket.getAncillaryFileDescriptors();
} catch (IOException ex) {
Log.w(TAG, "IOException on command socket " + ex.getMessage());
closeSocket();
return true;
}
checkTime(startTime, "zygoteConnection.runOnce: readArgumentList");
if (args == null) {
// EOF reached.
closeSocket();
return true;
}
/** the stderr of the most recent request, if avail */
PrintStream newStderr = null;
if (descriptors != null && descriptors.length >= 3) {
newStderr = new PrintStream(
new FileOutputStream(descriptors[2]));
}
int pid = -1;
FileDescriptor childPipeFd = null;
FileDescriptor serverPipeFd = null;
try {
parsedArgs = new Arguments(args);
if (parsedArgs.abiListQuery) {
return handleAbiListQuery();
}
if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
throw new ZygoteSecurityException("Client may not specify capabilities: " +
"permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
}
applyUidSecurityPolicy(parsedArgs, peer, peerSecurityContext);
applyRlimitSecurityPolicy(parsedArgs, peer, peerSecurityContext);
applyInvokeWithSecurityPolicy(parsedArgs, peer, peerSecurityContext);
applyseInfoSecurityPolicy(parsedArgs, peer, peerSecurityContext);
checkTime(startTime, "zygoteConnection.runOnce: apply security policies");
applyDebuggerSystemProperty(parsedArgs);
applyInvokeWithSystemProperty(parsedArgs);
checkTime(startTime, "zygoteConnection.runOnce: apply security policies");
int[][] rlimits = null;
if (parsedArgs.rlimits != null) {
rlimits = parsedArgs.rlimits.toArray(intArray2d);
}
if (parsedArgs.runtimeInit && parsedArgs.invokeWith != null) {
FileDescriptor[] pipeFds = Os.pipe();
childPipeFd = pipeFds[1];
serverPipeFd = pipeFds[0];
ZygoteInit.setCloseOnExec(serverPipeFd, true);
}
/**
* 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 = ZygoteInit.getServerSocketFileDescriptor();
if (fd != null) {
fdsToClose[1] = fd.getInt$();
}
fd = null;
checkTime(startTime, "zygoteConnection.runOnce: preForkAndSpecialize");
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);
}
try {
if (pid == 0) {
// in child
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
// should never get here, the child is expected to either
// throw ZygoteInit.MethodAndArgsCaller or exec().
return true;
} else {
// in parent...pid of < 0 means failure
IoUtils.closeQuietly(childPipeFd);
childPipeFd = null;
return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
}
} finally {
IoUtils.closeQuietly(childPipeFd);
IoUtils.closeQuietly(serverPipeFd);
}
| private void | setChildPgid(int pid)
// Try to move the new child into the peer's process group.
try {
ZygoteInit.setpgid(pid, ZygoteInit.getpgid(peer.getPid()));
} catch (IOException ex) {
// This exception is expected in the case where
// the peer is not in our session
// TODO get rid of this log message in the case where
// getsid(0) != getsid(peer.getPid())
Log.i(TAG, "Zygote: setpgid failed. This is "
+ "normal if peer is not in our session");
}
|
|