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