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