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 262 try { 263 if (pid == 0) { 264 // in child 265 zygoteServer.setForkChild(); 266 267 zygoteServer.closeServerSocket(); 268 IoUtils.closeQuietly(serverPipeFd); 269 serverPipeFd = null; 270 271 return handleChildProc(parsedArgs, childPipeFd, parsedArgs.mStartChildZygote); 272 } else { 273 // In the parent. A pid < 0 indicates a failure and will be handled in 274 // handleParentProc. 275 IoUtils.closeQuietly(childPipeFd); 276 childPipeFd = null; 277 handleParentProc(pid, serverPipeFd); 278 return null; 279 } 280 } finally { 281 IoUtils.closeQuietly(childPipeFd); 282 IoUtils.closeQuietly(serverPipeFd); 283 } 284 } 285 handleAbiListQuery()286 private void handleAbiListQuery() { 287 try { 288 final byte[] abiListBytes = abiList.getBytes(StandardCharsets.US_ASCII); 289 mSocketOutStream.writeInt(abiListBytes.length); 290 mSocketOutStream.write(abiListBytes); 291 } catch (IOException ioe) { 292 throw new IllegalStateException("Error writing to command socket", ioe); 293 } 294 } 295 handlePidQuery()296 private void handlePidQuery() { 297 try { 298 String pidString = String.valueOf(Process.myPid()); 299 final byte[] pidStringBytes = pidString.getBytes(StandardCharsets.US_ASCII); 300 mSocketOutStream.writeInt(pidStringBytes.length); 301 mSocketOutStream.write(pidStringBytes); 302 } catch (IOException ioe) { 303 throw new IllegalStateException("Error writing to command socket", ioe); 304 } 305 } 306 handleBootCompleted()307 private void handleBootCompleted() { 308 try { 309 mSocketOutStream.writeInt(0); 310 } catch (IOException ioe) { 311 throw new IllegalStateException("Error writing to command socket", ioe); 312 } 313 314 VMRuntime.bootCompleted(); 315 } 316 317 /** 318 * Preloads resources if the zygote is in lazily preload mode. Writes the result of the 319 * preload operation; {@code 0} when a preload was initiated due to this request and {@code 1} 320 * if no preload was initiated. The latter implies that the zygote is not configured to load 321 * resources lazy or that the zygote has already handled a previous request to handlePreload. 322 */ handlePreload()323 private void handlePreload() { 324 try { 325 if (isPreloadComplete()) { 326 mSocketOutStream.writeInt(1); 327 } else { 328 preload(); 329 mSocketOutStream.writeInt(0); 330 } 331 } catch (IOException ioe) { 332 throw new IllegalStateException("Error writing to command socket", ioe); 333 } 334 } 335 stateChangeWithUsapPoolReset(ZygoteServer zygoteServer, Runnable stateChangeCode)336 private Runnable stateChangeWithUsapPoolReset(ZygoteServer zygoteServer, 337 Runnable stateChangeCode) { 338 try { 339 if (zygoteServer.isUsapPoolEnabled()) { 340 Log.i(TAG, "Emptying USAP Pool due to state change."); 341 Zygote.emptyUsapPool(); 342 } 343 344 stateChangeCode.run(); 345 346 if (zygoteServer.isUsapPoolEnabled()) { 347 Runnable fpResult = 348 zygoteServer.fillUsapPool( 349 new int[]{mSocket.getFileDescriptor().getInt$()}, false); 350 351 if (fpResult != null) { 352 zygoteServer.setForkChild(); 353 return fpResult; 354 } else { 355 Log.i(TAG, "Finished refilling USAP Pool after state change."); 356 } 357 } 358 359 mSocketOutStream.writeInt(0); 360 361 return null; 362 } catch (IOException ioe) { 363 throw new IllegalStateException("Error writing to command socket", ioe); 364 } 365 } 366 367 /** 368 * Makes the necessary changes to implement a new API blacklist exemption policy, and then 369 * responds to the system server, letting it know that the task has been completed. 370 * 371 * This necessitates a change to the internal state of the Zygote. As such, if the USAP 372 * pool is enabled all existing USAPs have an incorrect API blacklist exemption list. To 373 * properly handle this request the pool must be emptied and refilled. This process can return 374 * a Runnable object that must be returned to ZygoteServer.runSelectLoop to be invoked. 375 * 376 * @param zygoteServer The server object that received the request 377 * @param exemptions The new exemption list. 378 * @return A Runnable object representing a new app in any USAPs spawned from here; the 379 * zygote process will always receive a null value from this function. 380 */ handleApiBlacklistExemptions(ZygoteServer zygoteServer, String[] exemptions)381 private Runnable handleApiBlacklistExemptions(ZygoteServer zygoteServer, String[] exemptions) { 382 return stateChangeWithUsapPoolReset(zygoteServer, 383 () -> ZygoteInit.setApiBlacklistExemptions(exemptions)); 384 } 385 handleUsapPoolStatusChange(ZygoteServer zygoteServer, boolean newStatus)386 private Runnable handleUsapPoolStatusChange(ZygoteServer zygoteServer, boolean newStatus) { 387 try { 388 Runnable fpResult = zygoteServer.setUsapPoolStatus(newStatus, mSocket); 389 390 if (fpResult == null) { 391 mSocketOutStream.writeInt(0); 392 } else { 393 zygoteServer.setForkChild(); 394 } 395 396 return fpResult; 397 } catch (IOException ioe) { 398 throw new IllegalStateException("Error writing to command socket", ioe); 399 } 400 } 401 402 /** 403 * Changes the API access log sample rate for the Zygote and processes spawned from it. 404 * 405 * This necessitates a change to the internal state of the Zygote. As such, if the USAP 406 * pool is enabled all existing USAPs have an incorrect API access log sample rate. To 407 * properly handle this request the pool must be emptied and refilled. This process can return 408 * a Runnable object that must be returned to ZygoteServer.runSelectLoop to be invoked. 409 * 410 * @param zygoteServer The server object that received the request 411 * @param samplingRate The new sample rate for regular logging 412 * @param statsdSamplingRate The new sample rate for statslog logging 413 * @return A Runnable object representing a new app in any blastulas spawned from here; the 414 * zygote process will always receive a null value from this function. 415 */ handleHiddenApiAccessLogSampleRate(ZygoteServer zygoteServer, int samplingRate, int statsdSamplingRate)416 private Runnable handleHiddenApiAccessLogSampleRate(ZygoteServer zygoteServer, 417 int samplingRate, int statsdSamplingRate) { 418 return stateChangeWithUsapPoolReset(zygoteServer, () -> { 419 int maxSamplingRate = Math.max(samplingRate, statsdSamplingRate); 420 ZygoteInit.setHiddenApiAccessLogSampleRate(maxSamplingRate); 421 StatsdHiddenApiUsageLogger.setHiddenApiAccessLogSampleRates( 422 samplingRate, statsdSamplingRate); 423 ZygoteInit.setHiddenApiUsageLogger(StatsdHiddenApiUsageLogger.getInstance()); 424 }); 425 } 426 preload()427 protected void preload() { 428 ZygoteInit.lazyPreload(); 429 } 430 isPreloadComplete()431 protected boolean isPreloadComplete() { 432 return ZygoteInit.isPreloadComplete(); 433 } 434 getSocketOutputStream()435 protected DataOutputStream getSocketOutputStream() { 436 return mSocketOutStream; 437 } 438 handlePreloadPackage(String packagePath, String libsPath, String libFileName, String cacheKey)439 protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName, 440 String cacheKey) { 441 throw new RuntimeException("Zygote does not support package preloading"); 442 } 443 canPreloadApp()444 protected boolean canPreloadApp() { 445 return false; 446 } 447 handlePreloadApp(ApplicationInfo aInfo)448 protected void handlePreloadApp(ApplicationInfo aInfo) { 449 throw new RuntimeException("Zygote does not support app preloading"); 450 } 451 452 /** 453 * Closes socket associated with this connection. 454 */ 455 @UnsupportedAppUsage closeSocket()456 void closeSocket() { 457 try { 458 mSocket.close(); 459 } catch (IOException ex) { 460 Log.e(TAG, "Exception while closing command " 461 + "socket in parent", ex); 462 } 463 } 464 isClosedByPeer()465 boolean isClosedByPeer() { 466 return isEof; 467 } 468 469 /** 470 * Handles post-fork setup of child proc, closing sockets as appropriate, 471 * reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller 472 * if successful or returning if failed. 473 * 474 * @param parsedArgs non-null; zygote args 475 * @param pipeFd null-ok; pipe for communication back to Zygote. 476 * @param isZygote whether this new child process is itself a new Zygote. 477 */ handleChildProc(ZygoteArguments parsedArgs, FileDescriptor pipeFd, boolean isZygote)478 private Runnable handleChildProc(ZygoteArguments parsedArgs, 479 FileDescriptor pipeFd, boolean isZygote) { 480 /* 481 * By the time we get here, the native code has closed the two actual Zygote 482 * socket connections, and substituted /dev/null in their place. The LocalSocket 483 * objects still need to be closed properly. 484 */ 485 486 closeSocket(); 487 488 Zygote.setAppProcessName(parsedArgs, TAG); 489 490 // End of the postFork event. 491 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 492 if (parsedArgs.mInvokeWith != null) { 493 WrapperInit.execApplication(parsedArgs.mInvokeWith, 494 parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, 495 VMRuntime.getCurrentInstructionSet(), 496 pipeFd, parsedArgs.mRemainingArgs); 497 498 // Should not get here. 499 throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned"); 500 } else { 501 if (!isZygote) { 502 return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, 503 parsedArgs.mDisabledCompatChanges, 504 parsedArgs.mRemainingArgs, null /* classLoader */); 505 } else { 506 return ZygoteInit.childZygoteInit(parsedArgs.mTargetSdkVersion, 507 parsedArgs.mRemainingArgs, null /* classLoader */); 508 } 509 } 510 } 511 512 /** 513 * Handles post-fork cleanup of parent proc 514 * 515 * @param pid != 0; pid of child if > 0 or indication of failed fork 516 * if < 0; 517 * @param pipeFd null-ok; pipe for communication with child. 518 */ handleParentProc(int pid, FileDescriptor pipeFd)519 private void handleParentProc(int pid, FileDescriptor pipeFd) { 520 if (pid > 0) { 521 setChildPgid(pid); 522 } 523 524 boolean usingWrapper = false; 525 if (pipeFd != null && pid > 0) { 526 int innerPid = -1; 527 try { 528 // Do a busy loop here. We can't guarantee that a failure (and thus an exception 529 // bail) happens in a timely manner. 530 final int BYTES_REQUIRED = 4; // Bytes in an int. 531 532 StructPollfd[] fds = new StructPollfd[] { 533 new StructPollfd() 534 }; 535 536 byte[] data = new byte[BYTES_REQUIRED]; 537 538 int remainingSleepTime = WRAPPED_PID_TIMEOUT_MILLIS; 539 int dataIndex = 0; 540 long startTime = System.nanoTime(); 541 542 while (dataIndex < data.length && remainingSleepTime > 0) { 543 fds[0].fd = pipeFd; 544 fds[0].events = (short) POLLIN; 545 fds[0].revents = 0; 546 fds[0].userData = null; 547 548 int res = android.system.Os.poll(fds, remainingSleepTime); 549 long endTime = System.nanoTime(); 550 int elapsedTimeMs = 551 (int) TimeUnit.MILLISECONDS.convert( 552 endTime - startTime, 553 TimeUnit.NANOSECONDS); 554 remainingSleepTime = WRAPPED_PID_TIMEOUT_MILLIS - elapsedTimeMs; 555 556 if (res > 0) { 557 if ((fds[0].revents & POLLIN) != 0) { 558 // Only read one byte, so as not to block. 559 int readBytes = android.system.Os.read(pipeFd, data, dataIndex, 1); 560 if (readBytes < 0) { 561 throw new RuntimeException("Some error"); 562 } 563 dataIndex += readBytes; 564 } else { 565 // Error case. revents should contain one of the error bits. 566 break; 567 } 568 } else if (res == 0) { 569 Log.w(TAG, "Timed out waiting for child."); 570 } 571 } 572 573 if (dataIndex == data.length) { 574 DataInputStream is = new DataInputStream(new ByteArrayInputStream(data)); 575 innerPid = is.readInt(); 576 } 577 578 if (innerPid == -1) { 579 Log.w(TAG, "Error reading pid from wrapped process, child may have died"); 580 } 581 } catch (Exception ex) { 582 Log.w(TAG, "Error reading pid from wrapped process, child may have died", ex); 583 } 584 585 // Ensure that the pid reported by the wrapped process is either the 586 // child process that we forked, or a descendant of it. 587 if (innerPid > 0) { 588 int parentPid = innerPid; 589 while (parentPid > 0 && parentPid != pid) { 590 parentPid = Process.getParentPid(parentPid); 591 } 592 if (parentPid > 0) { 593 Log.i(TAG, "Wrapped process has pid " + innerPid); 594 pid = innerPid; 595 usingWrapper = true; 596 } else { 597 Log.w(TAG, "Wrapped process reported a pid that is not a child of " 598 + "the process that we forked: childPid=" + pid 599 + " innerPid=" + innerPid); 600 } 601 } 602 } 603 604 try { 605 mSocketOutStream.writeInt(pid); 606 mSocketOutStream.writeBoolean(usingWrapper); 607 } catch (IOException ex) { 608 throw new IllegalStateException("Error writing to command socket", ex); 609 } 610 } 611 setChildPgid(int pid)612 private void setChildPgid(int pid) { 613 // Try to move the new child into the peer's process group. 614 try { 615 Os.setpgid(pid, Os.getpgid(peer.getPid())); 616 } catch (ErrnoException ex) { 617 // This exception is expected in the case where 618 // the peer is not in our session 619 // TODO get rid of this log message in the case where 620 // getsid(0) != getsid(peer.getPid()) 621 Log.i(TAG, "Zygote: setpgid failed. This is " 622 + "normal if peer is not in our session"); 623 } 624 } 625 } 626