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