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 package com.android.microdroid.test; 17 18 import static com.google.common.truth.Truth.assertThat; 19 import static com.google.common.truth.TruthJUnit.assume; 20 21 import static org.junit.Assume.assumeNoException; 22 23 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; 24 25 import android.content.Context; 26 import android.os.Build; 27 import android.os.ParcelFileDescriptor; 28 import android.os.SystemProperties; 29 import android.sysprop.HypervisorProperties; 30 import android.system.virtualmachine.VirtualMachine; 31 import android.system.virtualmachine.VirtualMachineCallback; 32 import android.system.virtualmachine.VirtualMachineConfig; 33 import android.system.virtualmachine.VirtualMachineConfig.DebugLevel; 34 import android.system.virtualmachine.VirtualMachineException; 35 import android.system.virtualmachine.VirtualMachineManager; 36 37 import androidx.annotation.CallSuper; 38 import androidx.test.core.app.ApplicationProvider; 39 40 import com.android.microdroid.testservice.ITestService; 41 42 import org.junit.After; 43 import org.junit.Before; 44 import org.junit.Rule; 45 import org.junit.Test; 46 import org.junit.rules.Timeout; 47 import org.junit.runner.RunWith; 48 import org.junit.runners.Parameterized; 49 50 import java.io.ByteArrayInputStream; 51 import java.io.File; 52 import java.io.IOException; 53 import java.io.RandomAccessFile; 54 import java.nio.file.Files; 55 import java.util.List; 56 import java.util.OptionalLong; 57 import java.util.UUID; 58 import java.util.concurrent.CompletableFuture; 59 import java.util.concurrent.ExecutorService; 60 import java.util.concurrent.Executors; 61 import java.util.concurrent.TimeUnit; 62 63 import co.nstant.in.cbor.CborDecoder; 64 import co.nstant.in.cbor.CborException; 65 import co.nstant.in.cbor.model.Array; 66 import co.nstant.in.cbor.model.DataItem; 67 import co.nstant.in.cbor.model.MajorType; 68 69 @RunWith(Parameterized.class) 70 public class MicrodroidTests { 71 @Rule public Timeout globalTimeout = Timeout.seconds(300); 72 73 private static final String KERNEL_VERSION = SystemProperties.get("ro.kernel.version"); 74 private static final String PRODUCT_NAME = SystemProperties.get("ro.product.name"); 75 76 private static class Inner { 77 public boolean mProtectedVm; 78 public Context mContext; 79 public VirtualMachineManager mVmm; 80 public VirtualMachine mVm; 81 Inner(boolean protectedVm)82 Inner(boolean protectedVm) { 83 mProtectedVm = protectedVm; 84 } 85 86 /** Create a new VirtualMachineConfig.Builder with the parameterized protection mode. */ newVmConfigBuilder(String payloadConfigPath)87 public VirtualMachineConfig.Builder newVmConfigBuilder(String payloadConfigPath) { 88 return new VirtualMachineConfig.Builder(mContext, payloadConfigPath) 89 .protectedVm(mProtectedVm); 90 } 91 } 92 93 @Parameterized.Parameters(name = "protectedVm={0}") protectedVmConfigs()94 public static Object[] protectedVmConfigs() { 95 return new Object[] { false, true }; 96 } 97 98 @Parameterized.Parameter 99 public boolean mProtectedVm; 100 101 private boolean mPkvmSupported = false; 102 private Inner mInner; 103 104 @Before setup()105 public void setup() { 106 // In case when the virt APEX doesn't exist on the device, classes in the 107 // android.system.virtualmachine package can't be loaded. Therefore, before using the 108 // classes, check the existence of a class in the package and skip this test if not exist. 109 try { 110 Class.forName("android.system.virtualmachine.VirtualMachineManager"); 111 mPkvmSupported = true; 112 } catch (ClassNotFoundException e) { 113 assumeNoException(e); 114 return; 115 } 116 if (mProtectedVm) { 117 assume() 118 .withMessage("Skip where protected VMs aren't support") 119 .that(HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) 120 .isTrue(); 121 } else { 122 assume() 123 .withMessage("Skip where VMs aren't support") 124 .that(HypervisorProperties.hypervisor_vm_supported().orElse(false)) 125 .isTrue(); 126 } 127 mInner = new Inner(mProtectedVm); 128 mInner.mContext = ApplicationProvider.getApplicationContext(); 129 mInner.mVmm = VirtualMachineManager.getInstance(mInner.mContext); 130 } 131 132 @After cleanup()133 public void cleanup() throws VirtualMachineException { 134 if (!mPkvmSupported) { 135 return; 136 } 137 if (mInner == null) { 138 return; 139 } 140 if (mInner.mVm == null) { 141 return; 142 } 143 mInner.mVm.stop(); 144 mInner.mVm.delete(); 145 } 146 isCuttlefish()147 private boolean isCuttlefish() { 148 return (null != PRODUCT_NAME) 149 && (PRODUCT_NAME.startsWith("aosp_cf_x86") 150 || PRODUCT_NAME.startsWith("aosp_cf_arm") 151 || PRODUCT_NAME.startsWith("cf_x86") 152 || PRODUCT_NAME.startsWith("cf_arm")); 153 } 154 155 private abstract static class VmEventListener implements VirtualMachineCallback { 156 private ExecutorService mExecutorService = Executors.newSingleThreadExecutor(); 157 runToFinish(VirtualMachine vm)158 void runToFinish(VirtualMachine vm) throws VirtualMachineException, InterruptedException { 159 vm.setCallback(mExecutorService, this); 160 vm.run(); 161 mExecutorService.awaitTermination(300, TimeUnit.SECONDS); 162 } 163 forceStop(VirtualMachine vm)164 void forceStop(VirtualMachine vm) { 165 try { 166 vm.clearCallback(); 167 vm.stop(); 168 mExecutorService.shutdown(); 169 } catch (VirtualMachineException e) { 170 throw new RuntimeException(e); 171 } 172 } 173 174 @Override onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream)175 public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {} 176 177 @Override onPayloadReady(VirtualMachine vm)178 public void onPayloadReady(VirtualMachine vm) {} 179 180 @Override onPayloadFinished(VirtualMachine vm, int exitCode)181 public void onPayloadFinished(VirtualMachine vm, int exitCode) {} 182 183 @Override onError(VirtualMachine vm, int errorCode, String message)184 public void onError(VirtualMachine vm, int errorCode, String message) {} 185 186 @Override 187 @CallSuper onDied(VirtualMachine vm, @DeathReason int reason)188 public void onDied(VirtualMachine vm, @DeathReason int reason) { 189 mExecutorService.shutdown(); 190 } 191 } 192 193 private static final int MIN_MEM_ARM64 = 145; 194 private static final int MIN_MEM_X86_64 = 196; 195 196 @Test connectToVmService()197 public void connectToVmService() throws VirtualMachineException, InterruptedException { 198 assume() 199 .withMessage("SKip on 5.4 kernel. b/218303240") 200 .that(KERNEL_VERSION) 201 .isNotEqualTo("5.4"); 202 203 VirtualMachineConfig.Builder builder = 204 mInner.newVmConfigBuilder("assets/vm_config_extra_apk.json"); 205 if (Build.SUPPORTED_ABIS.length > 0) { 206 String primaryAbi = Build.SUPPORTED_ABIS[0]; 207 switch(primaryAbi) { 208 case "x86_64": 209 builder.memoryMib(MIN_MEM_X86_64); 210 break; 211 case "arm64-v8a": 212 builder.memoryMib(MIN_MEM_ARM64); 213 break; 214 } 215 } 216 VirtualMachineConfig config = builder.build(); 217 218 mInner.mVm = mInner.mVmm.getOrCreate("test_vm_extra_apk", config); 219 220 class TestResults { 221 Exception mException; 222 Integer mAddInteger; 223 String mAppRunProp; 224 String mSublibRunProp; 225 String mExtraApkTestProp; 226 } 227 final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>(); 228 final CompletableFuture<Boolean> payloadReady = new CompletableFuture<>(); 229 final TestResults testResults = new TestResults(); 230 VmEventListener listener = 231 new VmEventListener() { 232 private void testVMService(VirtualMachine vm) { 233 try { 234 ITestService testService = ITestService.Stub.asInterface( 235 vm.connectToVsockServer(ITestService.SERVICE_PORT).get()); 236 testResults.mAddInteger = testService.addInteger(123, 456); 237 testResults.mAppRunProp = 238 testService.readProperty("debug.microdroid.app.run"); 239 testResults.mSublibRunProp = 240 testService.readProperty("debug.microdroid.app.sublib.run"); 241 testResults.mExtraApkTestProp = 242 testService.readProperty("debug.microdroid.test.extra_apk"); 243 } catch (Exception e) { 244 testResults.mException = e; 245 } 246 } 247 248 @Override 249 public void onPayloadReady(VirtualMachine vm) { 250 payloadReady.complete(true); 251 testVMService(vm); 252 forceStop(vm); 253 } 254 255 @Override 256 public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) { 257 payloadStarted.complete(true); 258 } 259 }; 260 listener.runToFinish(mInner.mVm); 261 assertThat(payloadStarted.getNow(false)).isTrue(); 262 assertThat(payloadReady.getNow(false)).isTrue(); 263 assertThat(testResults.mException).isNull(); 264 assertThat(testResults.mAddInteger).isEqualTo(123 + 456); 265 assertThat(testResults.mAppRunProp).isEqualTo("true"); 266 assertThat(testResults.mSublibRunProp).isEqualTo("true"); 267 assertThat(testResults.mExtraApkTestProp).isEqualTo("PASS"); 268 } 269 270 @Test changingDebugLevelInvalidatesVmIdentity()271 public void changingDebugLevelInvalidatesVmIdentity() 272 throws VirtualMachineException, InterruptedException, IOException { 273 assume() 274 .withMessage("SKip on 5.4 kernel. b/218303240") 275 .that(KERNEL_VERSION) 276 .isNotEqualTo("5.4"); 277 278 VirtualMachineConfig.Builder builder = mInner.newVmConfigBuilder("assets/vm_config.json"); 279 VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build(); 280 mInner.mVm = mInner.mVmm.getOrCreate("test_vm", normalConfig); 281 VmEventListener listener = 282 new VmEventListener() { 283 @Override 284 public void onPayloadReady(VirtualMachine vm) { 285 forceStop(vm); 286 } 287 }; 288 listener.runToFinish(mInner.mVm); 289 290 // Launch the same VM with different debug level. The Java API prohibits this (thankfully). 291 // For testing, we do that by creating another VM with debug level, and copy the config file 292 // from the new VM directory to the old VM directory. 293 VirtualMachineConfig debugConfig = builder.debugLevel(DebugLevel.FULL).build(); 294 VirtualMachine newVm = mInner.mVmm.getOrCreate("test_debug_vm", debugConfig); 295 File vmRoot = new File(mInner.mContext.getFilesDir(), "vm"); 296 File newVmConfig = new File(new File(vmRoot, "test_debug_vm"), "config.xml"); 297 File oldVmConfig = new File(new File(vmRoot, "test_vm"), "config.xml"); 298 Files.copy(newVmConfig.toPath(), oldVmConfig.toPath(), REPLACE_EXISTING); 299 newVm.delete(); 300 mInner.mVm = mInner.mVmm.get("test_vm"); // re-load with the copied-in config file. 301 final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>(); 302 listener = 303 new VmEventListener() { 304 @Override 305 public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) { 306 payloadStarted.complete(true); 307 forceStop(vm); 308 } 309 }; 310 listener.runToFinish(mInner.mVm); 311 assertThat(payloadStarted.getNow(false)).isFalse(); 312 } 313 314 private class VmCdis { 315 public byte[] cdiAttest; 316 public byte[] cdiSeal; 317 } 318 launchVmAndGetCdis(String instanceName)319 private VmCdis launchVmAndGetCdis(String instanceName) 320 throws VirtualMachineException, InterruptedException { 321 VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder("assets/vm_config.json") 322 .debugLevel(DebugLevel.NONE) 323 .build(); 324 mInner.mVm = mInner.mVmm.getOrCreate(instanceName, normalConfig); 325 final VmCdis vmCdis = new VmCdis(); 326 final CompletableFuture<Exception> exception = new CompletableFuture<>(); 327 VmEventListener listener = 328 new VmEventListener() { 329 @Override 330 public void onPayloadReady(VirtualMachine vm) { 331 try { 332 ITestService testService = ITestService.Stub.asInterface( 333 vm.connectToVsockServer(ITestService.SERVICE_PORT).get()); 334 vmCdis.cdiAttest = testService.insecurelyExposeAttestationCdi(); 335 vmCdis.cdiSeal = testService.insecurelyExposeSealingCdi(); 336 forceStop(vm); 337 } catch (Exception e) { 338 exception.complete(e); 339 } 340 } 341 }; 342 listener.runToFinish(mInner.mVm); 343 assertThat(exception.getNow(null)).isNull(); 344 return vmCdis; 345 } 346 347 @Test instancesOfSameVmHaveDifferentCdis()348 public void instancesOfSameVmHaveDifferentCdis() 349 throws VirtualMachineException, InterruptedException { 350 assume() 351 .withMessage("SKip on 5.4 kernel. b/218303240") 352 .that(KERNEL_VERSION) 353 .isNotEqualTo("5.4"); 354 355 VmCdis vm_a_cdis = launchVmAndGetCdis("test_vm_a"); 356 VmCdis vm_b_cdis = launchVmAndGetCdis("test_vm_b"); 357 assertThat(vm_a_cdis.cdiAttest).isNotNull(); 358 assertThat(vm_b_cdis.cdiAttest).isNotNull(); 359 assertThat(vm_a_cdis.cdiAttest).isNotEqualTo(vm_b_cdis.cdiAttest); 360 assertThat(vm_a_cdis.cdiSeal).isNotNull(); 361 assertThat(vm_b_cdis.cdiSeal).isNotNull(); 362 assertThat(vm_a_cdis.cdiSeal).isNotEqualTo(vm_b_cdis.cdiSeal); 363 assertThat(vm_a_cdis.cdiAttest).isNotEqualTo(vm_b_cdis.cdiSeal); 364 } 365 366 @Test sameInstanceKeepsSameCdis()367 public void sameInstanceKeepsSameCdis() 368 throws VirtualMachineException, InterruptedException { 369 assume() 370 .withMessage("SKip on 5.4 kernel. b/218303240") 371 .that(KERNEL_VERSION) 372 .isNotEqualTo("5.4"); 373 assume().withMessage("Skip on CF. Too Slow. b/257270529").that(isCuttlefish()).isFalse(); 374 375 VmCdis first_boot_cdis = launchVmAndGetCdis("test_vm"); 376 VmCdis second_boot_cdis = launchVmAndGetCdis("test_vm"); 377 // The attestation CDI isn't specified to be stable, though it might be 378 assertThat(first_boot_cdis.cdiSeal).isNotNull(); 379 assertThat(second_boot_cdis.cdiSeal).isNotNull(); 380 assertThat(first_boot_cdis.cdiSeal).isEqualTo(second_boot_cdis.cdiSeal); 381 } 382 383 @Test bccIsSuperficiallyWellFormed()384 public void bccIsSuperficiallyWellFormed() 385 throws VirtualMachineException, InterruptedException, CborException { 386 assume() 387 .withMessage("SKip on 5.4 kernel. b/218303240") 388 .that(KERNEL_VERSION) 389 .isNotEqualTo("5.4"); 390 391 VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder("assets/vm_config.json") 392 .debugLevel(DebugLevel.NONE) 393 .build(); 394 mInner.mVm = mInner.mVmm.getOrCreate("bcc_vm", normalConfig); 395 final VmCdis vmCdis = new VmCdis(); 396 final CompletableFuture<byte[]> bcc = new CompletableFuture<>(); 397 final CompletableFuture<Exception> exception = new CompletableFuture<>(); 398 VmEventListener listener = 399 new VmEventListener() { 400 @Override 401 public void onPayloadReady(VirtualMachine vm) { 402 try { 403 ITestService testService = ITestService.Stub.asInterface( 404 vm.connectToVsockServer(ITestService.SERVICE_PORT).get()); 405 bcc.complete(testService.getBcc()); 406 forceStop(vm); 407 } catch (Exception e) { 408 exception.complete(e); 409 } 410 } 411 }; 412 listener.runToFinish(mInner.mVm); 413 byte[] bccBytes = bcc.getNow(null); 414 assertThat(exception.getNow(null)).isNull(); 415 assertThat(bccBytes).isNotNull(); 416 417 ByteArrayInputStream bais = new ByteArrayInputStream(bccBytes); 418 List<DataItem> dataItems = new CborDecoder(bais).decode(); 419 assertThat(dataItems.size()).isEqualTo(1); 420 assertThat(dataItems.get(0).getMajorType()).isEqualTo(MajorType.ARRAY); 421 List<DataItem> rootArrayItems = ((Array) dataItems.get(0)).getDataItems(); 422 assertThat(rootArrayItems.size()).isAtLeast(2); // Public key and one certificate 423 if (mProtectedVm) { 424 // When a true BCC is created, microdroid expects entries for at least: the root public 425 // key, pvmfw, u-boot, u-boot-env, microdroid, app payload and the service process. 426 assertThat(rootArrayItems.size()).isAtLeast(7); 427 } 428 } 429 430 private static final UUID MICRODROID_PARTITION_UUID = 431 UUID.fromString("cf9afe9a-0662-11ec-a329-c32663a09d75"); 432 private static final UUID U_BOOT_AVB_PARTITION_UUID = 433 UUID.fromString("7e8221e7-03e6-4969-948b-73a4c809a4f2"); 434 private static final UUID U_BOOT_ENV_PARTITION_UUID = 435 UUID.fromString("0ab72d30-86ae-4d05-81b2-c1760be2b1f9"); 436 private static final UUID PVM_FW_PARTITION_UUID = 437 UUID.fromString("90d2174a-038a-4bc6-adf3-824848fc5825"); 438 private static final long BLOCK_SIZE = 512; 439 440 // Find the starting offset which holds the data of a partition having UUID. 441 // This is a kind of hack; rather than parsing QCOW2 we exploit the fact that the cluster size 442 // is normally greater than 512. It implies that the partition data should exist at a block 443 // which follows the header block findPartitionDataOffset(RandomAccessFile file, UUID uuid)444 private OptionalLong findPartitionDataOffset(RandomAccessFile file, UUID uuid) 445 throws IOException { 446 // For each 512-byte block in file, check header 447 long fileSize = file.length(); 448 449 for (long idx = 0; idx + BLOCK_SIZE < fileSize; idx += BLOCK_SIZE) { 450 file.seek(idx); 451 long high = file.readLong(); 452 long low = file.readLong(); 453 if (uuid.equals(new UUID(high, low))) return OptionalLong.of(idx + BLOCK_SIZE); 454 } 455 return OptionalLong.empty(); 456 } 457 flipBit(RandomAccessFile file, long offset)458 private void flipBit(RandomAccessFile file, long offset) throws IOException { 459 file.seek(offset); 460 int b = file.readByte(); 461 file.seek(offset); 462 file.writeByte(b ^ 1); 463 } 464 tryBootVm(String vmName)465 private boolean tryBootVm(String vmName) 466 throws VirtualMachineException, InterruptedException { 467 mInner.mVm = mInner.mVmm.get(vmName); // re-load the vm before running tests 468 final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>(); 469 VmEventListener listener = 470 new VmEventListener() { 471 @Override 472 public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) { 473 payloadStarted.complete(true); 474 forceStop(vm); 475 } 476 }; 477 listener.runToFinish(mInner.mVm); 478 return payloadStarted.getNow(false); 479 } 480 prepareInstanceImage(String vmName)481 private RandomAccessFile prepareInstanceImage(String vmName) 482 throws VirtualMachineException, InterruptedException, IOException { 483 VirtualMachineConfig config = mInner.newVmConfigBuilder("assets/vm_config.json") 484 .debugLevel(DebugLevel.NONE) 485 .build(); 486 487 // Remove any existing VM so we can start from scratch 488 VirtualMachine oldVm = mInner.mVmm.getOrCreate(vmName, config); 489 oldVm.delete(); 490 mInner.mVmm.getOrCreate(vmName, config); 491 492 assertThat(tryBootVm(vmName)).isTrue(); 493 494 File vmRoot = new File(mInner.mContext.getFilesDir(), "vm"); 495 File vmDir = new File(vmRoot, vmName); 496 File instanceImgPath = new File(vmDir, "instance.img"); 497 return new RandomAccessFile(instanceImgPath, "rw"); 498 499 } 500 assertThatPartitionIsMissing(UUID partitionUuid)501 private void assertThatPartitionIsMissing(UUID partitionUuid) 502 throws VirtualMachineException, InterruptedException, IOException { 503 RandomAccessFile instanceFile = prepareInstanceImage("test_vm_integrity"); 504 assertThat(findPartitionDataOffset(instanceFile, partitionUuid).isPresent()) 505 .isFalse(); 506 } 507 508 // Flips a bit of given partition, and then see if boot fails. assertThatBootFailsAfterCompromisingPartition(UUID partitionUuid)509 private void assertThatBootFailsAfterCompromisingPartition(UUID partitionUuid) 510 throws VirtualMachineException, InterruptedException, IOException { 511 RandomAccessFile instanceFile = prepareInstanceImage("test_vm_integrity"); 512 OptionalLong offset = findPartitionDataOffset(instanceFile, partitionUuid); 513 assertThat(offset.isPresent()).isTrue(); 514 515 flipBit(instanceFile, offset.getAsLong()); 516 assertThat(tryBootVm("test_vm_integrity")).isFalse(); 517 } 518 519 @Test bootFailsWhenMicrodroidDataIsCompromised()520 public void bootFailsWhenMicrodroidDataIsCompromised() 521 throws VirtualMachineException, InterruptedException, IOException { 522 assume().withMessage("Skip on CF. Too Slow. b/257270529").that(isCuttlefish()).isFalse(); 523 524 assertThatBootFailsAfterCompromisingPartition(MICRODROID_PARTITION_UUID); 525 } 526 527 @Test bootFailsWhenUBootAvbDataIsCompromised()528 public void bootFailsWhenUBootAvbDataIsCompromised() 529 throws VirtualMachineException, InterruptedException, IOException { 530 if (mProtectedVm) { 531 assertThatBootFailsAfterCompromisingPartition(U_BOOT_AVB_PARTITION_UUID); 532 } else { 533 // non-protected VM shouldn't have u-boot avb data 534 assertThatPartitionIsMissing(U_BOOT_AVB_PARTITION_UUID); 535 } 536 } 537 538 @Test bootFailsWhenUBootEnvDataIsCompromised()539 public void bootFailsWhenUBootEnvDataIsCompromised() 540 throws VirtualMachineException, InterruptedException, IOException { 541 if (mProtectedVm) { 542 assertThatBootFailsAfterCompromisingPartition(U_BOOT_ENV_PARTITION_UUID); 543 } else { 544 // non-protected VM shouldn't have u-boot env data 545 assertThatPartitionIsMissing(U_BOOT_ENV_PARTITION_UUID); 546 } 547 } 548 549 @Test bootFailsWhenPvmFwDataIsCompromised()550 public void bootFailsWhenPvmFwDataIsCompromised() 551 throws VirtualMachineException, InterruptedException, IOException { 552 if (mProtectedVm) { 553 assertThatBootFailsAfterCompromisingPartition(PVM_FW_PARTITION_UUID); 554 } else { 555 // non-protected VM shouldn't have pvmfw data 556 assertThatPartitionIsMissing(PVM_FW_PARTITION_UUID); 557 } 558 } 559 } 560