1 /* 2 * Copyright (C) 2021 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 android.system.virtualmachine; 18 19 import static android.os.ParcelFileDescriptor.AutoCloseInputStream; 20 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; 21 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; 22 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_CHANGED; 23 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_INVALID_CONFIG; 24 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_VERIFICATION_FAILED; 25 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_UNKNOWN; 26 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_CRASH; 27 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_HANGUP; 28 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_INFRASTRUCTURE_ERROR; 29 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_KILLED; 30 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE; 31 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG; 32 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED; 33 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED; 34 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR; 35 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED; 36 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH; 37 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_REBOOT; 38 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_SHUTDOWN; 39 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_START_FAILED; 40 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_UNKNOWN; 41 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_VIRTUALIZATION_SERVICE_DIED; 42 43 import static java.util.Objects.requireNonNull; 44 45 import android.annotation.CallbackExecutor; 46 import android.annotation.FlaggedApi; 47 import android.annotation.IntDef; 48 import android.annotation.IntRange; 49 import android.annotation.NonNull; 50 import android.annotation.Nullable; 51 import android.annotation.RequiresPermission; 52 import android.annotation.SuppressLint; 53 import android.annotation.SystemApi; 54 import android.annotation.TestApi; 55 import android.annotation.WorkerThread; 56 import android.content.ComponentCallbacks2; 57 import android.content.Context; 58 import android.content.pm.ApplicationInfo; 59 import android.content.pm.PackageManager; 60 import android.content.res.Configuration; 61 import android.os.Binder; 62 import android.os.IBinder; 63 import android.os.ParcelFileDescriptor; 64 import android.os.RemoteException; 65 import android.os.ServiceSpecificException; 66 import android.system.ErrnoException; 67 import android.system.OsConstants; 68 import android.system.virtualizationcommon.DeathReason; 69 import android.system.virtualizationcommon.ErrorCode; 70 import android.system.virtualizationservice.IVirtualMachine; 71 import android.system.virtualizationservice.IVirtualMachineCallback; 72 import android.system.virtualizationservice.IVirtualizationService; 73 import android.system.virtualizationservice.InputDevice; 74 import android.system.virtualizationservice.MemoryTrimLevel; 75 import android.system.virtualizationservice.PartitionType; 76 import android.system.virtualizationservice.VirtualMachineAppConfig; 77 import android.system.virtualizationservice.VirtualMachineRawConfig; 78 import android.system.virtualizationservice.VirtualMachineState; 79 import android.util.JsonReader; 80 import android.util.Log; 81 import android.view.KeyEvent; 82 import android.view.MotionEvent; 83 84 import com.android.internal.annotations.GuardedBy; 85 import com.android.system.virtualmachine.flags.Flags; 86 87 import libcore.io.IoBridge; 88 import libcore.io.IoUtils; 89 90 import java.io.File; 91 import java.io.FileDescriptor; 92 import java.io.FileInputStream; 93 import java.io.FileNotFoundException; 94 import java.io.FileOutputStream; 95 import java.io.IOException; 96 import java.io.InputStream; 97 import java.io.InputStreamReader; 98 import java.io.OutputStream; 99 import java.lang.annotation.Retention; 100 import java.lang.annotation.RetentionPolicy; 101 import java.nio.ByteBuffer; 102 import java.nio.ByteOrder; 103 import java.nio.channels.FileChannel; 104 import java.nio.charset.StandardCharsets; 105 import java.nio.file.FileAlreadyExistsException; 106 import java.nio.file.FileVisitResult; 107 import java.nio.file.Files; 108 import java.nio.file.Path; 109 import java.nio.file.SimpleFileVisitor; 110 import java.nio.file.attribute.BasicFileAttributes; 111 import java.util.ArrayList; 112 import java.util.Arrays; 113 import java.util.Collection; 114 import java.util.Collections; 115 import java.util.List; 116 import java.util.concurrent.Executor; 117 import java.util.concurrent.Executors; 118 import java.util.concurrent.atomic.AtomicBoolean; 119 import java.util.function.Consumer; 120 import java.util.zip.ZipFile; 121 122 /** 123 * Represents an VM instance, with its own configuration and state. Instances are persistent and are 124 * created or retrieved via {@link VirtualMachineManager}. 125 * 126 * <p>The {@link #run} method actually starts up the VM and allows the payload code to execute. It 127 * will continue until it exits or {@link #stop} is called. Updates on the state of the VM can be 128 * received using {@link #setCallback}. The app can communicate with the VM using {@link 129 * #connectToVsockServer} or {@link #connectVsock}. 130 * 131 * <p>The payload code running inside the VM has access to a set of native APIs; see the <a 132 * href="https://cs.android.com/android/platform/superproject/+/master:packages/modules/Virtualization/vm_payload/README.md">README 133 * file</a> for details. 134 * 135 * <p>Each VM has a unique secret, computed from the APK that contains the code running in it, the 136 * VM configuration, and a random per-instance salt. The secret can be accessed by the payload code 137 * running inside the VM (using {@code AVmPayload_getVmInstanceSecret}) but is not made available 138 * outside it. 139 * 140 * @hide 141 */ 142 @SystemApi 143 public class VirtualMachine implements AutoCloseable { 144 /** The permission needed to create or run a virtual machine. */ 145 public static final String MANAGE_VIRTUAL_MACHINE_PERMISSION = 146 "android.permission.MANAGE_VIRTUAL_MACHINE"; 147 148 /** 149 * The permission needed to create a virtual machine with more advanced configuration options. 150 */ 151 public static final String USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION = 152 "android.permission.USE_CUSTOM_VIRTUAL_MACHINE"; 153 154 /** 155 * The lowest port number that can be used to communicate with the virtual machine payload. 156 * 157 * @see #connectToVsockServer 158 * @see #connectVsock 159 */ 160 @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock. 161 public static final long MIN_VSOCK_PORT = 1024; 162 163 /** 164 * The highest port number that can be used to communicate with the virtual machine payload. 165 * 166 * @see #connectToVsockServer 167 * @see #connectVsock 168 */ 169 @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock. 170 public static final long MAX_VSOCK_PORT = (1L << 32) - 1; 171 172 private ParcelFileDescriptor mTouchSock; 173 private ParcelFileDescriptor mKeySock; 174 private ParcelFileDescriptor mMouseSock; 175 176 /** 177 * Status of a virtual machine 178 * 179 * @hide 180 */ 181 @Retention(RetentionPolicy.SOURCE) 182 @IntDef(prefix = "STATUS_", value = { 183 STATUS_STOPPED, 184 STATUS_RUNNING, 185 STATUS_DELETED 186 }) 187 public @interface Status {} 188 189 /** The virtual machine has just been created, or {@link #stop} was called on it. */ 190 public static final int STATUS_STOPPED = 0; 191 192 /** The virtual machine is running. */ 193 public static final int STATUS_RUNNING = 1; 194 195 /** 196 * The virtual machine has been deleted. This is an irreversible state. Once a virtual machine 197 * is deleted all its secrets are permanently lost, and it cannot be run. A new virtual machine 198 * with the same name and config may be created, with new and different secrets. 199 */ 200 public static final int STATUS_DELETED = 2; 201 202 private static final String TAG = "VirtualMachine"; 203 204 /** Name of the directory under the files directory where all VMs created for the app exist. */ 205 private static final String VM_DIR = "vm"; 206 207 /** Name of the persisted config file for a VM. */ 208 private static final String CONFIG_FILE = "config.xml"; 209 210 /** Name of the instance image file for a VM. (Not implemented) */ 211 private static final String INSTANCE_IMAGE_FILE = "instance.img"; 212 213 /** Name of the file for a VM containing Id. */ 214 private static final String INSTANCE_ID_FILE = "instance_id"; 215 216 /** Name of the idsig file for a VM */ 217 private static final String IDSIG_FILE = "idsig"; 218 219 /** Name of the idsig files for extra APKs. */ 220 private static final String EXTRA_IDSIG_FILE_PREFIX = "extra_idsig_"; 221 222 /** Size of the instance image. 10 MB. */ 223 private static final long INSTANCE_FILE_SIZE = 10 * 1024 * 1024; 224 225 /** Name of the file backing the encrypted storage */ 226 private static final String ENCRYPTED_STORE_FILE = "storage.img"; 227 228 /** The package which owns this VM. */ 229 @NonNull private final String mPackageName; 230 231 /** Name of this VM within the package. The name should be unique in the package. */ 232 @NonNull private final String mName; 233 234 /** 235 * Path to the directory containing all the files related to this VM. 236 */ 237 @NonNull private final File mVmRootPath; 238 239 /** 240 * Path to the config file for this VM. The config file is where the configuration is persisted. 241 */ 242 @NonNull private final File mConfigFilePath; 243 244 /** Path to the instance image file for this VM. */ 245 @NonNull private final File mInstanceFilePath; 246 247 /** Path to the idsig file for this VM. */ 248 @NonNull private final File mIdsigFilePath; 249 250 /** File that backs the encrypted storage - Will be null if not enabled. */ 251 @Nullable private final File mEncryptedStoreFilePath; 252 253 /** File that contains the Id. This is NULL iff FEATURE_LLPVM is disabled */ 254 @Nullable private final File mInstanceIdPath; 255 256 /** 257 * Unmodifiable list of extra apks. Apks are specified by the vm config, and corresponding 258 * idsigs are to be generated. 259 */ 260 @NonNull private final List<ExtraApkSpec> mExtraApks; 261 262 private class MemoryManagementCallbacks implements ComponentCallbacks2 { 263 @Override onConfigurationChanged(@onNull Configuration newConfig)264 public void onConfigurationChanged(@NonNull Configuration newConfig) {} 265 266 @Override onLowMemory()267 public void onLowMemory() {} 268 269 @Override onTrimMemory(int level)270 public void onTrimMemory(int level) { 271 @MemoryTrimLevel int vmTrimLevel; 272 273 switch (level) { 274 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: 275 vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_CRITICAL; 276 break; 277 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: 278 vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_LOW; 279 break; 280 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: 281 vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_MODERATE; 282 break; 283 case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: 284 case ComponentCallbacks2.TRIM_MEMORY_MODERATE: 285 case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: 286 /* Release as much memory as we can. The app is on the LMKD LRU kill list. */ 287 vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_CRITICAL; 288 break; 289 default: 290 /* Treat unrecognised messages as generic low-memory warnings. */ 291 vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_LOW; 292 break; 293 } 294 295 synchronized (mLock) { 296 try { 297 if (mVirtualMachine != null) { 298 mVirtualMachine.onTrimMemory(vmTrimLevel); 299 } 300 } catch (Exception e) { 301 /* Caller doesn't want our exceptions. Log them instead. */ 302 Log.w(TAG, "TrimMemory failed: ", e); 303 } 304 } 305 } 306 } 307 308 /** Running instance of virtmgr that hosts VirtualizationService for this VM. */ 309 @NonNull private final VirtualizationService mVirtualizationService; 310 311 @NonNull private final MemoryManagementCallbacks mMemoryManagementCallbacks; 312 313 @NonNull private final Context mContext; 314 315 // A note on lock ordering: 316 // You can take mLock while holding VirtualMachineManager.sCreateLock, but not vice versa. 317 // We never take any other lock while holding mCallbackLock; therefore you can 318 // take mCallbackLock while holding any other lock. 319 320 /** Lock protecting our mutable state (other than callbacks). */ 321 private final Object mLock = new Object(); 322 323 /** Lock protecting callbacks. */ 324 private final Object mCallbackLock = new Object(); 325 326 private final boolean mVmOutputCaptured; 327 328 private final boolean mVmConsoleInputSupported; 329 330 private final boolean mConnectVmConsole; 331 332 private final Executor mConsoleExecutor = Executors.newSingleThreadExecutor(); 333 334 /** The configuration that is currently associated with this VM. */ 335 @GuardedBy("mLock") 336 @NonNull 337 private VirtualMachineConfig mConfig; 338 339 /** Handle to the "running" VM. */ 340 @GuardedBy("mLock") 341 @Nullable 342 private IVirtualMachine mVirtualMachine; 343 344 @GuardedBy("mLock") 345 @Nullable 346 private ParcelFileDescriptor mConsoleOutReader; 347 348 @GuardedBy("mLock") 349 @Nullable 350 private ParcelFileDescriptor mConsoleOutWriter; 351 352 @GuardedBy("mLock") 353 @Nullable 354 private ParcelFileDescriptor mConsoleInReader; 355 356 @GuardedBy("mLock") 357 @Nullable 358 private ParcelFileDescriptor mConsoleInWriter; 359 360 @GuardedBy("mLock") 361 @Nullable 362 private ParcelFileDescriptor mTeeConsoleOutReader; 363 364 @GuardedBy("mLock") 365 @Nullable 366 private ParcelFileDescriptor mTeeConsoleOutWriter; 367 368 @GuardedBy("mLock") 369 @Nullable 370 private ParcelFileDescriptor mPtyFd; 371 372 @GuardedBy("mLock") 373 @Nullable 374 private ParcelFileDescriptor mPtsFd; 375 376 @GuardedBy("mLock") 377 @Nullable 378 private String mPtsName; 379 380 @GuardedBy("mLock") 381 @Nullable 382 private ParcelFileDescriptor mLogReader; 383 384 @GuardedBy("mLock") 385 @Nullable 386 private ParcelFileDescriptor mLogWriter; 387 388 @GuardedBy("mLock") 389 private boolean mWasDeleted = false; 390 391 /** The registered callback */ 392 @GuardedBy("mCallbackLock") 393 @Nullable 394 private VirtualMachineCallback mCallback; 395 396 /** The executor on which the callback will be executed */ 397 @GuardedBy("mCallbackLock") 398 @Nullable 399 private Executor mCallbackExecutor; 400 401 private static class ExtraApkSpec { 402 public final File apk; 403 public final File idsig; 404 ExtraApkSpec(File apk, File idsig)405 ExtraApkSpec(File apk, File idsig) { 406 this.apk = apk; 407 this.idsig = idsig; 408 } 409 } 410 411 static { 412 System.loadLibrary("virtualmachine_jni"); 413 } 414 VirtualMachine( @onNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config, @NonNull VirtualizationService service)415 private VirtualMachine( 416 @NonNull Context context, 417 @NonNull String name, 418 @NonNull VirtualMachineConfig config, 419 @NonNull VirtualizationService service) 420 throws VirtualMachineException { 421 mPackageName = context.getPackageName(); 422 mName = requireNonNull(name, "Name must not be null"); 423 mConfig = requireNonNull(config, "Config must not be null"); 424 mVirtualizationService = service; 425 426 File thisVmDir = getVmDir(context, mName); 427 mVmRootPath = thisVmDir; 428 mConfigFilePath = new File(thisVmDir, CONFIG_FILE); 429 try { 430 mInstanceIdPath = 431 (mVirtualizationService 432 .getBinder() 433 .isFeatureEnabled(IVirtualizationService.FEATURE_LLPVM_CHANGES)) 434 ? new File(thisVmDir, INSTANCE_ID_FILE) 435 : null; 436 } catch (RemoteException e) { 437 throw e.rethrowAsRuntimeException(); 438 } 439 mInstanceFilePath = new File(thisVmDir, INSTANCE_IMAGE_FILE); 440 mIdsigFilePath = new File(thisVmDir, IDSIG_FILE); 441 mExtraApks = setupExtraApks(context, config, thisVmDir); 442 mMemoryManagementCallbacks = new MemoryManagementCallbacks(); 443 mContext = context; 444 mEncryptedStoreFilePath = 445 (config.isEncryptedStorageEnabled()) 446 ? new File(thisVmDir, ENCRYPTED_STORE_FILE) 447 : null; 448 449 mVmOutputCaptured = config.isVmOutputCaptured(); 450 mVmConsoleInputSupported = config.isVmConsoleInputSupported(); 451 mConnectVmConsole = config.isConnectVmConsole(); 452 } 453 454 /** 455 * Creates a virtual machine from an {@link VirtualMachineDescriptor} object and associates it 456 * with the given name. 457 * 458 * <p>The new virtual machine will be in the same state as the descriptor indicates. 459 * 460 * <p>Once a virtual machine is imported it is persisted until it is deleted by calling {@link 461 * #delete}. The imported virtual machine is in {@link #STATUS_STOPPED} state. To run the VM, 462 * call {@link #run}. 463 */ 464 @GuardedBy("VirtualMachineManager.sCreateLock") 465 @NonNull fromDescriptor( @onNull Context context, @NonNull String name, @NonNull VirtualMachineDescriptor vmDescriptor)466 static VirtualMachine fromDescriptor( 467 @NonNull Context context, 468 @NonNull String name, 469 @NonNull VirtualMachineDescriptor vmDescriptor) 470 throws VirtualMachineException { 471 File vmDir = createVmDir(context, name); 472 try { 473 VirtualMachine vm; 474 try (vmDescriptor) { 475 VirtualMachineConfig config = VirtualMachineConfig.from(vmDescriptor.getConfigFd()); 476 vm = new VirtualMachine(context, name, config, VirtualizationService.getInstance()); 477 config.serialize(vm.mConfigFilePath); 478 try { 479 vm.mInstanceFilePath.createNewFile(); 480 } catch (IOException e) { 481 throw new VirtualMachineException("failed to create instance image", e); 482 } 483 vm.importInstanceFrom(vmDescriptor.getInstanceImgFd()); 484 485 if (vmDescriptor.getEncryptedStoreFd() != null) { 486 try { 487 vm.mEncryptedStoreFilePath.createNewFile(); 488 } catch (IOException e) { 489 throw new VirtualMachineException( 490 "failed to create encrypted storage image", e); 491 } 492 vm.importEncryptedStoreFrom(vmDescriptor.getEncryptedStoreFd()); 493 } 494 if (vm.mInstanceIdPath != null) { 495 vm.importInstanceIdFrom(vmDescriptor.getInstanceIdFd()); 496 vm.claimInstance(); 497 } 498 } 499 return vm; 500 } catch (VirtualMachineException | RuntimeException e) { 501 // If anything goes wrong, delete any files created so far and the VM's directory 502 try { 503 deleteRecursively(vmDir); 504 } catch (Exception innerException) { 505 e.addSuppressed(innerException); 506 } 507 throw e; 508 } 509 } 510 511 /** 512 * Creates a virtual machine with the given name and config. Once a virtual machine is created 513 * it is persisted until it is deleted by calling {@link #delete}. The created virtual machine 514 * is in {@link #STATUS_STOPPED} state. To run the VM, call {@link #run}. 515 */ 516 @GuardedBy("VirtualMachineManager.sCreateLock") 517 @NonNull create( @onNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)518 static VirtualMachine create( 519 @NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config) 520 throws VirtualMachineException { 521 File vmDir = createVmDir(context, name); 522 523 try { 524 VirtualMachine vm = 525 new VirtualMachine(context, name, config, VirtualizationService.getInstance()); 526 config.serialize(vm.mConfigFilePath); 527 try { 528 vm.mInstanceFilePath.createNewFile(); 529 } catch (IOException e) { 530 throw new VirtualMachineException("failed to create instance image", e); 531 } 532 if (config.isEncryptedStorageEnabled()) { 533 try { 534 vm.mEncryptedStoreFilePath.createNewFile(); 535 } catch (IOException e) { 536 throw new VirtualMachineException( 537 "failed to create encrypted storage image", e); 538 } 539 } 540 541 IVirtualizationService service = vm.mVirtualizationService.getBinder(); 542 543 if (vm.mInstanceIdPath != null) { 544 try (FileOutputStream stream = new FileOutputStream(vm.mInstanceIdPath)) { 545 byte[] id = service.allocateInstanceId(); 546 stream.write(id); 547 } catch (FileNotFoundException e) { 548 throw new VirtualMachineException("instance_id file missing", e); 549 } catch (IOException e) { 550 throw new VirtualMachineException("failed to persist instance_id", e); 551 } catch (RemoteException e) { 552 throw e.rethrowAsRuntimeException(); 553 } catch (ServiceSpecificException | IllegalArgumentException e) { 554 throw new VirtualMachineException("failed to create instance_id", e); 555 } 556 } 557 558 try { 559 service.initializeWritablePartition( 560 ParcelFileDescriptor.open(vm.mInstanceFilePath, MODE_READ_WRITE), 561 INSTANCE_FILE_SIZE, 562 PartitionType.ANDROID_VM_INSTANCE); 563 } catch (FileNotFoundException e) { 564 throw new VirtualMachineException("instance image missing", e); 565 } catch (RemoteException e) { 566 throw e.rethrowAsRuntimeException(); 567 } catch (ServiceSpecificException | IllegalArgumentException e) { 568 throw new VirtualMachineException("failed to create instance partition", e); 569 } 570 571 if (config.isEncryptedStorageEnabled()) { 572 try { 573 service.initializeWritablePartition( 574 ParcelFileDescriptor.open(vm.mEncryptedStoreFilePath, MODE_READ_WRITE), 575 config.getEncryptedStorageBytes(), 576 PartitionType.ENCRYPTEDSTORE); 577 } catch (FileNotFoundException e) { 578 throw new VirtualMachineException("encrypted storage image missing", e); 579 } catch (RemoteException e) { 580 throw e.rethrowAsRuntimeException(); 581 } catch (ServiceSpecificException | IllegalArgumentException e) { 582 throw new VirtualMachineException( 583 "failed to create encrypted storage partition", e); 584 } 585 } 586 return vm; 587 } catch (VirtualMachineException | RuntimeException e) { 588 // If anything goes wrong, delete any files created so far and the VM's directory 589 try { 590 vmInstanceCleanup(context, name); 591 } catch (Exception innerException) { 592 e.addSuppressed(innerException); 593 } 594 throw e; 595 } 596 } 597 598 /** Loads a virtual machine that is already created before. */ 599 @GuardedBy("VirtualMachineManager.sCreateLock") 600 @Nullable load(@onNull Context context, @NonNull String name)601 static VirtualMachine load(@NonNull Context context, @NonNull String name) 602 throws VirtualMachineException { 603 File thisVmDir = getVmDir(context, name); 604 if (!thisVmDir.exists()) { 605 // The VM doesn't exist. 606 return null; 607 } 608 File configFilePath = new File(thisVmDir, CONFIG_FILE); 609 VirtualMachineConfig config = VirtualMachineConfig.from(configFilePath); 610 VirtualMachine vm = 611 new VirtualMachine(context, name, config, VirtualizationService.getInstance()); 612 613 if (vm.mInstanceIdPath != null && !vm.mInstanceIdPath.exists()) { 614 throw new VirtualMachineException("instance_id file missing"); 615 } 616 if (!vm.mInstanceFilePath.exists()) { 617 throw new VirtualMachineException("instance image missing"); 618 } 619 if (config.isEncryptedStorageEnabled() && !vm.mEncryptedStoreFilePath.exists()) { 620 throw new VirtualMachineException("Storage image missing"); 621 } 622 return vm; 623 } 624 625 @GuardedBy("VirtualMachineManager.sCreateLock") delete(Context context, String name)626 void delete(Context context, String name) throws VirtualMachineException { 627 synchronized (mLock) { 628 checkStopped(); 629 // Once we explicitly delete a VM it must remain permanently in the deleted state; 630 // if a new VM is created with the same name (and files) that's unrelated. 631 mWasDeleted = true; 632 } 633 vmInstanceCleanup(context, name); 634 } 635 636 // Delete the full VM directory and notify VirtualizationService to remove this 637 // VM instance for housekeeping. 638 @GuardedBy("VirtualMachineManager.sCreateLock") vmInstanceCleanup(Context context, String name)639 static void vmInstanceCleanup(Context context, String name) throws VirtualMachineException { 640 File vmDir = getVmDir(context, name); 641 notifyInstanceRemoval(vmDir, VirtualizationService.getInstance()); 642 try { 643 deleteRecursively(vmDir); 644 } catch (IOException e) { 645 throw new VirtualMachineException(e); 646 } 647 } 648 notifyInstanceRemoval( File vmDirectory, @NonNull VirtualizationService service)649 private static void notifyInstanceRemoval( 650 File vmDirectory, @NonNull VirtualizationService service) { 651 File instanceIdFile = new File(vmDirectory, INSTANCE_ID_FILE); 652 try { 653 byte[] instanceId = Files.readAllBytes(instanceIdFile.toPath()); 654 service.getBinder().removeVmInstance(instanceId); 655 } catch (Exception e) { 656 // Deliberately ignoring error in removing VM instance. This potentially leads to 657 // unaccounted instances in the VS' database. But, nothing much can be done by caller. 658 Log.w(TAG, "Failed to notify VS to remove the VM instance", e); 659 } 660 } 661 662 // Claim the instance. This notifies the global VS about the ownership of this 663 // instance_id for housekeeping purpose. claimInstance()664 void claimInstance() throws VirtualMachineException { 665 if (mInstanceIdPath != null) { 666 IVirtualizationService service = mVirtualizationService.getBinder(); 667 try { 668 byte[] instanceId = Files.readAllBytes(mInstanceIdPath.toPath()); 669 service.claimVmInstance(instanceId); 670 } 671 catch (IOException e) { 672 throw new VirtualMachineException("failed to read instance_id", e); 673 } catch (RemoteException e) { 674 throw e.rethrowAsRuntimeException(); 675 } 676 } 677 } 678 679 @GuardedBy("VirtualMachineManager.sCreateLock") 680 @NonNull createVmDir(@onNull Context context, @NonNull String name)681 private static File createVmDir(@NonNull Context context, @NonNull String name) 682 throws VirtualMachineException { 683 File vmDir = getVmDir(context, name); 684 try { 685 // We don't need to undo this even if VM creation fails. 686 Files.createDirectories(vmDir.getParentFile().toPath()); 687 688 // The checking of the existence of this directory and the creation of it is done 689 // atomically. If the directory already exists (i.e. the VM with the same name was 690 // already created), FileAlreadyExistsException is thrown. 691 Files.createDirectory(vmDir.toPath()); 692 } catch (FileAlreadyExistsException e) { 693 throw new VirtualMachineException("virtual machine already exists", e); 694 } catch (IOException e) { 695 throw new VirtualMachineException("failed to create directory for VM", e); 696 } 697 return vmDir; 698 } 699 700 @NonNull getVmDir(@onNull Context context, @NonNull String name)701 private static File getVmDir(@NonNull Context context, @NonNull String name) { 702 if (name.contains(File.separator) || name.equals(".") || name.equals("..")) { 703 throw new IllegalArgumentException("Invalid VM name: " + name); 704 } 705 File vmRoot = new File(context.getDataDir(), VM_DIR); 706 return new File(vmRoot, name); 707 } 708 709 /** 710 * Returns the name of this virtual machine. The name is unique in the package and can't be 711 * changed. 712 * 713 * @hide 714 */ 715 @SystemApi 716 @NonNull getName()717 public String getName() { 718 return mName; 719 } 720 721 /** 722 * Returns the currently selected config of this virtual machine. There can be multiple virtual 723 * machines sharing the same config. Even in that case, the virtual machines are completely 724 * isolated from each other; they have different secrets. It is also possible that a virtual 725 * machine can change its config, which can be done by calling {@link #setConfig}. 726 * 727 * <p>NOTE: This method may block and should not be called on the main thread. 728 * 729 * @hide 730 */ 731 @SystemApi 732 @WorkerThread 733 @NonNull getConfig()734 public VirtualMachineConfig getConfig() { 735 synchronized (mLock) { 736 return mConfig; 737 } 738 } 739 740 /** 741 * Returns the current status of this virtual machine. 742 * 743 * <p>NOTE: This method may block and should not be called on the main thread. 744 * 745 * @hide 746 */ 747 @SystemApi 748 @WorkerThread 749 @Status getStatus()750 public int getStatus() { 751 IVirtualMachine virtualMachine; 752 synchronized (mLock) { 753 if (mWasDeleted) { 754 return STATUS_DELETED; 755 } 756 virtualMachine = mVirtualMachine; 757 } 758 759 int status; 760 if (virtualMachine == null) { 761 status = STATUS_STOPPED; 762 } else { 763 try { 764 status = stateToStatus(virtualMachine.getState()); 765 } catch (RemoteException e) { 766 throw e.rethrowAsRuntimeException(); 767 } 768 } 769 if (status == STATUS_STOPPED && !mVmRootPath.exists()) { 770 // A VM can quite happily keep running if its backing files have been deleted. 771 // But once it stops, it's gone forever. 772 synchronized (mLock) { 773 dropVm(); 774 } 775 return STATUS_DELETED; 776 } 777 return status; 778 } 779 stateToStatus(@irtualMachineState int state)780 private int stateToStatus(@VirtualMachineState int state) { 781 switch (state) { 782 case VirtualMachineState.STARTING: 783 case VirtualMachineState.STARTED: 784 case VirtualMachineState.READY: 785 case VirtualMachineState.FINISHED: 786 return STATUS_RUNNING; 787 case VirtualMachineState.NOT_STARTED: 788 case VirtualMachineState.DEAD: 789 default: 790 return STATUS_STOPPED; 791 } 792 } 793 794 // Throw an appropriate exception if we have a running VM, or the VM has been deleted. 795 @GuardedBy("mLock") checkStopped()796 private void checkStopped() throws VirtualMachineException { 797 if (mWasDeleted || !mVmRootPath.exists()) { 798 throw new VirtualMachineException("VM has been deleted"); 799 } 800 if (mVirtualMachine == null) { 801 return; 802 } 803 try { 804 if (stateToStatus(mVirtualMachine.getState()) != STATUS_STOPPED) { 805 throw new VirtualMachineException("VM is not in stopped state"); 806 } 807 } catch (RemoteException e) { 808 throw e.rethrowAsRuntimeException(); 809 } 810 // It's stopped, but we still have a reference to it - we can fix that. 811 dropVm(); 812 } 813 814 /** 815 * This should only be called when we know our VM has stopped; we no longer need to hold a 816 * reference to it (this allows resources to be GC'd) and we no longer need to be informed of 817 * memory pressure. 818 */ 819 @GuardedBy("mLock") dropVm()820 private void dropVm() { 821 mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks); 822 mVirtualMachine = null; 823 } 824 825 /** If we have an IVirtualMachine in the running state return it, otherwise throw. */ 826 @GuardedBy("mLock") getRunningVm()827 private IVirtualMachine getRunningVm() throws VirtualMachineException { 828 try { 829 if (mVirtualMachine != null 830 && stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) { 831 return mVirtualMachine; 832 } else { 833 if (mWasDeleted || !mVmRootPath.exists()) { 834 throw new VirtualMachineException("VM has been deleted"); 835 } else { 836 throw new VirtualMachineException("VM is not in running state"); 837 } 838 } 839 } catch (RemoteException e) { 840 throw e.rethrowAsRuntimeException(); 841 } 842 } 843 844 /** 845 * Registers the callback object to get events from the virtual machine. If a callback was 846 * already registered, it is replaced with the new one. 847 * 848 * @hide 849 */ 850 @SystemApi setCallback( @onNull @allbackExecutor Executor executor, @NonNull VirtualMachineCallback callback)851 public void setCallback( 852 @NonNull @CallbackExecutor Executor executor, 853 @NonNull VirtualMachineCallback callback) { 854 synchronized (mCallbackLock) { 855 mCallback = callback; 856 mCallbackExecutor = executor; 857 } 858 } 859 860 /** 861 * Clears the currently registered callback. 862 * 863 * @hide 864 */ 865 @SystemApi clearCallback()866 public void clearCallback() { 867 synchronized (mCallbackLock) { 868 mCallback = null; 869 mCallbackExecutor = null; 870 } 871 } 872 873 /** Executes a callback on the callback executor. */ executeCallback(Consumer<VirtualMachineCallback> fn)874 private void executeCallback(Consumer<VirtualMachineCallback> fn) { 875 final VirtualMachineCallback callback; 876 final Executor executor; 877 synchronized (mCallbackLock) { 878 callback = mCallback; 879 executor = mCallbackExecutor; 880 } 881 if (callback == null || executor == null) { 882 return; 883 } 884 final long restoreToken = Binder.clearCallingIdentity(); 885 try { 886 executor.execute(() -> fn.accept(callback)); 887 } finally { 888 Binder.restoreCallingIdentity(restoreToken); 889 } 890 } 891 892 private android.system.virtualizationservice.VirtualMachineConfig createVirtualMachineConfigForRawFrom(VirtualMachineConfig vmConfig)893 createVirtualMachineConfigForRawFrom(VirtualMachineConfig vmConfig) 894 throws IllegalStateException, IOException { 895 VirtualMachineRawConfig rawConfig = vmConfig.toVsRawConfig(); 896 897 // Handle input devices here 898 List<InputDevice> inputDevices = new ArrayList<>(); 899 if (vmConfig.getCustomImageConfig() != null 900 && rawConfig.displayConfig != null) { 901 if (vmConfig.getCustomImageConfig().useTouch()) { 902 ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair(); 903 mTouchSock = pfds[0]; 904 InputDevice.SingleTouch t = new InputDevice.SingleTouch(); 905 t.width = rawConfig.displayConfig.width; 906 t.height = rawConfig.displayConfig.height; 907 t.pfd = pfds[1]; 908 inputDevices.add(InputDevice.singleTouch(t)); 909 } 910 if (vmConfig.getCustomImageConfig().useKeyboard()) { 911 ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair(); 912 mKeySock = pfds[0]; 913 InputDevice.Keyboard k = new InputDevice.Keyboard(); 914 k.pfd = pfds[1]; 915 inputDevices.add(InputDevice.keyboard(k)); 916 } 917 if (vmConfig.getCustomImageConfig().useMouse()) { 918 ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair(); 919 mMouseSock = pfds[0]; 920 InputDevice.Mouse m = new InputDevice.Mouse(); 921 m.pfd = pfds[1]; 922 inputDevices.add(InputDevice.mouse(m)); 923 } 924 } 925 rawConfig.inputDevices = inputDevices.toArray(new InputDevice[0]); 926 927 return android.system.virtualizationservice.VirtualMachineConfig.rawConfig(rawConfig); 928 } 929 InputEvent(short type, short code, int value)930 private static record InputEvent(short type, short code, int value) {} 931 932 /** @hide */ sendKeyEvent(KeyEvent event)933 public boolean sendKeyEvent(KeyEvent event) { 934 if (mKeySock == null) { 935 Log.d(TAG, "mKeySock == null"); 936 return false; 937 } 938 // from include/uapi/linux/input-event-codes.h in the kernel. 939 short EV_SYN = 0x00; 940 short EV_KEY = 0x01; 941 short SYN_REPORT = 0x00; 942 boolean down = event.getAction() != MotionEvent.ACTION_UP; 943 944 return writeEventsToSock( 945 mKeySock, 946 Arrays.asList( 947 new InputEvent(EV_KEY, (short) event.getScanCode(), down ? 1 : 0), 948 new InputEvent(EV_SYN, SYN_REPORT, 0))); 949 } 950 951 /** @hide */ sendMouseEvent(MotionEvent event)952 public boolean sendMouseEvent(MotionEvent event) { 953 if (mMouseSock == null) { 954 Log.d(TAG, "mMouseSock == null"); 955 return false; 956 } 957 // from include/uapi/linux/input-event-codes.h in the kernel. 958 short EV_SYN = 0x00; 959 short EV_REL = 0x02; 960 short EV_KEY = 0x01; 961 short REL_X = 0x00; 962 short REL_Y = 0x01; 963 short SYN_REPORT = 0x00; 964 switch (event.getAction()) { 965 case MotionEvent.ACTION_MOVE: 966 int x = (int) event.getX(); 967 int y = (int) event.getY(); 968 return writeEventsToSock( 969 mMouseSock, 970 Arrays.asList( 971 new InputEvent(EV_REL, REL_X, x), 972 new InputEvent(EV_REL, REL_Y, y), 973 new InputEvent(EV_SYN, SYN_REPORT, 0))); 974 case MotionEvent.ACTION_BUTTON_PRESS: 975 case MotionEvent.ACTION_BUTTON_RELEASE: 976 short BTN_LEFT = 0x110; 977 short BTN_RIGHT = 0x111; 978 short BTN_MIDDLE = 0x112; 979 short keyCode; 980 switch (event.getActionButton()) { 981 case MotionEvent.BUTTON_PRIMARY: 982 keyCode = BTN_LEFT; 983 break; 984 case MotionEvent.BUTTON_SECONDARY: 985 keyCode = BTN_RIGHT; 986 break; 987 case MotionEvent.BUTTON_TERTIARY: 988 keyCode = BTN_MIDDLE; 989 break; 990 default: 991 Log.d(TAG, event.toString()); 992 return false; 993 } 994 return writeEventsToSock( 995 mMouseSock, 996 Arrays.asList( 997 new InputEvent( 998 EV_KEY, 999 keyCode, 1000 event.getAction() == MotionEvent.ACTION_BUTTON_PRESS 1001 ? 1 1002 : 0), 1003 new InputEvent(EV_SYN, SYN_REPORT, 0))); 1004 case MotionEvent.ACTION_SCROLL: 1005 short REL_HWHEEL = 0x06; 1006 short REL_WHEEL = 0x08; 1007 int scrollX = (int) event.getAxisValue(MotionEvent.AXIS_HSCROLL); 1008 int scrollY = (int) event.getAxisValue(MotionEvent.AXIS_VSCROLL); 1009 boolean status = true; 1010 if (scrollX != 0) { 1011 status &= 1012 writeEventsToSock( 1013 mMouseSock, 1014 Arrays.asList( 1015 new InputEvent(EV_REL, REL_HWHEEL, scrollX), 1016 new InputEvent(EV_SYN, SYN_REPORT, 0))); 1017 } else if (scrollY != 0) { 1018 status &= 1019 writeEventsToSock( 1020 mMouseSock, 1021 Arrays.asList( 1022 new InputEvent(EV_REL, REL_WHEEL, scrollY), 1023 new InputEvent(EV_SYN, SYN_REPORT, 0))); 1024 } else { 1025 Log.d(TAG, event.toString()); 1026 return false; 1027 } 1028 return status; 1029 case MotionEvent.ACTION_UP: 1030 case MotionEvent.ACTION_DOWN: 1031 // Ignored because it's handled by ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE 1032 return true; 1033 default: 1034 Log.d(TAG, event.toString()); 1035 return false; 1036 } 1037 } 1038 1039 /** @hide */ sendSingleTouchEvent(MotionEvent event)1040 public boolean sendSingleTouchEvent(MotionEvent event) { 1041 if (mTouchSock == null) { 1042 Log.d(TAG, "mTouchSock == null"); 1043 return false; 1044 } 1045 // from include/uapi/linux/input-event-codes.h in the kernel. 1046 short EV_SYN = 0x00; 1047 short EV_ABS = 0x03; 1048 short EV_KEY = 0x01; 1049 short BTN_TOUCH = 0x14a; 1050 short ABS_X = 0x00; 1051 short ABS_Y = 0x01; 1052 short SYN_REPORT = 0x00; 1053 1054 int x = (int) event.getX(); 1055 int y = (int) event.getY(); 1056 boolean down = event.getAction() != MotionEvent.ACTION_UP; 1057 1058 return writeEventsToSock( 1059 mTouchSock, 1060 Arrays.asList( 1061 new InputEvent(EV_ABS, ABS_X, x), 1062 new InputEvent(EV_ABS, ABS_Y, y), 1063 new InputEvent(EV_KEY, BTN_TOUCH, down ? 1 : 0), 1064 new InputEvent(EV_SYN, SYN_REPORT, 0))); 1065 } 1066 writeEventsToSock(ParcelFileDescriptor sock, List<InputEvent> evtList)1067 private boolean writeEventsToSock(ParcelFileDescriptor sock, List<InputEvent> evtList) { 1068 ByteBuffer byteBuffer = 1069 ByteBuffer.allocate(8 /* (type: u16 + code: u16 + value: i32) */ * evtList.size()); 1070 byteBuffer.clear(); 1071 byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 1072 for (InputEvent e : evtList) { 1073 byteBuffer.putShort(e.type); 1074 byteBuffer.putShort(e.code); 1075 byteBuffer.putInt(e.value); 1076 } 1077 try { 1078 IoBridge.write( 1079 sock.getFileDescriptor(), byteBuffer.array(), 0, byteBuffer.array().length); 1080 } catch (IOException e) { 1081 Log.d(TAG, "cannot send event", e); 1082 return false; 1083 } 1084 return true; 1085 } 1086 1087 private android.system.virtualizationservice.VirtualMachineConfig createVirtualMachineConfigForAppFrom( VirtualMachineConfig vmConfig, IVirtualizationService service)1088 createVirtualMachineConfigForAppFrom( 1089 VirtualMachineConfig vmConfig, IVirtualizationService service) 1090 throws RemoteException, IOException, VirtualMachineException { 1091 VirtualMachineAppConfig appConfig = vmConfig.toVsConfig(mContext.getPackageManager()); 1092 appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE); 1093 appConfig.name = mName; 1094 if (mInstanceIdPath != null) { 1095 appConfig.instanceId = Files.readAllBytes(mInstanceIdPath.toPath()); 1096 } else { 1097 // FEATURE_LLPVM_CHANGES is disabled, instance_id is not used. 1098 appConfig.instanceId = new byte[64]; 1099 } 1100 if (mEncryptedStoreFilePath != null) { 1101 appConfig.encryptedStorageImage = 1102 ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_WRITE); 1103 } 1104 1105 if (!vmConfig.getExtraApks().isEmpty()) { 1106 // Extra APKs were specified directly, rather than via config file. 1107 // We've already populated the file names for the extra APKs and IDSigs 1108 // (via setupExtraApks). But we also need to open the APK files and add 1109 // fds for them to the payload config. 1110 // This isn't needed when the extra APKs are specified in a config file - 1111 // then 1112 // Virtualization Manager opens them itself. 1113 List<ParcelFileDescriptor> extraApkFiles = new ArrayList<>(mExtraApks.size()); 1114 for (ExtraApkSpec extraApk : mExtraApks) { 1115 try { 1116 extraApkFiles.add(ParcelFileDescriptor.open(extraApk.apk, MODE_READ_ONLY)); 1117 } catch (FileNotFoundException e) { 1118 throw new VirtualMachineException("Failed to open extra APK", e); 1119 } 1120 } 1121 appConfig.payload.getPayloadConfig().extraApks = extraApkFiles; 1122 } 1123 1124 try { 1125 createIdSigsAndUpdateConfig(service, appConfig); 1126 } catch (FileNotFoundException e) { 1127 throw new VirtualMachineException("Failed to generate APK signature", e); 1128 } 1129 return android.system.virtualizationservice.VirtualMachineConfig.appConfig(appConfig); 1130 } 1131 1132 /** 1133 * Runs this virtual machine. The returning of this method however doesn't mean that the VM has 1134 * actually started running or the OS has booted there. Such events can be notified by 1135 * registering a callback using {@link #setCallback} before calling {@code run()}. 1136 * 1137 * <p>NOTE: This method may block and should not be called on the main thread. 1138 * 1139 * @throws VirtualMachineException if the virtual machine is not stopped or could not be 1140 * started. 1141 * @hide 1142 */ 1143 @SystemApi 1144 @WorkerThread 1145 @RequiresPermission(MANAGE_VIRTUAL_MACHINE_PERMISSION) run()1146 public void run() throws VirtualMachineException { 1147 synchronized (mLock) { 1148 checkStopped(); 1149 1150 try { 1151 mIdsigFilePath.createNewFile(); 1152 for (ExtraApkSpec extraApk : mExtraApks) { 1153 extraApk.idsig.createNewFile(); 1154 } 1155 } catch (IOException e) { 1156 // If the file already exists, exception is not thrown. 1157 throw new VirtualMachineException("Failed to create APK signature file", e); 1158 } 1159 1160 IVirtualizationService service = mVirtualizationService.getBinder(); 1161 1162 try { 1163 if (mConnectVmConsole) { 1164 createPtyConsole(); 1165 } 1166 1167 if (mVmOutputCaptured) { 1168 createVmOutputPipes(); 1169 } 1170 1171 if (mVmConsoleInputSupported) { 1172 createVmInputPipes(); 1173 } 1174 1175 ParcelFileDescriptor consoleOutFd = null; 1176 if (mConnectVmConsole && mVmOutputCaptured) { 1177 // If we are enabling output pipes AND the host console, then we tee the console 1178 // output to both. 1179 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 1180 mTeeConsoleOutReader = pipe[0]; 1181 mTeeConsoleOutWriter = pipe[1]; 1182 consoleOutFd = mTeeConsoleOutWriter; 1183 TeeWorker tee = 1184 new TeeWorker( 1185 mName + " console", 1186 new FileInputStream(mTeeConsoleOutReader.getFileDescriptor()), 1187 List.of( 1188 new FileOutputStream(mPtyFd.getFileDescriptor()), 1189 new FileOutputStream( 1190 mConsoleOutWriter.getFileDescriptor()))); 1191 // If the VM is stopped then the tee worker thread would get an EOF or read() 1192 // error which would tear down itself. 1193 mConsoleExecutor.execute(tee); 1194 } else if (mConnectVmConsole) { 1195 consoleOutFd = mPtyFd; 1196 } else if (mVmOutputCaptured) { 1197 consoleOutFd = mConsoleOutWriter; 1198 } 1199 1200 ParcelFileDescriptor consoleInFd = null; 1201 if (mConnectVmConsole) { 1202 consoleInFd = mPtyFd; 1203 } else if (mVmConsoleInputSupported) { 1204 consoleInFd = mConsoleInReader; 1205 } 1206 1207 VirtualMachineConfig vmConfig = getConfig(); 1208 android.system.virtualizationservice.VirtualMachineConfig vmConfigParcel = 1209 vmConfig.getCustomImageConfig() != null 1210 ? createVirtualMachineConfigForRawFrom(vmConfig) 1211 : createVirtualMachineConfigForAppFrom(vmConfig, service); 1212 1213 mVirtualMachine = 1214 service.createVm(vmConfigParcel, consoleOutFd, consoleInFd, mLogWriter); 1215 mVirtualMachine.registerCallback(new CallbackTranslator(service)); 1216 mContext.registerComponentCallbacks(mMemoryManagementCallbacks); 1217 mVirtualMachine.start(); 1218 } catch (IOException e) { 1219 throw new VirtualMachineException("failed to persist files", e); 1220 } catch (IllegalStateException | ServiceSpecificException e) { 1221 throw new VirtualMachineException(e); 1222 } catch (RemoteException e) { 1223 throw e.rethrowAsRuntimeException(); 1224 } 1225 } 1226 } 1227 createIdSigsAndUpdateConfig( IVirtualizationService service, VirtualMachineAppConfig appConfig)1228 private void createIdSigsAndUpdateConfig( 1229 IVirtualizationService service, VirtualMachineAppConfig appConfig) 1230 throws RemoteException, FileNotFoundException { 1231 // Fill the idsig file by hashing the apk 1232 service.createOrUpdateIdsigFile( 1233 appConfig.apk, ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_WRITE)); 1234 1235 for (ExtraApkSpec extraApk : mExtraApks) { 1236 service.createOrUpdateIdsigFile( 1237 ParcelFileDescriptor.open(extraApk.apk, MODE_READ_ONLY), 1238 ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_WRITE)); 1239 } 1240 1241 // Re-open idsig files in read-only mode 1242 appConfig.idsig = ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_ONLY); 1243 List<ParcelFileDescriptor> extraIdsigs = new ArrayList<>(); 1244 for (ExtraApkSpec extraApk : mExtraApks) { 1245 extraIdsigs.add(ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_ONLY)); 1246 } 1247 appConfig.extraIdsigs = extraIdsigs; 1248 } 1249 1250 @GuardedBy("mLock") createVmOutputPipes()1251 private void createVmOutputPipes() throws VirtualMachineException { 1252 try { 1253 if (mConsoleOutReader == null || mConsoleOutWriter == null) { 1254 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 1255 mConsoleOutReader = pipe[0]; 1256 mConsoleOutWriter = pipe[1]; 1257 } 1258 1259 if (mLogReader == null || mLogWriter == null) { 1260 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 1261 mLogReader = pipe[0]; 1262 mLogWriter = pipe[1]; 1263 } 1264 } catch (IOException e) { 1265 throw new VirtualMachineException("Failed to create output stream for VM", e); 1266 } 1267 } 1268 1269 @GuardedBy("mLock") createVmInputPipes()1270 private void createVmInputPipes() throws VirtualMachineException { 1271 try { 1272 if (mConsoleInReader == null || mConsoleInWriter == null) { 1273 if (mConnectVmConsole) { 1274 // If we are enabling input pipes AND the host console, then we should just use 1275 // the host pty peer end as the console write end. 1276 createPtyConsole(); 1277 mConsoleInReader = mPtyFd.dup(); 1278 mConsoleInWriter = mPtsFd.dup(); 1279 } else { 1280 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 1281 mConsoleInReader = pipe[0]; 1282 mConsoleInWriter = pipe[1]; 1283 } 1284 } 1285 } catch (IOException e) { 1286 throw new VirtualMachineException("Failed to create input stream for VM", e); 1287 } 1288 } 1289 1290 @FunctionalInterface 1291 private static interface OpenPtyCallback { apply(FileDescriptor mfd, FileDescriptor sfd, byte[] name)1292 public void apply(FileDescriptor mfd, FileDescriptor sfd, byte[] name); 1293 } 1294 1295 // Opens a pty and set the master end to raw mode and O_NONBLOCK. nativeOpenPtyRawNonblock(OpenPtyCallback resultCallback)1296 private static native void nativeOpenPtyRawNonblock(OpenPtyCallback resultCallback) 1297 throws IOException; 1298 1299 @GuardedBy("mLock") createPtyConsole()1300 private void createPtyConsole() throws VirtualMachineException { 1301 if (mPtyFd != null && mPtsFd != null) { 1302 return; 1303 } 1304 List<FileDescriptor> fd = new ArrayList<>(2); 1305 StringBuilder nameBuilder = new StringBuilder(); 1306 try { 1307 try { 1308 nativeOpenPtyRawNonblock( 1309 (FileDescriptor mfd, FileDescriptor sfd, byte[] ptsName) -> { 1310 fd.add(mfd); 1311 fd.add(sfd); 1312 nameBuilder.append(new String(ptsName, StandardCharsets.UTF_8)); 1313 }); 1314 } catch (Exception e) { 1315 fd.forEach(IoUtils::closeQuietly); 1316 throw e; 1317 } 1318 } catch (IOException e) { 1319 throw new VirtualMachineException( 1320 "Failed to create host console to connect to the VM console", e); 1321 } 1322 mPtyFd = new ParcelFileDescriptor(fd.get(0)); 1323 mPtsFd = new ParcelFileDescriptor(fd.get(1)); 1324 mPtsName = nameBuilder.toString(); 1325 Log.d(TAG, "Serial console device: " + mPtsName); 1326 } 1327 1328 /** 1329 * Returns the name of the peer end (ptsname) of the host console. The host console is only 1330 * available if the {@link VirtualMachineConfig} specifies that a host console should 1331 * {@linkplain VirtualMachineConfig#isConnectVmConsole connect} to the VM console. 1332 * 1333 * @throws VirtualMachineException if the host pseudoterminal could not be created, or 1334 * connecting to the VM console is not enabled. 1335 * @hide 1336 */ 1337 @NonNull getHostConsoleName()1338 public String getHostConsoleName() throws VirtualMachineException { 1339 if (!mConnectVmConsole) { 1340 throw new VirtualMachineException("Host console is not enabled"); 1341 } 1342 synchronized (mLock) { 1343 createPtyConsole(); 1344 return mPtsName; 1345 } 1346 } 1347 1348 /** 1349 * Returns the stream object representing the console output from the virtual machine. The 1350 * console output is only available if the {@link VirtualMachineConfig} specifies that it should 1351 * be {@linkplain VirtualMachineConfig#isVmOutputCaptured captured}. 1352 * 1353 * <p>If you turn on output capture, you must consume data from {@code getConsoleOutput} - 1354 * because otherwise the code in the VM may get blocked when the pipe buffer fills up. 1355 * 1356 * <p>NOTE: This method may block and should not be called on the main thread. 1357 * 1358 * @throws VirtualMachineException if the stream could not be created, or capturing is turned 1359 * off. 1360 * @hide 1361 */ 1362 @SystemApi 1363 @WorkerThread 1364 @NonNull getConsoleOutput()1365 public InputStream getConsoleOutput() throws VirtualMachineException { 1366 if (!mVmOutputCaptured) { 1367 throw new VirtualMachineException("Capturing vm outputs is turned off"); 1368 } 1369 synchronized (mLock) { 1370 createVmOutputPipes(); 1371 return new FileInputStream(mConsoleOutReader.getFileDescriptor()); 1372 } 1373 } 1374 1375 /** 1376 * Returns the stream object representing the console input to the virtual machine. The console 1377 * input is only available if the {@link VirtualMachineConfig} specifies that it should be 1378 * {@linkplain VirtualMachineConfig#isVmConsoleInputSupported supported}. 1379 * 1380 * <p>NOTE: This method may block and should not be called on the main thread. 1381 * 1382 * @throws VirtualMachineException if the stream could not be created, or console input is not 1383 * supported. 1384 * @hide 1385 */ 1386 @TestApi 1387 @WorkerThread 1388 @NonNull getConsoleInput()1389 public OutputStream getConsoleInput() throws VirtualMachineException { 1390 if (!mVmConsoleInputSupported) { 1391 throw new VirtualMachineException("VM console input is not supported"); 1392 } 1393 synchronized (mLock) { 1394 createVmInputPipes(); 1395 return new FileOutputStream(mConsoleInWriter.getFileDescriptor()); 1396 } 1397 } 1398 1399 1400 /** 1401 * Returns the stream object representing the log output from the virtual machine. The log 1402 * output is only available if the VirtualMachineConfig specifies that it should be {@linkplain 1403 * VirtualMachineConfig#isVmOutputCaptured captured}. 1404 * 1405 * <p>If you turn on output capture, you must consume data from {@code getLogOutput} - because 1406 * otherwise the code in the VM may get blocked when the pipe buffer fills up. 1407 * 1408 * <p>NOTE: This method may block and should not be called on the main thread. 1409 * 1410 * @throws VirtualMachineException if the stream could not be created, or capturing is turned 1411 * off. 1412 * @hide 1413 */ 1414 @SystemApi 1415 @WorkerThread 1416 @NonNull getLogOutput()1417 public InputStream getLogOutput() throws VirtualMachineException { 1418 if (!mVmOutputCaptured) { 1419 throw new VirtualMachineException("Capturing vm outputs is turned off"); 1420 } 1421 synchronized (mLock) { 1422 createVmOutputPipes(); 1423 return new FileInputStream(mLogReader.getFileDescriptor()); 1424 } 1425 } 1426 1427 /** 1428 * Stops this virtual machine. Stopping a virtual machine is like pulling the plug on a real 1429 * computer; the machine halts immediately. Software running on the virtual machine is not 1430 * notified of the event. Writes to {@linkplain 1431 * VirtualMachineConfig.Builder#setEncryptedStorageBytes encrypted storage} might not be 1432 * persisted, and the instance might be left in an inconsistent state. 1433 * 1434 * <p>For a graceful shutdown, you could request the payload to call {@code exit()}, e.g. via a 1435 * {@linkplain #connectToVsockServer binder request}, and wait for {@link 1436 * VirtualMachineCallback#onPayloadFinished} to be called. 1437 * 1438 * <p>A stopped virtual machine can be re-started by calling {@link #run()}. 1439 * 1440 * <p>NOTE: This method may block and should not be called on the main thread. 1441 * 1442 * @throws VirtualMachineException if the virtual machine is not running or could not be 1443 * stopped. 1444 * @hide 1445 */ 1446 @SystemApi 1447 @WorkerThread stop()1448 public void stop() throws VirtualMachineException { 1449 synchronized (mLock) { 1450 if (mVirtualMachine == null) { 1451 throw new VirtualMachineException("VM is not running"); 1452 } 1453 try { 1454 mVirtualMachine.stop(); 1455 dropVm(); 1456 } catch (RemoteException e) { 1457 throw e.rethrowAsRuntimeException(); 1458 } catch (ServiceSpecificException e) { 1459 throw new VirtualMachineException(e); 1460 } 1461 } 1462 } 1463 1464 /** 1465 * Stops this virtual machine, if it is running. 1466 * 1467 * <p>NOTE: This method may block and should not be called on the main thread. 1468 * 1469 * @see #stop() 1470 * @hide 1471 */ 1472 @SystemApi 1473 @WorkerThread 1474 @Override close()1475 public void close() { 1476 synchronized (mLock) { 1477 if (mVirtualMachine == null) { 1478 return; 1479 } 1480 try { 1481 if (stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) { 1482 mVirtualMachine.stop(); 1483 dropVm(); 1484 } 1485 } catch (RemoteException e) { 1486 throw e.rethrowAsRuntimeException(); 1487 } catch (ServiceSpecificException e) { 1488 // Deliberately ignored; this almost certainly means the VM exited just as 1489 // we tried to stop it. 1490 Log.i(TAG, "Ignoring error on close()", e); 1491 } 1492 } 1493 } 1494 deleteRecursively(File dir)1495 private static void deleteRecursively(File dir) throws IOException { 1496 // Note: This doesn't follow symlinks, which is important. Instead they are just deleted 1497 // (and Files.delete deletes the link not the target). 1498 Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<>() { 1499 @Override 1500 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 1501 throws IOException { 1502 Files.delete(file); 1503 return FileVisitResult.CONTINUE; 1504 } 1505 1506 @Override 1507 public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { 1508 // Directory is deleted after we've visited (deleted) all its contents, so it 1509 // should be empty by now. 1510 Files.delete(dir); 1511 return FileVisitResult.CONTINUE; 1512 } 1513 }); 1514 } 1515 1516 /** 1517 * Changes the config of this virtual machine to a new one. This can be used to adjust things 1518 * like the number of CPU and size of the RAM, depending on the situation (e.g. the size of the 1519 * application to run on the virtual machine, etc.) 1520 * 1521 * <p>The new config must be {@linkplain VirtualMachineConfig#isCompatibleWith compatible with} 1522 * the existing config. 1523 * 1524 * <p>NOTE: This method may block and should not be called on the main thread. 1525 * 1526 * @return the old config 1527 * @throws VirtualMachineException if the virtual machine is not stopped, or the new config is 1528 * incompatible. 1529 * @hide 1530 */ 1531 @SystemApi 1532 @WorkerThread 1533 @NonNull setConfig(@onNull VirtualMachineConfig newConfig)1534 public VirtualMachineConfig setConfig(@NonNull VirtualMachineConfig newConfig) 1535 throws VirtualMachineException { 1536 synchronized (mLock) { 1537 VirtualMachineConfig oldConfig = mConfig; 1538 if (!oldConfig.isCompatibleWith(newConfig)) { 1539 throw new VirtualMachineException("incompatible config"); 1540 } 1541 checkStopped(); 1542 1543 if (oldConfig != newConfig) { 1544 // Delete any existing file before recreating; that ensures any 1545 // VirtualMachineDescriptor that refers to the old file does not see the new config. 1546 mConfigFilePath.delete(); 1547 newConfig.serialize(mConfigFilePath); 1548 mConfig = newConfig; 1549 } 1550 return oldConfig; 1551 } 1552 } 1553 1554 @Nullable nativeConnectToVsockServer(IBinder vmBinder, int port)1555 private static native IBinder nativeConnectToVsockServer(IBinder vmBinder, int port); 1556 1557 /** 1558 * Connect to a VM's binder service via vsock and return the root IBinder object. Guest VMs are 1559 * expected to set up vsock servers in their payload. After the host app receives the {@link 1560 * VirtualMachineCallback#onPayloadReady}, it can use this method to establish a connection to 1561 * the guest VM. 1562 * 1563 * <p>NOTE: This method may block and should not be called on the main thread. 1564 * 1565 * @throws VirtualMachineException if the virtual machine is not running or the connection 1566 * failed. 1567 * @hide 1568 */ 1569 @SystemApi 1570 @WorkerThread 1571 @NonNull connectToVsockServer( @ntRangefrom = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)1572 public IBinder connectToVsockServer( 1573 @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port) 1574 throws VirtualMachineException { 1575 1576 synchronized (mLock) { 1577 IBinder iBinder = 1578 nativeConnectToVsockServer(getRunningVm().asBinder(), validatePort(port)); 1579 if (iBinder == null) { 1580 throw new VirtualMachineException("Failed to connect to vsock server"); 1581 } 1582 return iBinder; 1583 } 1584 } 1585 1586 /** 1587 * Opens a vsock connection to the VM on the given port. 1588 * 1589 * <p>The caller is responsible for closing the returned {@code ParcelFileDescriptor}. 1590 * 1591 * <p>NOTE: This method may block and should not be called on the main thread. 1592 * 1593 * @throws VirtualMachineException if connecting fails. 1594 * @hide 1595 */ 1596 @SystemApi 1597 @WorkerThread 1598 @NonNull connectVsock( @ntRangefrom = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)1599 public ParcelFileDescriptor connectVsock( 1600 @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port) 1601 throws VirtualMachineException { 1602 synchronized (mLock) { 1603 try { 1604 return getRunningVm().connectVsock(validatePort(port)); 1605 } catch (RemoteException e) { 1606 throw e.rethrowAsRuntimeException(); 1607 } catch (ServiceSpecificException e) { 1608 throw new VirtualMachineException(e); 1609 } 1610 } 1611 } 1612 validatePort(long port)1613 private int validatePort(long port) { 1614 // Ports below 1024 are "privileged" (payload code can't bind to these), and port numbers 1615 // are 32-bit unsigned numbers at the OS level, even though we pass them as 32-bit signed 1616 // numbers internally. 1617 if (port < MIN_VSOCK_PORT || port > MAX_VSOCK_PORT) { 1618 throw new IllegalArgumentException("Bad port " + port); 1619 } 1620 return (int) port; 1621 } 1622 1623 /** 1624 * Returns the root directory where all files related to this {@link VirtualMachine} (e.g. 1625 * {@code instance.img}, {@code apk.idsig}, etc) are stored. 1626 * 1627 * @hide 1628 */ 1629 @TestApi 1630 @NonNull getRootDir()1631 public File getRootDir() { 1632 return mVmRootPath; 1633 } 1634 1635 /** 1636 * Enables the VM to request attestation in testing mode. 1637 * 1638 * <p>This function provisions a key pair for the VM attestation testing, a fake certificate 1639 * will be associated to the fake key pair when the VM requests attestation in testing mode. 1640 * 1641 * <p>The provisioned key pair can only be used in subsequent calls to {@link 1642 * AVmPayload_requestAttestationForTesting} within a running VM. 1643 * 1644 * @hide 1645 */ 1646 @TestApi 1647 @RequiresPermission(USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) 1648 @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS) enableTestAttestation()1649 public void enableTestAttestation() throws VirtualMachineException { 1650 try { 1651 mVirtualizationService.getBinder().enableTestAttestation(); 1652 } catch (RemoteException e) { 1653 throw e.rethrowAsRuntimeException(); 1654 } 1655 } 1656 1657 /** 1658 * Captures the current state of the VM in a {@link VirtualMachineDescriptor} instance. The VM 1659 * needs to be stopped to avoid inconsistency in its state representation. 1660 * 1661 * <p>The state of the VM is not actually copied until {@link 1662 * VirtualMachineManager#importFromDescriptor} is called. It is recommended that the VM not be 1663 * started until that operation is complete. 1664 * 1665 * <p>NOTE: This method may block and should not be called on the main thread. 1666 * 1667 * @return a {@link VirtualMachineDescriptor} instance that represents the VM's state. 1668 * @throws VirtualMachineException if the virtual machine is not stopped, or the state could not 1669 * be captured. 1670 * @hide 1671 */ 1672 @SystemApi 1673 @WorkerThread 1674 @NonNull toDescriptor()1675 public VirtualMachineDescriptor toDescriptor() throws VirtualMachineException { 1676 synchronized (mLock) { 1677 checkStopped(); 1678 try { 1679 return new VirtualMachineDescriptor( 1680 ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY), 1681 mInstanceIdPath != null 1682 ? ParcelFileDescriptor.open(mInstanceIdPath, MODE_READ_ONLY) 1683 : null, 1684 ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY), 1685 mEncryptedStoreFilePath != null 1686 ? ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_ONLY) 1687 : null); 1688 } catch (IOException e) { 1689 throw new VirtualMachineException(e); 1690 } 1691 } 1692 } 1693 1694 @Override toString()1695 public String toString() { 1696 VirtualMachineConfig config = getConfig(); 1697 String payloadConfigPath = config.getPayloadConfigPath(); 1698 String payloadBinaryName = config.getPayloadBinaryName(); 1699 1700 StringBuilder result = new StringBuilder(); 1701 result.append("VirtualMachine(") 1702 .append("name:") 1703 .append(getName()) 1704 .append(", "); 1705 if (payloadBinaryName != null) { 1706 result.append("payload:").append(payloadBinaryName).append(", "); 1707 } 1708 if (payloadConfigPath != null) { 1709 result.append("config:") 1710 .append(payloadConfigPath) 1711 .append(", "); 1712 } 1713 result.append("package: ") 1714 .append(mPackageName) 1715 .append(")"); 1716 return result.toString(); 1717 } 1718 1719 /** 1720 * Reads the payload config inside the application, parses extra APK information, and then 1721 * creates corresponding idsig file paths. 1722 */ setupExtraApks( @onNull Context context, @NonNull VirtualMachineConfig config, @NonNull File vmDir)1723 private static List<ExtraApkSpec> setupExtraApks( 1724 @NonNull Context context, @NonNull VirtualMachineConfig config, @NonNull File vmDir) 1725 throws VirtualMachineException { 1726 String configPath = config.getPayloadConfigPath(); 1727 List<String> extraApks = config.getExtraApks(); 1728 if (configPath != null) { 1729 return setupExtraApksFromConfigFile(context, vmDir, configPath); 1730 } else if (!extraApks.isEmpty()) { 1731 return setupExtraApksFromList(context, vmDir, extraApks); 1732 } else { 1733 return Collections.emptyList(); 1734 } 1735 } 1736 setupExtraApksFromConfigFile( Context context, File vmDir, String configPath)1737 private static List<ExtraApkSpec> setupExtraApksFromConfigFile( 1738 Context context, File vmDir, String configPath) throws VirtualMachineException { 1739 try (ZipFile zipFile = new ZipFile(context.getPackageCodePath())) { 1740 InputStream inputStream = zipFile.getInputStream(zipFile.getEntry(configPath)); 1741 List<String> apkList = 1742 parseExtraApkListFromPayloadConfig( 1743 new JsonReader(new InputStreamReader(inputStream))); 1744 1745 List<ExtraApkSpec> extraApks = new ArrayList<>(apkList.size()); 1746 for (int i = 0; i < apkList.size(); ++i) { 1747 extraApks.add( 1748 new ExtraApkSpec( 1749 new File(apkList.get(i)), 1750 new File(vmDir, EXTRA_IDSIG_FILE_PREFIX + i))); 1751 } 1752 1753 return extraApks; 1754 } catch (IOException e) { 1755 throw new VirtualMachineException("Couldn't parse extra apks from the vm config", e); 1756 } 1757 } 1758 parseExtraApkListFromPayloadConfig(JsonReader reader)1759 private static List<String> parseExtraApkListFromPayloadConfig(JsonReader reader) 1760 throws VirtualMachineException { 1761 /* 1762 * JSON schema from packages/modules/Virtualization/microdroid/payload/config/src/lib.rs: 1763 * 1764 * <p>{ "extra_apks": [ { "path": "/system/app/foo.apk", }, ... ], ... } 1765 */ 1766 try { 1767 List<String> apks = new ArrayList<>(); 1768 1769 reader.beginObject(); 1770 while (reader.hasNext()) { 1771 if (reader.nextName().equals("extra_apks")) { 1772 reader.beginArray(); 1773 while (reader.hasNext()) { 1774 reader.beginObject(); 1775 String name = reader.nextName(); 1776 if (name.equals("path")) { 1777 apks.add(reader.nextString()); 1778 } else { 1779 reader.skipValue(); 1780 } 1781 reader.endObject(); 1782 } 1783 reader.endArray(); 1784 } else { 1785 reader.skipValue(); 1786 } 1787 } 1788 reader.endObject(); 1789 return apks; 1790 } catch (IOException e) { 1791 throw new VirtualMachineException(e); 1792 } 1793 } 1794 setupExtraApksFromList( Context context, File vmDir, List<String> extraApkInfo)1795 private static List<ExtraApkSpec> setupExtraApksFromList( 1796 Context context, File vmDir, List<String> extraApkInfo) throws VirtualMachineException { 1797 int count = extraApkInfo.size(); 1798 List<ExtraApkSpec> extraApks = new ArrayList<>(count); 1799 for (int i = 0; i < count; i++) { 1800 String packageName = extraApkInfo.get(i); 1801 ApplicationInfo appInfo; 1802 try { 1803 appInfo = 1804 context.getPackageManager() 1805 .getApplicationInfo( 1806 packageName, PackageManager.ApplicationInfoFlags.of(0)); 1807 } catch (PackageManager.NameNotFoundException e) { 1808 throw new VirtualMachineException("Extra APK package not found", e); 1809 } 1810 1811 extraApks.add( 1812 new ExtraApkSpec( 1813 new File(appInfo.sourceDir), 1814 new File(vmDir, EXTRA_IDSIG_FILE_PREFIX + i))); 1815 } 1816 return extraApks; 1817 } 1818 importInstanceIdFrom(@onNull ParcelFileDescriptor instanceIdFd)1819 private void importInstanceIdFrom(@NonNull ParcelFileDescriptor instanceIdFd) 1820 throws VirtualMachineException { 1821 try (FileChannel idOutput = new FileOutputStream(mInstanceIdPath).getChannel(); 1822 FileChannel idInput = new AutoCloseInputStream(instanceIdFd).getChannel()) { 1823 idOutput.transferFrom(idInput, /*position=*/ 0, idInput.size()); 1824 } catch (IOException e) { 1825 throw new VirtualMachineException("failed to copy instance_id", e); 1826 } 1827 } 1828 importInstanceFrom(@onNull ParcelFileDescriptor instanceFd)1829 private void importInstanceFrom(@NonNull ParcelFileDescriptor instanceFd) 1830 throws VirtualMachineException { 1831 try (FileChannel instance = new FileOutputStream(mInstanceFilePath).getChannel(); 1832 FileChannel instanceInput = new AutoCloseInputStream(instanceFd).getChannel()) { 1833 instance.transferFrom(instanceInput, /*position=*/ 0, instanceInput.size()); 1834 } catch (IOException e) { 1835 throw new VirtualMachineException("failed to transfer instance image", e); 1836 } 1837 } 1838 importEncryptedStoreFrom(@onNull ParcelFileDescriptor encryptedStoreFd)1839 private void importEncryptedStoreFrom(@NonNull ParcelFileDescriptor encryptedStoreFd) 1840 throws VirtualMachineException { 1841 try (FileChannel storeOutput = new FileOutputStream(mEncryptedStoreFilePath).getChannel(); 1842 FileChannel storeInput = new AutoCloseInputStream(encryptedStoreFd).getChannel()) { 1843 storeOutput.transferFrom(storeInput, /*position=*/ 0, storeInput.size()); 1844 } catch (IOException e) { 1845 throw new VirtualMachineException("failed to transfer encryptedstore image", e); 1846 } 1847 } 1848 1849 /** Map the raw AIDL (& binder) callbacks to what the client expects. */ 1850 private class CallbackTranslator extends IVirtualMachineCallback.Stub { 1851 private final IVirtualizationService mService; 1852 private final DeathRecipient mDeathRecipient; 1853 1854 // The VM should only be observed to die once 1855 private final AtomicBoolean mOnDiedCalled = new AtomicBoolean(false); 1856 CallbackTranslator(IVirtualizationService service)1857 public CallbackTranslator(IVirtualizationService service) throws RemoteException { 1858 this.mService = service; 1859 this.mDeathRecipient = () -> reportStopped(STOP_REASON_VIRTUALIZATION_SERVICE_DIED); 1860 service.asBinder().linkToDeath(mDeathRecipient, 0); 1861 } 1862 1863 @Override onPayloadStarted(int cid)1864 public void onPayloadStarted(int cid) { 1865 executeCallback((cb) -> cb.onPayloadStarted(VirtualMachine.this)); 1866 } 1867 1868 @Override onPayloadReady(int cid)1869 public void onPayloadReady(int cid) { 1870 executeCallback((cb) -> cb.onPayloadReady(VirtualMachine.this)); 1871 } 1872 1873 @Override onPayloadFinished(int cid, int exitCode)1874 public void onPayloadFinished(int cid, int exitCode) { 1875 executeCallback((cb) -> cb.onPayloadFinished(VirtualMachine.this, exitCode)); 1876 } 1877 1878 @Override onError(int cid, int errorCode, String message)1879 public void onError(int cid, int errorCode, String message) { 1880 int translatedError = getTranslatedError(errorCode); 1881 executeCallback((cb) -> cb.onError(VirtualMachine.this, translatedError, message)); 1882 } 1883 1884 @Override onDied(int cid, int reason)1885 public void onDied(int cid, int reason) { 1886 int translatedReason = getTranslatedReason(reason); 1887 reportStopped(translatedReason); 1888 mService.asBinder().unlinkToDeath(mDeathRecipient, 0); 1889 } 1890 reportStopped(@irtualMachineCallback.StopReason int reason)1891 private void reportStopped(@VirtualMachineCallback.StopReason int reason) { 1892 if (mOnDiedCalled.compareAndSet(false, true)) { 1893 executeCallback((cb) -> cb.onStopped(VirtualMachine.this, reason)); 1894 } 1895 } 1896 1897 @VirtualMachineCallback.ErrorCode getTranslatedError(int reason)1898 private int getTranslatedError(int reason) { 1899 switch (reason) { 1900 case ErrorCode.PAYLOAD_VERIFICATION_FAILED: 1901 return ERROR_PAYLOAD_VERIFICATION_FAILED; 1902 case ErrorCode.PAYLOAD_CHANGED: 1903 return ERROR_PAYLOAD_CHANGED; 1904 case ErrorCode.PAYLOAD_INVALID_CONFIG: 1905 return ERROR_PAYLOAD_INVALID_CONFIG; 1906 default: 1907 return ERROR_UNKNOWN; 1908 } 1909 } 1910 1911 @VirtualMachineCallback.StopReason getTranslatedReason(int reason)1912 private int getTranslatedReason(int reason) { 1913 switch (reason) { 1914 case DeathReason.INFRASTRUCTURE_ERROR: 1915 return STOP_REASON_INFRASTRUCTURE_ERROR; 1916 case DeathReason.KILLED: 1917 return STOP_REASON_KILLED; 1918 case DeathReason.SHUTDOWN: 1919 return STOP_REASON_SHUTDOWN; 1920 case DeathReason.START_FAILED: 1921 return STOP_REASON_START_FAILED; 1922 case DeathReason.REBOOT: 1923 return STOP_REASON_REBOOT; 1924 case DeathReason.CRASH: 1925 return STOP_REASON_CRASH; 1926 case DeathReason.PVM_FIRMWARE_PUBLIC_KEY_MISMATCH: 1927 return STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH; 1928 case DeathReason.PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED: 1929 return STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED; 1930 case DeathReason.MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE: 1931 return STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE; 1932 case DeathReason.MICRODROID_PAYLOAD_HAS_CHANGED: 1933 return STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED; 1934 case DeathReason.MICRODROID_PAYLOAD_VERIFICATION_FAILED: 1935 return STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED; 1936 case DeathReason.MICRODROID_INVALID_PAYLOAD_CONFIG: 1937 return STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG; 1938 case DeathReason.MICRODROID_UNKNOWN_RUNTIME_ERROR: 1939 return STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR; 1940 case DeathReason.HANGUP: 1941 return STOP_REASON_HANGUP; 1942 default: 1943 return STOP_REASON_UNKNOWN; 1944 } 1945 } 1946 } 1947 1948 /** 1949 * Duplicates {@code InputStream} data to multiple {@code OutputStream}. Like the "tee" command. 1950 * 1951 * <p>Supports non-blocking writes to the output streams by ignoring EAGAIN error. 1952 */ 1953 private static class TeeWorker implements Runnable { 1954 private final String mName; 1955 private final InputStream mIn; 1956 private final List<OutputStream> mOuts; 1957 TeeWorker(String name, InputStream in, Collection<OutputStream> outs)1958 TeeWorker(String name, InputStream in, Collection<OutputStream> outs) { 1959 mName = name; 1960 mIn = in; 1961 mOuts = new ArrayList<>(outs); 1962 } 1963 1964 @Override run()1965 public void run() { 1966 byte[] buffer = new byte[2048]; 1967 try { 1968 while (!Thread.interrupted()) { 1969 int len = mIn.read(buffer); 1970 if (len < 0) { 1971 break; 1972 } 1973 for (OutputStream out : mOuts) { 1974 try { 1975 out.write(buffer, 0, len); 1976 } catch (IOException e) { 1977 // EAGAIN is expected because the file description has O_NONBLOCK flag. 1978 if (!isErrnoError(e, OsConstants.EAGAIN)) { 1979 throw e; 1980 } 1981 } 1982 } 1983 } 1984 } catch (Exception e) { 1985 Log.e(TAG, "Tee " + mName, e); 1986 } 1987 } 1988 asErrnoException(Throwable e)1989 private static ErrnoException asErrnoException(Throwable e) { 1990 if (e instanceof ErrnoException) { 1991 return (ErrnoException) e; 1992 } else if (e instanceof IOException) { 1993 // Try to unwrap ErrnoException#rethrowAsIOException() 1994 return asErrnoException(e.getCause()); 1995 } 1996 return null; 1997 } 1998 isErrnoError(Exception e, int expectedValue)1999 private static boolean isErrnoError(Exception e, int expectedValue) { 2000 ErrnoException errno = asErrnoException(e); 2001 return errno != null && errno.errno == expectedValue; 2002 } 2003 } 2004 } 2005