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