FileDocCategorySizeDatePackage
ZygoteConnection.javaAPI DocAndroid 5.1 API40190Thu Mar 12 22:22:10 GMT 2015com.android.internal.os

ZygoteConnection

public class ZygoteConnection extends Object
A connection that can make spawn requests.

Fields Summary
private static final String
TAG
private static final int[]
intArray2d
a 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_ARGC
max number of arguments that a connection can specify
private final android.net.LocalSocket
mSocket
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 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.

param
socket non-null; connected socket
param
abiList non-null; a list of ABIs this zygote supports.
throws
IOException


                               
          
        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 voidapplyDebuggerSystemProperty(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.

param
args non-null; zygote spawner args

        if ("1".equals(SystemProperties.get("ro.debuggable"))) {
            args.debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
        }
    
private static voidapplyInvokeWithSecurityPolicy(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:
  1. uid 0 (root) may specify --invoke-with to launch Zygote with a wrapper command.
  2. Any other uid may not specify any invoke-with argument.

    param
    args non-null; zygote spawner arguments
    param
    peer non-null; peer credentials
    throws
    ZygoteSecurityException

            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 voidapplyInvokeWithSystemProperty(com.android.internal.os.ZygoteConnection$Arguments args)
Applies invoke-with system properties to the zygote arguments.

param
args non-null; zygote args

        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 voidapplyRlimitSecurityPolicy(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:
  1. peers of uid 0 (root) and uid 1000 (Process.SYSTEM_UID) may specify any rlimits.
  2. All other uids may not specify rlimits.

    param
    args non-null; zygote spawner arguments
    param
    peer non-null; peer credentials
    throws
    ZygoteSecurityException

    
            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 voidapplyUidSecurityPolicy(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:
  1. uid 0 (root) may specify any uid, gid, and setgroups() list
  2. 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.
  3. Any other uid may not specify any uid, gid, or setgroups list. The uid and gid will be inherited from the requesting process.

    param
    args non-null; zygote spawner arguments
    param
    peer non-null; peer credentials
    throws
    ZygoteSecurityException

    
            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 voidapplyseInfoSecurityPolicy(com.android.internal.os.ZygoteConnection$Arguments args, android.net.Credentials peer, java.lang.String peerSecurityContext)
Applies zygote security policy for SELinux information.

param
args non-null; zygote spawner arguments
param
peer non-null; peer credentials
throws
ZygoteSecurityException

        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 voidcheckTime(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);
        }
    
voidcloseSocket()
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.FileDescriptorgetFileDescriptor()
Returns the file descriptor of the associated socket.

return
null-ok; file descriptor

        return mSocket.getFileDescriptor();
    
private booleanhandleAbiListQuery()

        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 voidhandleChildProc(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.

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
newStderr null-ok; stream to use for stderr until stdio is reopened.
throws
ZygoteInit.MethodAndArgsCaller on success to trampoline to code that invokes static main.


        /**
         * 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 booleanhandleParentProc(int pid, java.io.FileDescriptor[] descriptors, java.io.FileDescriptor pipeFd, com.android.internal.os.ZygoteConnection$Arguments parsedArgs)
Handles post-fork cleanup of parent proc

param
pid != 0; pid of child if > 0 or indication of failed fork if < 0;
param
descriptors null-ok; file descriptors for child's new stdio if specified.
param
pipeFd null-ok; pipe for communication with child.
param
parsedArgs non-null; zygote args
return
true for "exit command loop" and false for "continue command loop"


        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 voidlogAndPrintError(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

param
newStderr null-ok; a standard error stream
param
message non-null; error message
param
ex null-ok an exception

        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/

return
Argument list or null if EOF is reached
throws
IOException passed straight through


        /**
         * 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;
    
booleanrunOnce()
Reads one start command from the command socket. If successful, a child is forked and a {@link ZygoteInit.MethodAndArgsCaller} exception is thrown in that child while in the parent process, the method returns normally. On failure, the child is not spawned and messages are printed to the log and stderr. Returns a boolean status value indicating whether an end-of-file on the command socket has been encountered.

return
false if command socket should continue to be read from, or true if an end-of-file has been encountered.
throws
ZygoteInit.MethodAndArgsCaller trampoline to invoke main() method in child process


        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 voidsetChildPgid(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");
        }