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