1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.os; 18 19 import static android.system.OsConstants.F_SETFD; 20 import static android.system.OsConstants.O_CLOEXEC; 21 import static android.system.OsConstants.POLLIN; 22 23 import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEOUT_MILLIS; 24 import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS; 25 26 import android.compat.annotation.UnsupportedAppUsage; 27 import android.content.pm.ApplicationInfo; 28 import android.net.Credentials; 29 import android.net.LocalSocket; 30 import android.os.Parcel; 31 import android.os.Process; 32 import android.os.Trace; 33 import android.system.ErrnoException; 34 import android.system.Os; 35 import android.system.StructPollfd; 36 import android.util.Log; 37 38 import dalvik.system.VMRuntime; 39 40 import libcore.io.IoUtils; 41 42 import java.io.BufferedReader; 43 import java.io.ByteArrayInputStream; 44 import java.io.DataInputStream; 45 import java.io.DataOutputStream; 46 import java.io.FileDescriptor; 47 import java.io.IOException; 48 import java.io.InputStreamReader; 49 import java.nio.charset.StandardCharsets; 50 import java.util.Base64; 51 import java.util.concurrent.TimeUnit; 52 53 /** 54 * A connection that can make spawn requests. 55 */ 56 class ZygoteConnection { 57 private static final String TAG = "Zygote"; 58 59 /** 60 * The command socket. 61 * 62 * mSocket is retained in the child process in "peer wait" mode, so 63 * that it closes when the child process terminates. In other cases, 64 * it is closed in the peer. 65 */ 66 @UnsupportedAppUsage 67 private final LocalSocket mSocket; 68 @UnsupportedAppUsage 69 private final DataOutputStream mSocketOutStream; 70 private final BufferedReader mSocketReader; 71 @UnsupportedAppUsage 72 private final Credentials peer; 73 private final String abiList; 74 private boolean isEof; 75 76 /** 77 * Constructs instance from connected socket. 78 * 79 * @param socket non-null; connected socket 80 * @param abiList non-null; a list of ABIs this zygote supports. 81 * @throws IOException If obtaining the peer credentials fails 82 */ ZygoteConnection(LocalSocket socket, String abiList)83 ZygoteConnection(LocalSocket socket, String abiList) throws IOException { 84 mSocket = socket; 85 this.abiList = abiList; 86 87 mSocketOutStream = new DataOutputStream(socket.getOutputStream()); 88 mSocketReader = 89 new BufferedReader( 90 new InputStreamReader(socket.getInputStream()), Zygote.SOCKET_BUFFER_SIZE); 91 92 mSocket.setSoTimeout(CONNECTION_TIMEOUT_MILLIS); 93 94 try { 95 peer = mSocket.getPeerCredentials(); 96 } catch (IOException ex) { 97 Log.e(TAG, "Cannot read peer credentials", ex); 98 throw ex; 99 } 100 101 isEof = false; 102 } 103 104 /** 105 * Returns the file descriptor of the associated socket. 106 * 107 * @return null-ok; file descriptor 108 */ getFileDescriptor()109 FileDescriptor getFileDescriptor() { 110 return mSocket.getFileDescriptor(); 111 } 112 113 /** 114 * Reads one start command from the command socket. If successful, a child is forked and a 115 * {@code Runnable} that calls the childs main method (or equivalent) is returned in the child 116 * process. {@code null} is always returned in the parent process (the zygote). 117 * 118 * If the client closes the socket, an {@code EOF} condition is set, which callers can test 119 * for by calling {@code ZygoteConnection.isClosedByPeer}. 120 */ processOneCommand(ZygoteServer zygoteServer)121 Runnable processOneCommand(ZygoteServer zygoteServer) { 122 String[] args; 123 124 try { 125 args = Zygote.readArgumentList(mSocketReader); 126 } catch (IOException ex) { 127 throw new IllegalStateException("IOException on command socket", ex); 128 } 129 130 // readArgumentList returns null only when it has reached EOF with no available 131 // data to read. This will only happen when the remote socket has disconnected. 132 if (args == null) { 133 isEof = true; 134 return null; 135 } 136 137 int pid; 138 FileDescriptor childPipeFd = null; 139 FileDescriptor serverPipeFd = null; 140 141 ZygoteArguments parsedArgs = new ZygoteArguments(args); 142 143 if (parsedArgs.mBootCompleted) { 144 handleBootCompleted(); 145 return null; 146 } 147 148 if (parsedArgs.mAbiListQuery) { 149 handleAbiListQuery(); 150 return null; 151 } 152 153 if (parsedArgs.mPidQuery) { 154 handlePidQuery(); 155 return null; 156 } 157 158 if (parsedArgs.mUsapPoolStatusSpecified) { 159 return handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled); 160 } 161 162 if (parsedArgs.mPreloadDefault) { 163 handlePreload(); 164 return null; 165 } 166 167 if (parsedArgs.mPreloadPackage != null) { 168 handlePreloadPackage(parsedArgs.mPreloadPackage, parsedArgs.mPreloadPackageLibs, 169 parsedArgs.mPreloadPackageLibFileName, parsedArgs.mPreloadPackageCacheKey); 170 return null; 171 } 172 173 if (canPreloadApp() && parsedArgs.mPreloadApp != null) { 174 byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp); 175 Parcel appInfoParcel = Parcel.obtain(); 176 appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length); 177 appInfoParcel.setDataPosition(0); 178 ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(appInfoParcel); 179 appInfoParcel.recycle(); 180 if (appInfo != null) { 181 handlePreloadApp(appInfo); 182 } else { 183 throw new IllegalArgumentException("Failed to deserialize --preload-app"); 184 } 185 return null; 186 } 187 188 if (parsedArgs.mApiBlacklistExemptions != null) { 189 return handleApiBlacklistExemptions(zygoteServer, parsedArgs.mApiBlacklistExemptions); 190 } 191 192 if (parsedArgs.mHiddenApiAccessLogSampleRate != -1 193 || parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) { 194 return handleHiddenApiAccessLogSampleRate(zygoteServer, 195 parsedArgs.mHiddenApiAccessLogSampleRate, 196 parsedArgs.mHiddenApiAccessStatslogSampleRate); 197 } 198 199 if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) { 200 throw new ZygoteSecurityException("Client may not specify capabilities: " 201 + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities) 202 + ", effective=0x" + Long.toHexString(parsedArgs.mEffectiveCapabilities)); 203 } 204 205 Zygote.applyUidSecurityPolicy(parsedArgs, peer); 206 Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer); 207 208 Zygote.applyDebuggerSystemProperty(parsedArgs); 209 Zygote.applyInvokeWithSystemProperty(parsedArgs); 210 211 int[][] rlimits = null; 212 213 if (parsedArgs.mRLimits != null) { 214 rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D); 215 } 216 217 int[] fdsToIgnore = null; 218 219 if (parsedArgs.mInvokeWith != null) { 220 try { 221 FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC); 222 childPipeFd = pipeFds[1]; 223 serverPipeFd = pipeFds[0]; 224 Os.fcntlInt(childPipeFd, F_SETFD, 0); 225 fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()}; 226 } catch (ErrnoException errnoEx) { 227 throw new IllegalStateException("Unable to set up pipe for invoke-with", errnoEx); 228 } 229 } 230 231 /* 232 * In order to avoid leaking descriptors to the Zygote child, 233 * the native code must close the two Zygote socket descriptors 234 * in the child process before it switches from Zygote-root to 235 * the UID and privileges of the application being launched. 236 * 237 * In order to avoid "bad file descriptor" errors when the 238 * two LocalSocket objects are closed, the Posix file 239 * descriptors are released via a dup2() call which closes 240 * the socket and substitutes an open descriptor to /dev/null. 241 */ 242 243 int [] fdsToClose = { -1, -1 }; 244 245 FileDescriptor fd = mSocket.getFileDescriptor(); 246 247 if (fd != null) { 248 fdsToClose[0] = fd.getInt$(); 249 } 250 251 fd = zygoteServer.getZygoteSocketFileDescriptor(); 252 253 if (fd != null) { 254 fdsToClose[1] = fd.getInt$(); 255 } 256 257 pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids, 258 parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo, 259 parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote, 260 parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mIsTopApp, 261 parsedArgs.mPkgDataInfoList, parsedArgs.mWhitelistedDataInfoList, 262 parsedArgs.mBindMountAppDataDirs, parsedArgs.mBindMountAppStorageDirs); 263 264 try { 265 if (pid == 0) { 266 // in child 267 zygoteServer.setForkChild(); 268 269 zygoteServer.closeServerSocket(); 270 IoUtils.closeQuietly(serverPipeFd); 271 serverPipeFd = null; 272 273 return handleChildProc(parsedArgs, childPipeFd, parsedArgs.mStartChildZygote); 274 } else { 275 // In the parent. A pid < 0 indicates a failure and will be handled in 276 // handleParentProc. 277 IoUtils.closeQuietly(childPipeFd); 278 childPipeFd = null; 279 handleParentProc(pid, serverPipeFd); 280 return null; 281 } 282 } finally { 283 IoUtils.closeQuietly(childPipeFd); 284 IoUtils.closeQuietly(serverPipeFd); 285 } 286 } 287 handleAbiListQuery()288 private void handleAbiListQuery() { 289 try { 290 final byte[] abiListBytes = abiList.getBytes(StandardCharsets.US_ASCII); 291 mSocketOutStream.writeInt(abiListBytes.length); 292 mSocketOutStream.write(abiListBytes); 293 } catch (IOException ioe) { 294 throw new IllegalStateException("Error writing to command socket", ioe); 295 } 296 } 297 handlePidQuery()298 private void handlePidQuery() { 299 try { 300 String pidString = String.valueOf(Process.myPid()); 301 final byte[] pidStringBytes = pidString.getBytes(StandardCharsets.US_ASCII); 302 mSocketOutStream.writeInt(pidStringBytes.length); 303 mSocketOutStream.write(pidStringBytes); 304 } catch (IOException ioe) { 305 throw new IllegalStateException("Error writing to command socket", ioe); 306 } 307 } 308 handleBootCompleted()309 private void handleBootCompleted() { 310 try { 311 mSocketOutStream.writeInt(0); 312 } catch (IOException ioe) { 313 throw new IllegalStateException("Error writing to command socket", ioe); 314 } 315 316 VMRuntime.bootCompleted(); 317 } 318 319 /** 320 * Preloads resources if the zygote is in lazily preload mode. Writes the result of the 321 * preload operation; {@code 0} when a preload was initiated due to this request and {@code 1} 322 * if no preload was initiated. The latter implies that the zygote is not configured to load 323 * resources lazy or that the zygote has already handled a previous request to handlePreload. 324 */ handlePreload()325 private void handlePreload() { 326 try { 327 if (isPreloadComplete()) { 328 mSocketOutStream.writeInt(1); 329 } else { 330 preload(); 331 mSocketOutStream.writeInt(0); 332 } 333 } catch (IOException ioe) { 334 throw new IllegalStateException("Error writing to command socket", ioe); 335 } 336 } 337 stateChangeWithUsapPoolReset(ZygoteServer zygoteServer, Runnable stateChangeCode)338 private Runnable stateChangeWithUsapPoolReset(ZygoteServer zygoteServer, 339 Runnable stateChangeCode) { 340 try { 341 if (zygoteServer.isUsapPoolEnabled()) { 342 Log.i(TAG, "Emptying USAP Pool due to state change."); 343 Zygote.emptyUsapPool(); 344 } 345 346 stateChangeCode.run(); 347 348 if (zygoteServer.isUsapPoolEnabled()) { 349 Runnable fpResult = 350 zygoteServer.fillUsapPool( 351 new int[]{mSocket.getFileDescriptor().getInt$()}, false); 352 353 if (fpResult != null) { 354 zygoteServer.setForkChild(); 355 return fpResult; 356 } else { 357 Log.i(TAG, "Finished refilling USAP Pool after state change."); 358 } 359 } 360 361 mSocketOutStream.writeInt(0); 362 363 return null; 364 } catch (IOException ioe) { 365 throw new IllegalStateException("Error writing to command socket", ioe); 366 } 367 } 368 369 /** 370 * Makes the necessary changes to implement a new API blacklist exemption policy, and then 371 * responds to the system server, letting it know that the task has been completed. 372 * 373 * This necessitates a change to the internal state of the Zygote. As such, if the USAP 374 * pool is enabled all existing USAPs have an incorrect API blacklist exemption list. To 375 * properly handle this request the pool must be emptied and refilled. This process can return 376 * a Runnable object that must be returned to ZygoteServer.runSelectLoop to be invoked. 377 * 378 * @param zygoteServer The server object that received the request 379 * @param exemptions The new exemption list. 380 * @return A Runnable object representing a new app in any USAPs spawned from here; the 381 * zygote process will always receive a null value from this function. 382 */ handleApiBlacklistExemptions(ZygoteServer zygoteServer, String[] exemptions)383 private Runnable handleApiBlacklistExemptions(ZygoteServer zygoteServer, String[] exemptions) { 384 return stateChangeWithUsapPoolReset(zygoteServer, 385 () -> ZygoteInit.setApiBlacklistExemptions(exemptions)); 386 } 387 handleUsapPoolStatusChange(ZygoteServer zygoteServer, boolean newStatus)388 private Runnable handleUsapPoolStatusChange(ZygoteServer zygoteServer, boolean newStatus) { 389 try { 390 Runnable fpResult = zygoteServer.setUsapPoolStatus(newStatus, mSocket); 391 392 if (fpResult == null) { 393 mSocketOutStream.writeInt(0); 394 } else { 395 zygoteServer.setForkChild(); 396 } 397 398 return fpResult; 399 } catch (IOException ioe) { 400 throw new IllegalStateException("Error writing to command socket", ioe); 401 } 402 } 403 404 /** 405 * Changes the API access log sample rate for the Zygote and processes spawned from it. 406 * 407 * This necessitates a change to the internal state of the Zygote. As such, if the USAP 408 * pool is enabled all existing USAPs have an incorrect API access log sample rate. To 409 * properly handle this request the pool must be emptied and refilled. This process can return 410 * a Runnable object that must be returned to ZygoteServer.runSelectLoop to be invoked. 411 * 412 * @param zygoteServer The server object that received the request 413 * @param samplingRate The new sample rate for regular logging 414 * @param statsdSamplingRate The new sample rate for statslog logging 415 * @return A Runnable object representing a new app in any blastulas spawned from here; the 416 * zygote process will always receive a null value from this function. 417 */ handleHiddenApiAccessLogSampleRate(ZygoteServer zygoteServer, int samplingRate, int statsdSamplingRate)418 private Runnable handleHiddenApiAccessLogSampleRate(ZygoteServer zygoteServer, 419 int samplingRate, int statsdSamplingRate) { 420 return stateChangeWithUsapPoolReset(zygoteServer, () -> { 421 int maxSamplingRate = Math.max(samplingRate, statsdSamplingRate); 422 ZygoteInit.setHiddenApiAccessLogSampleRate(maxSamplingRate); 423 StatsdHiddenApiUsageLogger.setHiddenApiAccessLogSampleRates( 424 samplingRate, statsdSamplingRate); 425 ZygoteInit.setHiddenApiUsageLogger(StatsdHiddenApiUsageLogger.getInstance()); 426 }); 427 } 428 preload()429 protected void preload() { 430 ZygoteInit.lazyPreload(); 431 } 432 isPreloadComplete()433 protected boolean isPreloadComplete() { 434 return ZygoteInit.isPreloadComplete(); 435 } 436 getSocketOutputStream()437 protected DataOutputStream getSocketOutputStream() { 438 return mSocketOutStream; 439 } 440 handlePreloadPackage(String packagePath, String libsPath, String libFileName, String cacheKey)441 protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName, 442 String cacheKey) { 443 throw new RuntimeException("Zygote does not support package preloading"); 444 } 445 canPreloadApp()446 protected boolean canPreloadApp() { 447 return false; 448 } 449 handlePreloadApp(ApplicationInfo aInfo)450 protected void handlePreloadApp(ApplicationInfo aInfo) { 451 throw new RuntimeException("Zygote does not support app preloading"); 452 } 453 454 /** 455 * Closes socket associated with this connection. 456 */ 457 @UnsupportedAppUsage closeSocket()458 void closeSocket() { 459 try { 460 mSocket.close(); 461 } catch (IOException ex) { 462 Log.e(TAG, "Exception while closing command " 463 + "socket in parent", ex); 464 } 465 } 466 isClosedByPeer()467 boolean isClosedByPeer() { 468 return isEof; 469 } 470 471 /** 472 * Handles post-fork setup of child proc, closing sockets as appropriate, 473 * reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller 474 * if successful or returning if failed. 475 * 476 * @param parsedArgs non-null; zygote args 477 * @param pipeFd null-ok; pipe for communication back to Zygote. 478 * @param isZygote whether this new child process is itself a new Zygote. 479 */ handleChildProc(ZygoteArguments parsedArgs, FileDescriptor pipeFd, boolean isZygote)480 private Runnable handleChildProc(ZygoteArguments parsedArgs, 481 FileDescriptor pipeFd, boolean isZygote) { 482 /* 483 * By the time we get here, the native code has closed the two actual Zygote 484 * socket connections, and substituted /dev/null in their place. The LocalSocket 485 * objects still need to be closed properly. 486 */ 487 488 closeSocket(); 489 490 Zygote.setAppProcessName(parsedArgs, TAG); 491 492 // End of the postFork event. 493 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 494 if (parsedArgs.mInvokeWith != null) { 495 WrapperInit.execApplication(parsedArgs.mInvokeWith, 496 parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, 497 VMRuntime.getCurrentInstructionSet(), 498 pipeFd, parsedArgs.mRemainingArgs); 499 500 // Should not get here. 501 throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned"); 502 } else { 503 if (!isZygote) { 504 return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, 505 parsedArgs.mDisabledCompatChanges, 506 parsedArgs.mRemainingArgs, null /* classLoader */); 507 } else { 508 return ZygoteInit.childZygoteInit(parsedArgs.mTargetSdkVersion, 509 parsedArgs.mRemainingArgs, null /* classLoader */); 510 } 511 } 512 } 513 514 /** 515 * Handles post-fork cleanup of parent proc 516 * 517 * @param pid != 0; pid of child if > 0 or indication of failed fork 518 * if < 0; 519 * @param pipeFd null-ok; pipe for communication with child. 520 */ handleParentProc(int pid, FileDescriptor pipeFd)521 private void handleParentProc(int pid, FileDescriptor pipeFd) { 522 if (pid > 0) { 523 setChildPgid(pid); 524 } 525 526 boolean usingWrapper = false; 527 if (pipeFd != null && pid > 0) { 528 int innerPid = -1; 529 try { 530 // Do a busy loop here. We can't guarantee that a failure (and thus an exception 531 // bail) happens in a timely manner. 532 final int BYTES_REQUIRED = 4; // Bytes in an int. 533 534 StructPollfd[] fds = new StructPollfd[] { 535 new StructPollfd() 536 }; 537 538 byte[] data = new byte[BYTES_REQUIRED]; 539 540 int remainingSleepTime = WRAPPED_PID_TIMEOUT_MILLIS; 541 int dataIndex = 0; 542 long startTime = System.nanoTime(); 543 544 while (dataIndex < data.length && remainingSleepTime > 0) { 545 fds[0].fd = pipeFd; 546 fds[0].events = (short) POLLIN; 547 fds[0].revents = 0; 548 fds[0].userData = null; 549 550 int res = android.system.Os.poll(fds, remainingSleepTime); 551 long endTime = System.nanoTime(); 552 int elapsedTimeMs = 553 (int) TimeUnit.MILLISECONDS.convert( 554 endTime - startTime, 555 TimeUnit.NANOSECONDS); 556 remainingSleepTime = WRAPPED_PID_TIMEOUT_MILLIS - elapsedTimeMs; 557 558 if (res > 0) { 559 if ((fds[0].revents & POLLIN) != 0) { 560 // Only read one byte, so as not to block. 561 int readBytes = android.system.Os.read(pipeFd, data, dataIndex, 1); 562 if (readBytes < 0) { 563 throw new RuntimeException("Some error"); 564 } 565 dataIndex += readBytes; 566 } else { 567 // Error case. revents should contain one of the error bits. 568 break; 569 } 570 } else if (res == 0) { 571 Log.w(TAG, "Timed out waiting for child."); 572 } 573 } 574 575 if (dataIndex == data.length) { 576 DataInputStream is = new DataInputStream(new ByteArrayInputStream(data)); 577 innerPid = is.readInt(); 578 } 579 580 if (innerPid == -1) { 581 Log.w(TAG, "Error reading pid from wrapped process, child may have died"); 582 } 583 } catch (Exception ex) { 584 Log.w(TAG, "Error reading pid from wrapped process, child may have died", ex); 585 } 586 587 // Ensure that the pid reported by the wrapped process is either the 588 // child process that we forked, or a descendant of it. 589 if (innerPid > 0) { 590 int parentPid = innerPid; 591 while (parentPid > 0 && parentPid != pid) { 592 parentPid = Process.getParentPid(parentPid); 593 } 594 if (parentPid > 0) { 595 Log.i(TAG, "Wrapped process has pid " + innerPid); 596 pid = innerPid; 597 usingWrapper = true; 598 } else { 599 Log.w(TAG, "Wrapped process reported a pid that is not a child of " 600 + "the process that we forked: childPid=" + pid 601 + " innerPid=" + innerPid); 602 } 603 } 604 } 605 606 try { 607 mSocketOutStream.writeInt(pid); 608 mSocketOutStream.writeBoolean(usingWrapper); 609 } catch (IOException ex) { 610 throw new IllegalStateException("Error writing to command socket", ex); 611 } 612 } 613 setChildPgid(int pid)614 private void setChildPgid(int pid) { 615 // Try to move the new child into the peer's process group. 616 try { 617 Os.setpgid(pid, Os.getpgid(peer.getPid())); 618 } catch (ErrnoException ex) { 619 // This exception is expected in the case where 620 // the peer is not in our session 621 // TODO get rid of this log message in the case where 622 // getsid(0) != getsid(peer.getPid()) 623 Log.i(TAG, "Zygote: setpgid failed. This is " 624 + "normal if peer is not in our session"); 625 } 626 } 627 } 628