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