ChildProcessLauncher.java revision 5f1c94371a64b3196d4be9466099bb892df9b88e
1// Copyright 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.content.browser;
6
7import android.content.Context;
8import android.graphics.SurfaceTexture;
9import android.util.Log;
10import android.util.Pair;
11import android.view.Surface;
12
13import com.google.common.annotations.VisibleForTesting;
14
15import org.chromium.base.CalledByNative;
16import org.chromium.base.JNINamespace;
17import org.chromium.base.ThreadUtils;
18import org.chromium.base.TraceEvent;
19import org.chromium.base.library_loader.Linker;
20import org.chromium.content.app.ChildProcessService;
21import org.chromium.content.app.ChromiumLinkerParams;
22import org.chromium.content.app.PrivilegedProcessService;
23import org.chromium.content.app.SandboxedProcessService;
24import org.chromium.content.common.IChildProcessCallback;
25import org.chromium.content.common.SurfaceWrapper;
26
27import java.util.ArrayList;
28import java.util.Map;
29import java.util.concurrent.ConcurrentHashMap;
30
31/**
32 * This class provides the method to start/stop ChildProcess called by native.
33 */
34@JNINamespace("content")
35public class ChildProcessLauncher {
36    private static final String TAG = "ChildProcessLauncher";
37
38    static final int CALLBACK_FOR_UNKNOWN_PROCESS = 0;
39    static final int CALLBACK_FOR_GPU_PROCESS = 1;
40    static final int CALLBACK_FOR_RENDERER_PROCESS = 2;
41
42    private static final String SWITCH_PROCESS_TYPE = "type";
43    private static final String SWITCH_PPAPI_BROKER_PROCESS = "ppapi-broker";
44    private static final String SWITCH_RENDERER_PROCESS = "renderer";
45    private static final String SWITCH_GPU_PROCESS = "gpu-process";
46
47    // The upper limit on the number of simultaneous sandboxed and privileged child service process
48    // instances supported. Each limit must not exceed total number of SandboxedProcessServiceX
49    // classes and PrivilegedProcessServiceX classes declared in this package and defined as
50    // services in the embedding application's manifest file.
51    // (See {@link ChildProcessService} for more details on defining the services.)
52    /* package */ static final int MAX_REGISTERED_SANDBOXED_SERVICES = 20;
53    /* package */ static final int MAX_REGISTERED_PRIVILEGED_SERVICES = 3;
54
55    private static class ChildConnectionAllocator {
56        // Connections to services. Indices of the array correspond to the service numbers.
57        private final ChildProcessConnection[] mChildProcessConnections;
58
59        // The list of free (not bound) service indices. When looking for a free service, the first
60        // index in that list should be used. When a service is unbound, its index is added to the
61        // end of the list. This is so that we avoid immediately reusing the freed service (see
62        // http://crbug.com/164069): the framework might keep a service process alive when it's been
63        // unbound for a short time. If a new connection to the same service is bound at that point,
64        // the process is reused and bad things happen (mostly static variables are set when we
65        // don't expect them to).
66        // SHOULD BE ACCESSED WITH mConnectionLock.
67        private final ArrayList<Integer> mFreeConnectionIndices;
68        private final Object mConnectionLock = new Object();
69
70        private Class<? extends ChildProcessService> mChildClass;
71        private final boolean mInSandbox;
72
73        public ChildConnectionAllocator(boolean inSandbox) {
74            int numChildServices = inSandbox ?
75                    MAX_REGISTERED_SANDBOXED_SERVICES : MAX_REGISTERED_PRIVILEGED_SERVICES;
76            mChildProcessConnections = new ChildProcessConnectionImpl[numChildServices];
77            mFreeConnectionIndices = new ArrayList<Integer>(numChildServices);
78            for (int i = 0; i < numChildServices; i++) {
79                mFreeConnectionIndices.add(i);
80            }
81            setServiceClass(inSandbox ?
82                    SandboxedProcessService.class : PrivilegedProcessService.class);
83            mInSandbox = inSandbox;
84        }
85
86        public void setServiceClass(Class<? extends ChildProcessService> childClass) {
87            mChildClass = childClass;
88        }
89
90        public ChildProcessConnection allocate(
91                Context context, ChildProcessConnection.DeathCallback deathCallback,
92                ChromiumLinkerParams chromiumLinkerParams) {
93            synchronized (mConnectionLock) {
94                if (mFreeConnectionIndices.isEmpty()) {
95                    Log.e(TAG, "Ran out of services to allocate.");
96                    assert false;
97                    return null;
98                }
99                int slot = mFreeConnectionIndices.remove(0);
100                assert mChildProcessConnections[slot] == null;
101                mChildProcessConnections[slot] = new ChildProcessConnectionImpl(context, slot,
102                        mInSandbox, deathCallback, mChildClass, chromiumLinkerParams);
103                return mChildProcessConnections[slot];
104            }
105        }
106
107        public void free(ChildProcessConnection connection) {
108            synchronized (mConnectionLock) {
109                int slot = connection.getServiceNumber();
110                if (mChildProcessConnections[slot] != connection) {
111                    int occupier = mChildProcessConnections[slot] == null ?
112                            -1 : mChildProcessConnections[slot].getServiceNumber();
113                    Log.e(TAG, "Unable to find connection to free in slot: " + slot +
114                            " already occupied by service: " + occupier);
115                    assert false;
116                } else {
117                    mChildProcessConnections[slot] = null;
118                    assert !mFreeConnectionIndices.contains(slot);
119                    mFreeConnectionIndices.add(slot);
120                }
121            }
122        }
123
124        /** @return the count of connections managed by the allocator */
125        @VisibleForTesting
126        int allocatedConnectionsCountForTesting() {
127            return mChildProcessConnections.length - mFreeConnectionIndices.size();
128        }
129    }
130
131    // Service class for child process. As the default value it uses SandboxedProcessService0 and
132    // PrivilegedProcessService0.
133    private static final ChildConnectionAllocator sSandboxedChildConnectionAllocator =
134            new ChildConnectionAllocator(true);
135    private static final ChildConnectionAllocator sPrivilegedChildConnectionAllocator =
136            new ChildConnectionAllocator(false);
137
138    private static boolean sConnectionAllocated = false;
139
140    /**
141     * Sets service class for sandboxed service and privileged service.
142     */
143    public static void setChildProcessClass(
144            Class<? extends SandboxedProcessService> sandboxedServiceClass,
145            Class<? extends PrivilegedProcessService> privilegedServiceClass) {
146        // We should guarantee this is called before allocating connection.
147        assert !sConnectionAllocated;
148        sSandboxedChildConnectionAllocator.setServiceClass(sandboxedServiceClass);
149        sPrivilegedChildConnectionAllocator.setServiceClass(privilegedServiceClass);
150    }
151
152    private static ChildConnectionAllocator getConnectionAllocator(boolean inSandbox) {
153        return inSandbox ?
154                sSandboxedChildConnectionAllocator : sPrivilegedChildConnectionAllocator;
155    }
156
157    private static ChildProcessConnection allocateConnection(Context context,
158            boolean inSandbox, ChromiumLinkerParams chromiumLinkerParams) {
159        ChildProcessConnection.DeathCallback deathCallback =
160            new ChildProcessConnection.DeathCallback() {
161                @Override
162                public void onChildProcessDied(ChildProcessConnection connection) {
163                    if (connection.getPid() != 0) {
164                        stop(connection.getPid());
165                    } else {
166                        freeConnection(connection);
167                    }
168                }
169            };
170        sConnectionAllocated = true;
171        return getConnectionAllocator(inSandbox).allocate(context, deathCallback,
172                chromiumLinkerParams);
173    }
174
175    private static boolean sLinkerInitialized = false;
176    private static long sLinkerLoadAddress = 0;
177
178    private static ChromiumLinkerParams getLinkerParamsForNewConnection() {
179        if (!sLinkerInitialized) {
180            if (Linker.isUsed()) {
181                sLinkerLoadAddress = Linker.getBaseLoadAddress();
182                if (sLinkerLoadAddress == 0) {
183                    Log.i(TAG, "Shared RELRO support disabled!");
184                }
185            }
186            sLinkerInitialized = true;
187        }
188
189        if (sLinkerLoadAddress == 0)
190            return null;
191
192        // Always wait for the shared RELROs in service processes.
193        final boolean waitForSharedRelros = true;
194        return new ChromiumLinkerParams(sLinkerLoadAddress,
195                                waitForSharedRelros,
196                                Linker.getTestRunnerClassName());
197    }
198
199    private static ChildProcessConnection allocateBoundConnection(Context context,
200            String[] commandLine, boolean inSandbox) {
201        ChromiumLinkerParams chromiumLinkerParams = getLinkerParamsForNewConnection();
202        ChildProcessConnection connection =
203                allocateConnection(context, inSandbox, chromiumLinkerParams);
204        if (connection != null) {
205            connection.start(commandLine);
206        }
207        return connection;
208    }
209
210    private static void freeConnection(ChildProcessConnection connection) {
211        getConnectionAllocator(connection.isInSandbox()).free(connection);
212    }
213
214    // Represents an invalid process handle; same as base/process/process.h kNullProcessHandle.
215    private static final int NULL_PROCESS_HANDLE = 0;
216
217    // Map from pid to ChildService connection.
218    private static Map<Integer, ChildProcessConnection> sServiceMap =
219            new ConcurrentHashMap<Integer, ChildProcessConnection>();
220
221    // A pre-allocated and pre-bound connection ready for connection setup, or null.
222    private static ChildProcessConnection sSpareSandboxedConnection = null;
223
224    // Manages oom bindings used to bind chind services.
225    private static BindingManager sBindingManager = BindingManagerImpl.createBindingManager();
226
227    // Map from surface id to Surface.
228    private static Map<Integer, Surface> sViewSurfaceMap =
229            new ConcurrentHashMap<Integer, Surface>();
230
231    // Map from surface texture id to Surface.
232    private static Map<Pair<Integer, Integer>, Surface> sSurfaceTextureSurfaceMap =
233            new ConcurrentHashMap<Pair<Integer, Integer>, Surface>();
234
235    @VisibleForTesting
236    public static void setBindingManagerForTesting(BindingManager manager) {
237        sBindingManager = manager;
238    }
239
240    /** @return true iff the child process is protected from out-of-memory killing */
241    @CalledByNative
242    private static boolean isOomProtected(int pid) {
243        return sBindingManager.isOomProtected(pid);
244    }
245
246    @CalledByNative
247    private static void registerViewSurface(int surfaceId, Surface surface) {
248        sViewSurfaceMap.put(surfaceId, surface);
249    }
250
251    @CalledByNative
252    private static void unregisterViewSurface(int surfaceId) {
253        sViewSurfaceMap.remove(surfaceId);
254    }
255
256    @CalledByNative
257    private static void registerSurfaceTexture(
258            int surfaceTextureId, int childProcessId, SurfaceTexture surfaceTexture) {
259        Pair<Integer, Integer> key = new Pair<Integer, Integer>(surfaceTextureId, childProcessId);
260        sSurfaceTextureSurfaceMap.put(key, new Surface(surfaceTexture));
261    }
262
263    @CalledByNative
264    private static void unregisterSurfaceTexture(int surfaceTextureId, int childProcessId) {
265        Pair<Integer, Integer> key = new Pair<Integer, Integer>(surfaceTextureId, childProcessId);
266        sSurfaceTextureSurfaceMap.remove(key);
267    }
268
269    /**
270     * Sets the visibility of the child process when it changes or when it is determined for the
271     * first time.
272     */
273    @CalledByNative
274    public static void setInForeground(int pid, boolean inForeground) {
275        sBindingManager.setInForeground(pid, inForeground);
276    }
277
278    /**
279     * Called when the embedding application is sent to background.
280     */
281    public static void onSentToBackground() {
282        sBindingManager.onSentToBackground();
283    }
284
285    /**
286     * Called when the embedding application is brought to foreground.
287     */
288    public static void onBroughtToForeground() {
289        sBindingManager.onBroughtToForeground();
290    }
291
292    /**
293     * Should be called early in startup so the work needed to spawn the child process can be done
294     * in parallel to other startup work. Must not be called on the UI thread. Spare connection is
295     * created in sandboxed child process.
296     * @param context the application context used for the connection.
297     */
298    public static void warmUp(Context context) {
299        synchronized (ChildProcessLauncher.class) {
300            assert !ThreadUtils.runningOnUiThread();
301            if (sSpareSandboxedConnection == null) {
302                sSpareSandboxedConnection = allocateBoundConnection(context, null, true);
303            }
304        }
305    }
306
307    private static String getSwitchValue(final String[] commandLine, String switchKey) {
308        if (commandLine == null || switchKey == null) {
309            return null;
310        }
311        // This format should be matched with the one defined in command_line.h.
312        final String switchKeyPrefix = "--" + switchKey + "=";
313        for (String command : commandLine) {
314            if (command != null && command.startsWith(switchKeyPrefix)) {
315                return command.substring(switchKeyPrefix.length());
316            }
317        }
318        return null;
319    }
320
321    /**
322     * Spawns and connects to a child process. May be called on any thread. It will not block, but
323     * will instead callback to {@link #nativeOnChildProcessStarted} when the connection is
324     * established. Note this callback will not necessarily be from the same thread (currently it
325     * always comes from the main thread).
326     *
327     * @param context Context used to obtain the application context.
328     * @param commandLine The child process command line argv.
329     * @param fileIds The ID that should be used when mapping files in the created process.
330     * @param fileFds The file descriptors that should be mapped in the created process.
331     * @param fileAutoClose Whether the file descriptors should be closed once they were passed to
332     * the created process.
333     * @param clientContext Arbitrary parameter used by the client to distinguish this connection.
334     */
335    @CalledByNative
336    static void start(
337            Context context,
338            final String[] commandLine,
339            int childProcessId,
340            int[] fileIds,
341            int[] fileFds,
342            boolean[] fileAutoClose,
343            long clientContext) {
344        TraceEvent.begin();
345        assert fileIds.length == fileFds.length && fileFds.length == fileAutoClose.length;
346        FileDescriptorInfo[] filesToBeMapped = new FileDescriptorInfo[fileFds.length];
347        for (int i = 0; i < fileFds.length; i++) {
348            filesToBeMapped[i] =
349                    new FileDescriptorInfo(fileIds[i], fileFds[i], fileAutoClose[i]);
350        }
351        assert clientContext != 0;
352
353        int callbackType = CALLBACK_FOR_UNKNOWN_PROCESS;
354        boolean inSandbox = true;
355        String processType = getSwitchValue(commandLine, SWITCH_PROCESS_TYPE);
356        if (SWITCH_RENDERER_PROCESS.equals(processType)) {
357            callbackType = CALLBACK_FOR_RENDERER_PROCESS;
358        } else if (SWITCH_GPU_PROCESS.equals(processType)) {
359            callbackType = CALLBACK_FOR_GPU_PROCESS;
360        } else if (SWITCH_PPAPI_BROKER_PROCESS.equals(processType)) {
361            inSandbox = false;
362        }
363
364        ChildProcessConnection allocatedConnection = null;
365        synchronized (ChildProcessLauncher.class) {
366            if (inSandbox) {
367                allocatedConnection = sSpareSandboxedConnection;
368                sSpareSandboxedConnection = null;
369            }
370        }
371        if (allocatedConnection == null) {
372            allocatedConnection = allocateBoundConnection(context, commandLine, inSandbox);
373            if (allocatedConnection == null) {
374                // Notify the native code so it can free the heap allocated callback.
375                nativeOnChildProcessStarted(clientContext, 0);
376                Log.e(TAG, "Allocation of new service failed.");
377                TraceEvent.end();
378                return;
379            }
380        }
381
382        Log.d(TAG, "Setting up connection to process: slot=" +
383                allocatedConnection.getServiceNumber());
384        triggerConnectionSetup(allocatedConnection, commandLine, childProcessId, filesToBeMapped,
385                callbackType, clientContext);
386        TraceEvent.end();
387    }
388
389    @VisibleForTesting
390    static void triggerConnectionSetup(
391            final ChildProcessConnection connection,
392            String[] commandLine,
393            int childProcessId,
394            FileDescriptorInfo[] filesToBeMapped,
395            int callbackType,
396            final long clientContext) {
397        ChildProcessConnection.ConnectionCallback connectionCallback =
398                new ChildProcessConnection.ConnectionCallback() {
399                    @Override
400                    public void onConnected(int pid) {
401                        Log.d(TAG, "on connect callback, pid=" + pid + " context=" + clientContext);
402                        if (pid != NULL_PROCESS_HANDLE) {
403                            sBindingManager.addNewConnection(pid, connection);
404                            sServiceMap.put(pid, connection);
405                        }
406                        // If the connection fails and pid == 0, the Java-side cleanup was already
407                        // handled by DeathCallback. We still have to call back to native for
408                        // cleanup there.
409                        if (clientContext != 0) {  // Will be 0 in Java instrumentation tests.
410                            nativeOnChildProcessStarted(clientContext, pid);
411                        }
412                    }
413                };
414
415        // TODO(sievers): Revisit this as it doesn't correctly handle the utility process
416        // assert callbackType != CALLBACK_FOR_UNKNOWN_PROCESS;
417
418        connection.setupConnection(commandLine,
419                                   filesToBeMapped,
420                                   createCallback(childProcessId, callbackType),
421                                   connectionCallback,
422                                   Linker.getSharedRelros());
423    }
424
425    /**
426     * Terminates a child process. This may be called from any thread.
427     *
428     * @param pid The pid (process handle) of the service connection obtained from {@link #start}.
429     */
430    @CalledByNative
431    static void stop(int pid) {
432        Log.d(TAG, "stopping child connection: pid=" + pid);
433        ChildProcessConnection connection = sServiceMap.remove(pid);
434        if (connection == null) {
435            logPidWarning(pid, "Tried to stop non-existent connection");
436            return;
437        }
438        sBindingManager.clearConnection(pid);
439        connection.stop();
440        freeConnection(connection);
441    }
442
443    /**
444     * This implementation is used to receive callbacks from the remote service.
445     */
446    private static IChildProcessCallback createCallback(
447            final int childProcessId, final int callbackType) {
448        return new IChildProcessCallback.Stub() {
449            /**
450             * This is called by the remote service regularly to tell us about new values. Note that
451             * IPC calls are dispatched through a thread pool running in each process, so the code
452             * executing here will NOT be running in our main thread -- so, to update the UI, we
453             * need to use a Handler.
454             */
455            @Override
456            public void establishSurfacePeer(
457                    int pid, Surface surface, int primaryID, int secondaryID) {
458                // Do not allow a malicious renderer to connect to a producer. This is only used
459                // from stream textures managed by the GPU process.
460                if (callbackType != CALLBACK_FOR_GPU_PROCESS) {
461                    Log.e(TAG, "Illegal callback for non-GPU process.");
462                    return;
463                }
464
465                nativeEstablishSurfacePeer(pid, surface, primaryID, secondaryID);
466            }
467
468            @Override
469            public SurfaceWrapper getViewSurface(int surfaceId) {
470                // Do not allow a malicious renderer to get to our view surface.
471                if (callbackType != CALLBACK_FOR_GPU_PROCESS) {
472                    Log.e(TAG, "Illegal callback for non-GPU process.");
473                    return null;
474                }
475
476                Surface surface = sViewSurfaceMap.get(surfaceId);
477                if (surface == null) {
478                    Log.e(TAG, "Invalid surfaceId.");
479                    return null;
480                }
481                assert surface.isValid();
482                return new SurfaceWrapper(surface);
483            }
484
485            @Override
486            public SurfaceWrapper getSurfaceTextureSurface(int primaryId, int secondaryId) {
487                if (callbackType != CALLBACK_FOR_RENDERER_PROCESS) {
488                    Log.e(TAG, "Illegal callback for non-renderer process.");
489                    return null;
490                }
491
492                if (secondaryId != childProcessId) {
493                    Log.e(TAG, "Illegal secondaryId for renderer process.");
494                    return null;
495                }
496
497                Pair<Integer, Integer> key = new Pair<Integer, Integer>(primaryId, secondaryId);
498                // Note: This removes the surface and passes the ownership to the caller.
499                Surface surface = sSurfaceTextureSurfaceMap.remove(key);
500                if (surface == null) {
501                    Log.e(TAG, "Invalid Id for surface texture.");
502                    return null;
503                }
504                assert surface.isValid();
505                return new SurfaceWrapper(surface);
506            }
507        };
508    }
509
510     static void logPidWarning(int pid, String message) {
511        // This class is effectively a no-op in single process mode, so don't log warnings there.
512        if (pid > 0 && !nativeIsSingleProcess()) {
513            Log.w(TAG, message + ", pid=" + pid);
514        }
515    }
516
517    @VisibleForTesting
518    static ChildProcessConnection allocateBoundConnectionForTesting(Context context) {
519        return allocateBoundConnection(context, null, true);
520    }
521
522    /** @return the count of sandboxed connections managed by the allocator */
523    @VisibleForTesting
524    static int allocatedConnectionsCountForTesting() {
525        return sSandboxedChildConnectionAllocator.allocatedConnectionsCountForTesting();
526    }
527
528    /** @return the count of services set up and working */
529    @VisibleForTesting
530    static int connectedServicesCountForTesting() {
531        return sServiceMap.size();
532    }
533
534    private static native void nativeOnChildProcessStarted(long clientContext, int pid);
535    private static native void nativeEstablishSurfacePeer(
536            int pid, Surface surface, int primaryID, int secondaryID);
537    private static native boolean nativeIsSingleProcess();
538}
539