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