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