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