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 static android.net.LocalSocket | sPeerWaitSocketA long-lived reference to the original command socket used to launch
this peer. If "peer wait" mode is specified, the process that requested
the new VM instance intends to track the lifetime of the spawned instance
via the command socket. In this case, the command socket is closed
in the Zygote and placed here in the spawned instance so that it will
not be collected and finalized. This field remains null at all times
in the original Zygote process, and in all spawned processes where
"peer-wait" mode was not requested. |
Constructors Summary |
---|
ZygoteConnection(android.net.LocalSocket socket)Constructs instance from connected socket.
mSocket = socket;
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;
}
|
Methods Summary |
---|
private static void | applyCapabilitiesSecurityPolicy(com.android.internal.os.ZygoteConnection$Arguments args, android.net.Credentials peer)Applies zygote security policy per bug #1042973. A root peer may
spawn an instance with any capabilities. All other uids may spawn
instances with any of the capabilities in the peer's permitted set
but no more.
if (args.permittedCapabilities == 0
&& args.effectiveCapabilities == 0) {
// nothing to check
return;
}
if (peer.getUid() == 0) {
// root may specify anything
return;
}
long permittedCaps;
try {
permittedCaps = ZygoteInit.capgetPermitted(peer.getPid());
} catch (IOException ex) {
throw new ZygoteSecurityException(
"Error retrieving peer's capabilities.");
}
/*
* Ensure that the client did not specify an effective set larger
* than the permitted set. The kernel will enforce this too, but we
* do it here to make the following check easier.
*/
if (((~args.permittedCapabilities) & args.effectiveCapabilities) != 0) {
throw new ZygoteSecurityException(
"Effective capabilities cannot be superset of "
+ " permitted capabilities" );
}
/*
* Ensure that the new permitted (and thus the new effective) set is
* a subset of the peer process's permitted set
*/
if (((~permittedCaps) & args.permittedCapabilities) != 0) {
throw new ZygoteSecurityException(
"Peer specified unpermitted capabilities" );
}
| private static void | applyDebuggerSecurityPolicy(com.android.internal.os.ZygoteConnection$Arguments args)Applies debugger security policy.
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 | applyRlimitSecurityPolicy(com.android.internal.os.ZygoteConnection$Arguments args, android.net.Credentials peer)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.");
}
}
| private static void | applyUidSecurityPolicy(com.android.internal.os.ZygoteConnection$Arguments args, android.net.Credentials peer)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 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;
}
| 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 | getFileDesciptor()Returns the file descriptor of the associated socket.
return mSocket.getFileDescriptor();
| private void | handleChildProc(com.android.internal.os.ZygoteConnection$Arguments parsedArgs, java.io.FileDescriptor[] descriptors, 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.
/*
* First, set the capabilities if necessary
*/
if (parsedArgs.uid != 0) {
try {
ZygoteInit.setCapabilities(parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IOException ex) {
Log.e(TAG, "Error setting capabilities", ex);
}
}
/*
* Close the socket, unless we're in "peer wait" mode, in which
* case it's used to track the liveness of this process.
*/
if (parsedArgs.peerWait) {
try {
ZygoteInit.setCloseOnExec(mSocket.getFileDescriptor(), true);
sPeerWaitSocket = mSocket;
} catch (IOException ex) {
Log.e(TAG, "Zygote Child: error setting peer wait "
+ "socket to be close-on-exec", ex);
}
} else {
closeSocket();
ZygoteInit.closeServerSocket();
}
if (descriptors != null) {
try {
ZygoteInit.reopenStdio(descriptors[0],
descriptors[1], descriptors[2]);
for (FileDescriptor fd: descriptors) {
ZygoteInit.closeDescriptor(fd);
}
newStderr = System.err;
} catch (IOException ex) {
Log.e(TAG, "Error reopening stdio", ex);
}
}
if (parsedArgs.runtimeInit) {
RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
} else {
ClassLoader cloader;
if (parsedArgs.classpath != null) {
cloader
= new PathClassLoader(parsedArgs.classpath,
ClassLoader.getSystemClassLoader());
} else {
cloader = ClassLoader.getSystemClassLoader();
}
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);
try {
ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
} catch (RuntimeException ex) {
logAndPrintError (newStderr, "Error starting. ", ex);
}
}
| private boolean | handleParentProc(int pid, java.io.FileDescriptor[] descriptors, com.android.internal.os.ZygoteConnection$Arguments parsedArgs)Handles post-fork cleanup of parent proc
if(pid > 0) {
// 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");
}
}
try {
if (descriptors != null) {
for (FileDescriptor fd: descriptors) {
ZygoteInit.closeDescriptor(fd);
}
}
} catch (IOException ex) {
Log.e(TAG, "Error closing passed descriptors in "
+ "parent process", ex);
}
try {
mSocketOutStream.writeInt(pid);
} catch (IOException ex) {
Log.e(TAG, "Error reading from command socket", ex);
return true;
}
/*
* If the peer wants to use the socket to wait on the
* newly spawned process, then we're all done.
*/
if (parsedArgs.peerWait) {
try {
mSocket.close();
} catch (IOException ex) {
Log.e(TAG, "Zygote: error closing sockets", 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;
| void | run()Reads start commands from an open command socket.
Start commands are presently a pair of newline-delimited lines
indicating a) class to invoke main() on b) nice name to set argv[0] to.
Continues to read commands and forkAndSpecialize children until
the socket is closed. This method is used in ZYGOTE_FORK_MODE
int loopCount = ZygoteInit.GC_LOOP_COUNT;
while (true) {
/*
* Call gc() before we block in readArgumentList().
* 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) {
ZygoteInit.gc();
loopCount = ZygoteInit.GC_LOOP_COUNT;
} else {
loopCount--;
}
if (runOnce()) {
break;
}
}
| 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;
try {
args = readArgumentList();
descriptors = mSocket.getAncillaryFileDescriptors();
} catch (IOException ex) {
Log.w(TAG, "IOException on command socket " + ex.getMessage());
closeSocket();
return true;
}
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;
try {
parsedArgs = new Arguments(args);
applyUidSecurityPolicy(parsedArgs, peer);
applyDebuggerSecurityPolicy(parsedArgs);
applyRlimitSecurityPolicy(parsedArgs, peer);
applyCapabilitiesSecurityPolicy(parsedArgs, peer);
int[][] rlimits = null;
if (parsedArgs.rlimits != null) {
rlimits = parsedArgs.rlimits.toArray(intArray2d);
}
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids, parsedArgs.debugFlags, rlimits);
} catch (IllegalArgumentException ex) {
logAndPrintError (newStderr, "Invalid zygote arguments", ex);
pid = -1;
} catch (ZygoteSecurityException ex) {
logAndPrintError(newStderr,
"Zygote security policy prevents request: ", ex);
pid = -1;
}
if (pid == 0) {
// in child
handleChildProc(parsedArgs, descriptors, newStderr);
// should never happen
return true;
} else { /* pid != 0 */
// in parent...pid of < 0 means failure
return handleParentProc(pid, descriptors, parsedArgs);
}
|
|