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 22 import static java.util.Objects.requireNonNull; 23 24 import android.annotation.IntDef; 25 import android.annotation.IntRange; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.annotation.RequiresPermission; 29 import android.annotation.SystemApi; 30 import android.annotation.TestApi; 31 import android.content.Context; 32 import android.content.pm.ApplicationInfo; 33 import android.content.pm.PackageManager; 34 import android.os.Build; 35 import android.os.ParcelFileDescriptor; 36 import android.os.PersistableBundle; 37 import android.sysprop.HypervisorProperties; 38 import android.system.virtualizationservice.VirtualMachineAppConfig; 39 import android.system.virtualizationservice.VirtualMachinePayloadConfig; 40 import android.util.Log; 41 42 import java.io.File; 43 import java.io.FileInputStream; 44 import java.io.FileNotFoundException; 45 import java.io.FileOutputStream; 46 import java.io.IOException; 47 import java.io.InputStream; 48 import java.io.OutputStream; 49 import java.lang.annotation.Retention; 50 import java.lang.annotation.RetentionPolicy; 51 import java.util.Objects; 52 import java.util.zip.ZipFile; 53 54 /** 55 * Represents a configuration of a virtual machine. A configuration consists of hardware 56 * configurations like the number of CPUs and the size of RAM, and software configurations like the 57 * payload to run on the virtual machine. 58 * 59 * @hide 60 */ 61 @SystemApi 62 public final class VirtualMachineConfig { 63 private static final String TAG = "VirtualMachineConfig"; 64 private static final String[] EMPTY_STRING_ARRAY = {}; 65 66 // These define the schema of the config file persisted on disk. 67 private static final int VERSION = 6; 68 private static final String KEY_VERSION = "version"; 69 private static final String KEY_PACKAGENAME = "packageName"; 70 private static final String KEY_APKPATH = "apkPath"; 71 private static final String KEY_PAYLOADCONFIGPATH = "payloadConfigPath"; 72 private static final String KEY_PAYLOADBINARYNAME = "payloadBinaryPath"; 73 private static final String KEY_DEBUGLEVEL = "debugLevel"; 74 private static final String KEY_PROTECTED_VM = "protectedVm"; 75 private static final String KEY_MEMORY_BYTES = "memoryBytes"; 76 private static final String KEY_CPU_TOPOLOGY = "cpuTopology"; 77 private static final String KEY_ENCRYPTED_STORAGE_BYTES = "encryptedStorageBytes"; 78 private static final String KEY_VM_OUTPUT_CAPTURED = "vmOutputCaptured"; 79 80 /** @hide */ 81 @Retention(RetentionPolicy.SOURCE) 82 @IntDef(prefix = "DEBUG_LEVEL_", value = { 83 DEBUG_LEVEL_NONE, 84 DEBUG_LEVEL_FULL 85 }) 86 public @interface DebugLevel {} 87 88 /** 89 * Not debuggable at all. No log is exported from the VM. Debugger can't be attached to the app 90 * process running in the VM. This is the default level. 91 * 92 * @hide 93 */ 94 @SystemApi public static final int DEBUG_LEVEL_NONE = 0; 95 96 /** 97 * Fully debuggable. All logs (both logcat and kernel message) are exported. All processes 98 * running in the VM can be attached to the debugger. Rooting is possible. 99 * 100 * @hide 101 */ 102 @SystemApi public static final int DEBUG_LEVEL_FULL = 1; 103 104 /** @hide */ 105 @Retention(RetentionPolicy.SOURCE) 106 @IntDef( 107 prefix = "CPU_TOPOLOGY_", 108 value = { 109 CPU_TOPOLOGY_ONE_CPU, 110 CPU_TOPOLOGY_MATCH_HOST, 111 }) 112 public @interface CpuTopology {} 113 114 /** 115 * Run VM with 1 vCPU. This is the default option, usually the fastest to boot and consuming the 116 * least amount of resources. Typically the best option for small or ephemeral workloads. 117 * 118 * @hide 119 */ 120 @SystemApi public static final int CPU_TOPOLOGY_ONE_CPU = 0; 121 122 /** 123 * Run VM with vCPU topology matching the physical CPU topology of the host. Usually takes 124 * longer to boot and cosumes more resources compared to a single vCPU. Typically a good option 125 * for long-running workloads that benefit from parallel execution. 126 * 127 * @hide 128 */ 129 @SystemApi public static final int CPU_TOPOLOGY_MATCH_HOST = 1; 130 131 /** Name of a package whose primary APK contains the VM payload. */ 132 @Nullable private final String mPackageName; 133 134 /** Absolute path to the APK file containing the VM payload. */ 135 @Nullable private final String mApkPath; 136 137 @DebugLevel private final int mDebugLevel; 138 139 /** 140 * Whether to run the VM in protected mode, so the host can't access its memory. 141 */ 142 private final boolean mProtectedVm; 143 144 /** 145 * The amount of RAM to give the VM, in bytes. If this is 0 or negative the default will be 146 * used. 147 */ 148 private final long mMemoryBytes; 149 150 /** CPU topology configuration of the VM. */ 151 @CpuTopology private final int mCpuTopology; 152 153 /** 154 * Path within the APK to the payload config file that defines software aspects of the VM. 155 */ 156 @Nullable private final String mPayloadConfigPath; 157 158 /** Name of the payload binary file within the APK that will be executed within the VM. */ 159 @Nullable private final String mPayloadBinaryName; 160 161 /** The size of storage in bytes. 0 indicates that encryptedStorage is not required */ 162 private final long mEncryptedStorageBytes; 163 164 /** Whether the app can read console and log output. */ 165 private final boolean mVmOutputCaptured; 166 VirtualMachineConfig( @ullable String packageName, @Nullable String apkPath, @Nullable String payloadConfigPath, @Nullable String payloadBinaryName, @DebugLevel int debugLevel, boolean protectedVm, long memoryBytes, @CpuTopology int cpuTopology, long encryptedStorageBytes, boolean vmOutputCaptured)167 private VirtualMachineConfig( 168 @Nullable String packageName, 169 @Nullable String apkPath, 170 @Nullable String payloadConfigPath, 171 @Nullable String payloadBinaryName, 172 @DebugLevel int debugLevel, 173 boolean protectedVm, 174 long memoryBytes, 175 @CpuTopology int cpuTopology, 176 long encryptedStorageBytes, 177 boolean vmOutputCaptured) { 178 // This is only called from Builder.build(); the builder handles parameter validation. 179 mPackageName = packageName; 180 mApkPath = apkPath; 181 mPayloadConfigPath = payloadConfigPath; 182 mPayloadBinaryName = payloadBinaryName; 183 mDebugLevel = debugLevel; 184 mProtectedVm = protectedVm; 185 mMemoryBytes = memoryBytes; 186 mCpuTopology = cpuTopology; 187 mEncryptedStorageBytes = encryptedStorageBytes; 188 mVmOutputCaptured = vmOutputCaptured; 189 } 190 191 /** Loads a config from a file. */ 192 @NonNull from(@onNull File file)193 static VirtualMachineConfig from(@NonNull File file) throws VirtualMachineException { 194 try (FileInputStream input = new FileInputStream(file)) { 195 return fromInputStream(input); 196 } catch (IOException e) { 197 throw new VirtualMachineException("Failed to read VM config from file", e); 198 } 199 } 200 201 /** Loads a config from a {@link ParcelFileDescriptor}. */ 202 @NonNull from(@onNull ParcelFileDescriptor fd)203 static VirtualMachineConfig from(@NonNull ParcelFileDescriptor fd) 204 throws VirtualMachineException { 205 try (AutoCloseInputStream input = new AutoCloseInputStream(fd)) { 206 return fromInputStream(input); 207 } catch (IOException e) { 208 throw new VirtualMachineException("failed to read VM config from file descriptor", e); 209 } 210 } 211 212 /** Loads a config from a stream, for example a file. */ 213 @NonNull fromInputStream(@onNull InputStream input)214 private static VirtualMachineConfig fromInputStream(@NonNull InputStream input) 215 throws IOException, VirtualMachineException { 216 PersistableBundle b = PersistableBundle.readFromStream(input); 217 try { 218 return fromPersistableBundle(b); 219 } catch (NullPointerException | IllegalArgumentException | IllegalStateException e) { 220 throw new VirtualMachineException("Persisted VM config is invalid", e); 221 } 222 } 223 224 @NonNull fromPersistableBundle(PersistableBundle b)225 private static VirtualMachineConfig fromPersistableBundle(PersistableBundle b) { 226 int version = b.getInt(KEY_VERSION); 227 if (version > VERSION) { 228 throw new IllegalArgumentException( 229 "Version " + version + " too high; current is " + VERSION); 230 } 231 232 String packageName = b.getString(KEY_PACKAGENAME); 233 Builder builder = new Builder(packageName); 234 235 String apkPath = b.getString(KEY_APKPATH); 236 if (apkPath != null) { 237 builder.setApkPath(apkPath); 238 } 239 240 String payloadConfigPath = b.getString(KEY_PAYLOADCONFIGPATH); 241 if (payloadConfigPath == null) { 242 builder.setPayloadBinaryName(b.getString(KEY_PAYLOADBINARYNAME)); 243 } else { 244 builder.setPayloadConfigPath(payloadConfigPath); 245 } 246 247 @DebugLevel int debugLevel = b.getInt(KEY_DEBUGLEVEL); 248 if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) { 249 throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel); 250 } 251 builder.setDebugLevel(debugLevel); 252 builder.setProtectedVm(b.getBoolean(KEY_PROTECTED_VM)); 253 long memoryBytes = b.getLong(KEY_MEMORY_BYTES); 254 if (memoryBytes != 0) { 255 builder.setMemoryBytes(memoryBytes); 256 } 257 builder.setCpuTopology(b.getInt(KEY_CPU_TOPOLOGY)); 258 long encryptedStorageBytes = b.getLong(KEY_ENCRYPTED_STORAGE_BYTES); 259 if (encryptedStorageBytes != 0) { 260 builder.setEncryptedStorageBytes(encryptedStorageBytes); 261 } 262 builder.setVmOutputCaptured(b.getBoolean(KEY_VM_OUTPUT_CAPTURED)); 263 264 return builder.build(); 265 } 266 267 /** Persists this config to a file. */ serialize(@onNull File file)268 void serialize(@NonNull File file) throws VirtualMachineException { 269 try (FileOutputStream output = new FileOutputStream(file)) { 270 serializeOutputStream(output); 271 } catch (IOException e) { 272 throw new VirtualMachineException("failed to write VM config", e); 273 } 274 } 275 276 /** Persists this config to a stream, for example a file. */ serializeOutputStream(@onNull OutputStream output)277 private void serializeOutputStream(@NonNull OutputStream output) throws IOException { 278 PersistableBundle b = new PersistableBundle(); 279 b.putInt(KEY_VERSION, VERSION); 280 if (mPackageName != null) { 281 b.putString(KEY_PACKAGENAME, mPackageName); 282 } 283 if (mApkPath != null) { 284 b.putString(KEY_APKPATH, mApkPath); 285 } 286 b.putString(KEY_PAYLOADCONFIGPATH, mPayloadConfigPath); 287 b.putString(KEY_PAYLOADBINARYNAME, mPayloadBinaryName); 288 b.putInt(KEY_DEBUGLEVEL, mDebugLevel); 289 b.putBoolean(KEY_PROTECTED_VM, mProtectedVm); 290 b.putInt(KEY_CPU_TOPOLOGY, mCpuTopology); 291 if (mMemoryBytes > 0) { 292 b.putLong(KEY_MEMORY_BYTES, mMemoryBytes); 293 } 294 if (mEncryptedStorageBytes > 0) { 295 b.putLong(KEY_ENCRYPTED_STORAGE_BYTES, mEncryptedStorageBytes); 296 } 297 b.putBoolean(KEY_VM_OUTPUT_CAPTURED, mVmOutputCaptured); 298 b.writeToStream(output); 299 } 300 301 /** 302 * Returns the absolute path of the APK which should contain the binary payload that will 303 * execute within the VM. Returns null if no specific path has been set. 304 * 305 * @hide 306 */ 307 @SystemApi 308 @Nullable getApkPath()309 public String getApkPath() { 310 return mApkPath; 311 } 312 313 /** 314 * Returns the path within the APK to the payload config file that defines software aspects of 315 * the VM. 316 * 317 * @hide 318 */ 319 @TestApi 320 @Nullable getPayloadConfigPath()321 public String getPayloadConfigPath() { 322 return mPayloadConfigPath; 323 } 324 325 /** 326 * Returns the name of the payload binary file, in the {@code lib/<ABI>} directory of the APK, 327 * that will be executed within the VM. 328 * 329 * @hide 330 */ 331 @SystemApi 332 @Nullable getPayloadBinaryName()333 public String getPayloadBinaryName() { 334 return mPayloadBinaryName; 335 } 336 337 /** 338 * Returns the debug level for the VM. 339 * 340 * @hide 341 */ 342 @SystemApi 343 @DebugLevel getDebugLevel()344 public int getDebugLevel() { 345 return mDebugLevel; 346 } 347 348 /** 349 * Returns whether the VM's memory will be protected from the host. 350 * 351 * @hide 352 */ 353 @SystemApi isProtectedVm()354 public boolean isProtectedVm() { 355 return mProtectedVm; 356 } 357 358 /** 359 * Returns the amount of RAM that will be made available to the VM, or 0 if the default size 360 * will be used. 361 * 362 * @hide 363 */ 364 @SystemApi 365 @IntRange(from = 0) getMemoryBytes()366 public long getMemoryBytes() { 367 return mMemoryBytes; 368 } 369 370 /** 371 * Returns the CPU topology configuration of the VM. 372 * 373 * @hide 374 */ 375 @SystemApi 376 @CpuTopology getCpuTopology()377 public int getCpuTopology() { 378 return mCpuTopology; 379 } 380 381 /** 382 * Returns whether encrypted storage is enabled or not. 383 * 384 * @hide 385 */ 386 @SystemApi isEncryptedStorageEnabled()387 public boolean isEncryptedStorageEnabled() { 388 return mEncryptedStorageBytes > 0; 389 } 390 391 /** 392 * Returns the size of encrypted storage (in bytes) available in the VM, or 0 if encrypted 393 * storage is not enabled 394 * 395 * @hide 396 */ 397 @SystemApi 398 @IntRange(from = 0) getEncryptedStorageBytes()399 public long getEncryptedStorageBytes() { 400 return mEncryptedStorageBytes; 401 } 402 403 /** 404 * Returns whether the app can read the VM console or log output. If not, the VM output is 405 * automatically forwarded to the host logcat. 406 * 407 * @see Builder#setVmOutputCaptured 408 * @hide 409 */ 410 @SystemApi isVmOutputCaptured()411 public boolean isVmOutputCaptured() { 412 return mVmOutputCaptured; 413 } 414 415 /** 416 * Tests if this config is compatible with other config. Being compatible means that the configs 417 * can be interchangeably used for the same virtual machine; they do not change the VM identity 418 * or secrets. Such changes include varying the number of CPUs or the size of the RAM. Changes 419 * that would alter the identity of the VM (e.g. using a different payload or changing the debug 420 * mode) are considered incompatible. 421 * 422 * @see VirtualMachine#setConfig 423 * @hide 424 */ 425 @SystemApi isCompatibleWith(@onNull VirtualMachineConfig other)426 public boolean isCompatibleWith(@NonNull VirtualMachineConfig other) { 427 if (this == other) { 428 return true; 429 } 430 return this.mDebugLevel == other.mDebugLevel 431 && this.mProtectedVm == other.mProtectedVm 432 && this.mEncryptedStorageBytes == other.mEncryptedStorageBytes 433 && this.mVmOutputCaptured == other.mVmOutputCaptured 434 && Objects.equals(this.mPayloadConfigPath, other.mPayloadConfigPath) 435 && Objects.equals(this.mPayloadBinaryName, other.mPayloadBinaryName) 436 && Objects.equals(this.mPackageName, other.mPackageName) 437 && Objects.equals(this.mApkPath, other.mApkPath); 438 } 439 440 /** 441 * Converts this config object into the parcelable type used when creating a VM via the 442 * virtualization service. Notice that the files are not passed as paths, but as file 443 * descriptors because the service doesn't accept paths as it might not have permission to open 444 * app-owned files and that could be abused to run a VM with software that the calling 445 * application doesn't own. 446 */ toVsConfig(@onNull PackageManager packageManager)447 VirtualMachineAppConfig toVsConfig(@NonNull PackageManager packageManager) 448 throws VirtualMachineException { 449 VirtualMachineAppConfig vsConfig = new VirtualMachineAppConfig(); 450 451 String apkPath = (mApkPath != null) ? mApkPath : findPayloadApk(packageManager); 452 453 try { 454 vsConfig.apk = ParcelFileDescriptor.open(new File(apkPath), MODE_READ_ONLY); 455 } catch (FileNotFoundException e) { 456 throw new VirtualMachineException("Failed to open APK", e); 457 } 458 if (mPayloadBinaryName != null) { 459 VirtualMachinePayloadConfig payloadConfig = new VirtualMachinePayloadConfig(); 460 payloadConfig.payloadBinaryName = mPayloadBinaryName; 461 vsConfig.payload = 462 VirtualMachineAppConfig.Payload.payloadConfig(payloadConfig); 463 } else { 464 vsConfig.payload = 465 VirtualMachineAppConfig.Payload.configPath(mPayloadConfigPath); 466 } 467 switch (mDebugLevel) { 468 case DEBUG_LEVEL_FULL: 469 vsConfig.debugLevel = VirtualMachineAppConfig.DebugLevel.FULL; 470 break; 471 default: 472 vsConfig.debugLevel = VirtualMachineAppConfig.DebugLevel.NONE; 473 break; 474 } 475 vsConfig.protectedVm = mProtectedVm; 476 vsConfig.memoryMib = bytesToMebiBytes(mMemoryBytes); 477 switch (mCpuTopology) { 478 case CPU_TOPOLOGY_MATCH_HOST: 479 vsConfig.cpuTopology = android.system.virtualizationservice.CpuTopology.MATCH_HOST; 480 break; 481 default: 482 vsConfig.cpuTopology = android.system.virtualizationservice.CpuTopology.ONE_CPU; 483 break; 484 } 485 // Don't allow apps to set task profiles ... at least for now. 486 vsConfig.taskProfiles = EMPTY_STRING_ARRAY; 487 return vsConfig; 488 } 489 findPayloadApk(PackageManager packageManager)490 private String findPayloadApk(PackageManager packageManager) throws VirtualMachineException { 491 ApplicationInfo appInfo; 492 try { 493 appInfo = 494 packageManager.getApplicationInfo( 495 mPackageName, PackageManager.ApplicationInfoFlags.of(0)); 496 } catch (PackageManager.NameNotFoundException e) { 497 throw new VirtualMachineException("Package not found", e); 498 } 499 500 String[] splitApkPaths = appInfo.splitSourceDirs; 501 String[] abis = Build.SUPPORTED_64_BIT_ABIS; 502 503 // If there are split APKs, and we know the payload binary name, see if we can find a 504 // split APK containing the binary. 505 if (mPayloadBinaryName != null && splitApkPaths != null && abis.length != 0) { 506 String[] libraryNames = new String[abis.length]; 507 for (int i = 0; i < abis.length; i++) { 508 libraryNames[i] = "lib/" + abis[i] + "/" + mPayloadBinaryName; 509 } 510 511 for (String path : splitApkPaths) { 512 try (ZipFile zip = new ZipFile(path)) { 513 for (String name : libraryNames) { 514 if (zip.getEntry(name) != null) { 515 Log.i(TAG, "Found payload in " + path); 516 return path; 517 } 518 } 519 } catch (IOException e) { 520 Log.w(TAG, "Failed to scan split APK: " + path, e); 521 } 522 } 523 } 524 525 // This really is the path to the APK, not a directory. 526 return appInfo.sourceDir; 527 } 528 bytesToMebiBytes(long mMemoryBytes)529 private int bytesToMebiBytes(long mMemoryBytes) { 530 long oneMebi = 1024 * 1024; 531 // We can't express requests for more than 2 exabytes, but then they're not going to succeed 532 // anyway. 533 if (mMemoryBytes > (Integer.MAX_VALUE - 1) * oneMebi) { 534 return Integer.MAX_VALUE; 535 } 536 return (int) ((mMemoryBytes + oneMebi - 1) / oneMebi); 537 } 538 539 /** 540 * A builder used to create a {@link VirtualMachineConfig}. 541 * 542 * @hide 543 */ 544 @SystemApi 545 public static final class Builder { 546 @Nullable private final String mPackageName; 547 @Nullable private String mApkPath; 548 @Nullable private String mPayloadConfigPath; 549 @Nullable private String mPayloadBinaryName; 550 @DebugLevel private int mDebugLevel = DEBUG_LEVEL_NONE; 551 private boolean mProtectedVm; 552 private boolean mProtectedVmSet; 553 private long mMemoryBytes; 554 @CpuTopology private int mCpuTopology = CPU_TOPOLOGY_ONE_CPU; 555 private long mEncryptedStorageBytes; 556 private boolean mVmOutputCaptured = false; 557 558 /** 559 * Creates a builder for the given context. 560 * 561 * @hide 562 */ 563 @SystemApi Builder(@onNull Context context)564 public Builder(@NonNull Context context) { 565 mPackageName = requireNonNull(context, "context must not be null").getPackageName(); 566 } 567 568 /** 569 * Creates a builder for a specific package. If packageName is null, {@link #setApkPath} 570 * must be called to specify the APK containing the payload. 571 */ Builder(@ullable String packageName)572 private Builder(@Nullable String packageName) { 573 mPackageName = packageName; 574 } 575 576 /** 577 * Builds an immutable {@link VirtualMachineConfig} 578 * 579 * @hide 580 */ 581 @SystemApi 582 @NonNull build()583 public VirtualMachineConfig build() { 584 String apkPath = null; 585 String packageName = null; 586 587 if (mApkPath != null) { 588 apkPath = mApkPath; 589 } else if (mPackageName != null) { 590 packageName = mPackageName; 591 } else { 592 // This should never happen, unless we're deserializing a bad config 593 throw new IllegalStateException("apkPath or packageName must be specified"); 594 } 595 596 if (mPayloadBinaryName == null) { 597 if (mPayloadConfigPath == null) { 598 throw new IllegalStateException("setPayloadBinaryName must be called"); 599 } 600 } else { 601 if (mPayloadConfigPath != null) { 602 throw new IllegalStateException( 603 "setPayloadBinaryName and setPayloadConfigPath may not both be called"); 604 } 605 } 606 607 if (!mProtectedVmSet) { 608 throw new IllegalStateException("setProtectedVm must be called explicitly"); 609 } 610 611 if (mVmOutputCaptured && mDebugLevel != DEBUG_LEVEL_FULL) { 612 throw new IllegalStateException("debug level must be FULL to capture output"); 613 } 614 615 return new VirtualMachineConfig( 616 packageName, 617 apkPath, 618 mPayloadConfigPath, 619 mPayloadBinaryName, 620 mDebugLevel, 621 mProtectedVm, 622 mMemoryBytes, 623 mCpuTopology, 624 mEncryptedStorageBytes, 625 mVmOutputCaptured); 626 } 627 628 /** 629 * Sets the absolute path of the APK containing the binary payload that will execute within 630 * the VM. If not set explicitly, defaults to the split APK containing the payload, if there 631 * is one, and otherwise the primary APK of the context. 632 * 633 * @hide 634 */ 635 @SystemApi 636 @NonNull setApkPath(@onNull String apkPath)637 public Builder setApkPath(@NonNull String apkPath) { 638 requireNonNull(apkPath, "apkPath must not be null"); 639 if (!apkPath.startsWith("/")) { 640 throw new IllegalArgumentException("APK path must be an absolute path"); 641 } 642 mApkPath = apkPath; 643 return this; 644 } 645 646 /** 647 * Sets the path within the APK to the payload config file that defines software aspects of 648 * the VM. The file is a JSON file; see 649 * packages/modules/Virtualization/microdroid/payload/config/src/lib.rs for the format. 650 * 651 * @hide 652 */ 653 @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) 654 @TestApi 655 @NonNull setPayloadConfigPath(@onNull String payloadConfigPath)656 public Builder setPayloadConfigPath(@NonNull String payloadConfigPath) { 657 mPayloadConfigPath = 658 requireNonNull(payloadConfigPath, "payloadConfigPath must not be null"); 659 return this; 660 } 661 662 /** 663 * Sets the name of the payload binary file that will be executed within the VM, e.g. 664 * "payload.so". The file must reside in the {@code lib/<ABI>} directory of the APK. 665 * 666 * <p>Note that VMs only support 64-bit code, even if the owning app is running as a 32-bit 667 * process. 668 * 669 * @hide 670 */ 671 @SystemApi 672 @NonNull setPayloadBinaryName(@onNull String payloadBinaryName)673 public Builder setPayloadBinaryName(@NonNull String payloadBinaryName) { 674 if (payloadBinaryName.contains(File.separator)) { 675 throw new IllegalArgumentException( 676 "Invalid binary file name: " + payloadBinaryName); 677 } 678 mPayloadBinaryName = 679 requireNonNull(payloadBinaryName, "payloadBinaryName must not be null"); 680 return this; 681 } 682 683 /** 684 * Sets the debug level. Defaults to {@link #DEBUG_LEVEL_NONE}. 685 * 686 * <p>If {@link #DEBUG_LEVEL_FULL} is set then logs from inside the VM are exported to the 687 * host and adb connections from the host are possible. This is convenient for debugging but 688 * may compromise the integrity of the VM - including bypassing the protections offered by a 689 * {@linkplain #setProtectedVm protected VM}. 690 * 691 * <p>Note that it isn't possible to {@linkplain #isCompatibleWith change} the debug level 692 * of a VM instance; debug and non-debug VMs always have different secrets. 693 * 694 * @hide 695 */ 696 @SystemApi 697 @NonNull setDebugLevel(@ebugLevel int debugLevel)698 public Builder setDebugLevel(@DebugLevel int debugLevel) { 699 if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) { 700 throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel); 701 } 702 mDebugLevel = debugLevel; 703 return this; 704 } 705 706 /** 707 * Sets whether to protect the VM memory from the host. No default is provided, this must be 708 * set explicitly. 709 * 710 * <p>Note that if debugging is {@linkplain #setDebugLevel enabled} for a protected VM, the 711 * VM is not truly protected - direct memory access by the host is prevented, but e.g. the 712 * debugger can be used to access the VM's internals. 713 * 714 * <p>It isn't possible to {@linkplain #isCompatibleWith change} the protected status of a 715 * VM instance; protected and non-protected VMs always have different secrets. 716 * 717 * @see VirtualMachineManager#getCapabilities 718 * @hide 719 */ 720 @SystemApi 721 @NonNull setProtectedVm(boolean protectedVm)722 public Builder setProtectedVm(boolean protectedVm) { 723 if (protectedVm) { 724 if (!HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) { 725 throw new UnsupportedOperationException( 726 "Protected VMs are not supported on this device."); 727 } 728 } else { 729 if (!HypervisorProperties.hypervisor_vm_supported().orElse(false)) { 730 throw new UnsupportedOperationException( 731 "Non-protected VMs are not supported on this device."); 732 } 733 } 734 mProtectedVm = protectedVm; 735 mProtectedVmSet = true; 736 return this; 737 } 738 739 /** 740 * Sets the amount of RAM to give the VM, in bytes. If not explicitly set then a default 741 * size will be used. 742 * 743 * @hide 744 */ 745 @SystemApi 746 @NonNull setMemoryBytes(@ntRangefrom = 1) long memoryBytes)747 public Builder setMemoryBytes(@IntRange(from = 1) long memoryBytes) { 748 if (memoryBytes <= 0) { 749 throw new IllegalArgumentException("Memory size must be positive"); 750 } 751 mMemoryBytes = memoryBytes; 752 return this; 753 } 754 755 /** 756 * Sets the CPU topology configuration of the VM. Defaults to {@link #CPU_TOPOLOGY_ONE_CPU}. 757 * 758 * <p>This determines how many virtual CPUs will be created, and their performance and 759 * scheduling characteristics, such as affinity masks. Topology also has an effect on memory 760 * usage as each vCPU requires additional memory to keep its state. 761 * 762 * @hide 763 */ 764 @SystemApi 765 @NonNull setCpuTopology(@puTopology int cpuTopology)766 public Builder setCpuTopology(@CpuTopology int cpuTopology) { 767 if (cpuTopology != CPU_TOPOLOGY_ONE_CPU && cpuTopology != CPU_TOPOLOGY_MATCH_HOST) { 768 throw new IllegalArgumentException("Invalid cpuTopology: " + cpuTopology); 769 } 770 mCpuTopology = cpuTopology; 771 return this; 772 } 773 774 /** 775 * Sets the size (in bytes) of encrypted storage available to the VM. If not set, no 776 * encrypted storage is provided. 777 * 778 * <p>The storage is encrypted with a key deterministically derived from the VM identity 779 * 780 * <p>The encrypted storage is persistent across VM reboots as well as device reboots. The 781 * backing file (containing encrypted data) is stored in the app's private data directory. 782 * 783 * <p>Note - There is no integrity guarantee or rollback protection on the storage in case 784 * the encrypted data is modified. 785 * 786 * <p>Deleting the VM will delete the encrypted data - there is no way to recover that data. 787 * 788 * @hide 789 */ 790 @SystemApi 791 @NonNull setEncryptedStorageBytes(@ntRangefrom = 1) long encryptedStorageBytes)792 public Builder setEncryptedStorageBytes(@IntRange(from = 1) long encryptedStorageBytes) { 793 if (encryptedStorageBytes <= 0) { 794 throw new IllegalArgumentException("Encrypted Storage size must be positive"); 795 } 796 mEncryptedStorageBytes = encryptedStorageBytes; 797 return this; 798 } 799 800 /** 801 * Sets whether to allow the app to read the VM outputs (console / log). Default is {@code 802 * false}. 803 * 804 * <p>By default, console and log outputs of a {@linkplain #setDebugLevel debuggable} VM are 805 * automatically forwarded to the host logcat. Setting this as {@code true} will allow the 806 * app to directly read {@linkplain VirtualMachine#getConsoleOutput console output} and 807 * {@linkplain VirtualMachine#getLogOutput log output}, instead of forwarding them to the 808 * host logcat. 809 * 810 * <p>If you turn on output capture, you must consume data from {@link 811 * VirtualMachine#getConsoleOutput} and {@link VirtualMachine#getLogOutput} - because 812 * otherwise the code in the VM may get blocked when the pipe buffer fills up. 813 * 814 * <p>The {@linkplain #setDebugLevel debug level} must be {@link #DEBUG_LEVEL_FULL} to be 815 * set as true. 816 * 817 * @hide 818 */ 819 @SystemApi 820 @NonNull setVmOutputCaptured(boolean captured)821 public Builder setVmOutputCaptured(boolean captured) { 822 mVmOutputCaptured = captured; 823 return this; 824 } 825 } 826 } 827