Allocate each VM an instance_id

Introduce a 64 bytes' long instance_id. When the VM is created, this is
requested from virtualization service, which randomly allocates it.
While it does that, it also logs the user & the package name.

The app persists this allocated instance_id in a file `instance_id` in
its directory for the vm instance (along with instance.img &
storage.img). When the VirtualMachine is run, this is an input into the
VM via DT.

This patch modifies Compos & vm binary to work with the instance_id.

flagging: instance_id allocation request is conditional to flag build
time flag llpvm_changes, no file `instance_id` is created if the flag is
off. `instanceId` is all 0s if the flag is off.

Bug: 291213394
Test: atest MicrodroidHostTest
Test: atest MicrodroidTests
Test: atest ComposHostTestCases
Test: Look for instance_id logged by VS

Change-Id: Ie8e25b9510e27362d4580c55c1bd557143ff7d0e
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index abaa74c..077a0ef 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -72,6 +72,7 @@
     /// Start a new CompOS VM instance using the specified instance image file and parameters.
     pub fn start(
         service: &dyn IVirtualizationService,
+        instance_id: [u8; 64],
         instance_image: File,
         idsig: &Path,
         idsig_manifest_apk: &Path,
@@ -121,6 +122,7 @@
             name: parameters.name.clone(),
             apk: Some(apk_fd),
             idsig: Some(idsig_fd),
+            instanceId: instance_id,
             instanceImage: Some(instance_fd),
             encryptedStorageImage: None,
             payload: Payload::ConfigPath(config_path),
diff --git a/compos/common/lib.rs b/compos/common/lib.rs
index 1f937c9..9d90717 100644
--- a/compos/common/lib.rs
+++ b/compos/common/lib.rs
@@ -39,6 +39,9 @@
 /// tests.
 pub const TEST_INSTANCE_DIR: &str = "test";
 
+/// The file that holds the instance_id of CompOS instance.
+pub const INSTANCE_ID_FILE: &str = "instance_id";
+
 /// The file that holds the instance image for a CompOS instance.
 pub const INSTANCE_IMAGE_FILE: &str = "instance.img";
 
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
index 457520f..76001a4 100644
--- a/compos/composd/src/instance_starter.rs
+++ b/compos/composd/src/instance_starter.rs
@@ -20,13 +20,13 @@
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
     IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
 };
-use anyhow::{Context, Result};
+use anyhow::{anyhow, Context, Result};
 use binder::{LazyServiceGuard, ParcelFileDescriptor, Strong};
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
 use compos_common::compos_client::{ComposClient, VmParameters};
 use compos_common::{
     COMPOS_DATA_ROOT, IDSIG_FILE, IDSIG_MANIFEST_APK_FILE, IDSIG_MANIFEST_EXT_APK_FILE,
-    INSTANCE_IMAGE_FILE,
+    INSTANCE_ID_FILE, INSTANCE_IMAGE_FILE,
 };
 use log::info;
 use std::fs;
@@ -66,6 +66,7 @@
 pub struct InstanceStarter {
     instance_name: String,
     instance_root: PathBuf,
+    instance_id_file: PathBuf,
     instance_image: PathBuf,
     idsig: PathBuf,
     idsig_manifest_apk: PathBuf,
@@ -77,6 +78,7 @@
     pub fn new(instance_name: &str, vm_parameters: VmParameters) -> Self {
         let instance_root = Path::new(COMPOS_DATA_ROOT).join(instance_name);
         let instance_root_path = instance_root.as_path();
+        let instance_id_file = instance_root_path.join(INSTANCE_ID_FILE);
         let instance_image = instance_root_path.join(INSTANCE_IMAGE_FILE);
         let idsig = instance_root_path.join(IDSIG_FILE);
         let idsig_manifest_apk = instance_root_path.join(IDSIG_MANIFEST_APK_FILE);
@@ -84,6 +86,7 @@
         Self {
             instance_name: instance_name.to_owned(),
             instance_root,
+            instance_id_file,
             instance_image,
             idsig,
             idsig_manifest_apk,
@@ -103,7 +106,10 @@
         // Overwrite any existing instance - it's unlikely to be valid with the current set
         // of APEXes, and finding out it isn't is much more expensive than creating a new one.
         self.create_instance_image(virtualization_service)?;
-
+        // TODO(b/294177871): Ping VS to delete the old instance's secret.
+        if cfg!(llpvm_changes) {
+            self.allocate_instance_id(virtualization_service)?;
+        }
         // Delete existing idsig files. Ignore error in case idsig doesn't exist.
         let _ignored1 = fs::remove_file(&self.idsig);
         let _ignored2 = fs::remove_file(&self.idsig_manifest_apk);
@@ -122,6 +128,14 @@
         &self,
         virtualization_service: &dyn IVirtualizationService,
     ) -> Result<CompOsInstance> {
+        let instance_id: [u8; 64] = if cfg!(llpvm_changes) {
+            fs::read(&self.instance_id_file)?
+                .try_into()
+                .map_err(|_| anyhow!("Failed to get instance_id"))?
+        } else {
+            [0u8; 64]
+        };
+
         let instance_image = fs::OpenOptions::new()
             .read(true)
             .write(true)
@@ -129,6 +143,7 @@
             .context("Failed to open instance image")?;
         let vm_instance = ComposClient::start(
             virtualization_service,
+            instance_id,
             instance_image,
             &self.idsig,
             &self.idsig_manifest_apk,
@@ -164,4 +179,13 @@
             .context("Writing instance image file")?;
         Ok(())
     }
+
+    fn allocate_instance_id(
+        &self,
+        virtualization_service: &dyn IVirtualizationService,
+    ) -> Result<()> {
+        let id = virtualization_service.allocateInstanceId().context("Allocating Instance Id")?;
+        fs::write(&self.instance_id_file, id)?;
+        Ok(())
+    }
 }
diff --git a/compos/verify/verify.rs b/compos/verify/verify.rs
index 567083d..a3f18d5 100644
--- a/compos/verify/verify.rs
+++ b/compos/verify/verify.rs
@@ -18,7 +18,7 @@
 //!  public key. The tool is intended to be run by odsign during boot.
 
 use android_logger::LogId;
-use anyhow::{bail, Context, Result};
+use anyhow::{anyhow, bail, Context, Result};
 use binder::ProcessState;
 use clap::{Parser, ValueEnum};
 use compos_common::compos_client::{ComposClient, VmCpuTopology, VmParameters};
@@ -28,9 +28,10 @@
 };
 use compos_common::{
     COMPOS_DATA_ROOT, CURRENT_INSTANCE_DIR, IDSIG_FILE, IDSIG_MANIFEST_APK_FILE,
-    IDSIG_MANIFEST_EXT_APK_FILE, INSTANCE_IMAGE_FILE, TEST_INSTANCE_DIR,
+    IDSIG_MANIFEST_EXT_APK_FILE, INSTANCE_ID_FILE, INSTANCE_IMAGE_FILE, TEST_INSTANCE_DIR,
 };
 use log::error;
+use std::fs;
 use std::fs::File;
 use std::io::Read;
 use std::panic;
@@ -90,11 +91,17 @@
         bail!("{:?} is not a directory", instance_dir);
     }
 
+    let instance_id_file = instance_dir.join(INSTANCE_ID_FILE);
     let instance_image = instance_dir.join(INSTANCE_IMAGE_FILE);
     let idsig = instance_dir.join(IDSIG_FILE);
     let idsig_manifest_apk = instance_dir.join(IDSIG_MANIFEST_APK_FILE);
     let idsig_manifest_ext_apk = instance_dir.join(IDSIG_MANIFEST_EXT_APK_FILE);
 
+    let instance_id: [u8; 64] = if cfg!(llpvm_changes) {
+        fs::read(instance_id_file)?.try_into().map_err(|_| anyhow!("Failed to get instance_id"))?
+    } else {
+        [0u8; 64]
+    };
     let instance_image = File::open(instance_image).context("Failed to open instance image")?;
 
     let info = artifacts_dir.join("compos.info");
@@ -110,6 +117,7 @@
     let virtualization_service = virtmgr.connect()?;
     let vm_instance = ComposClient::start(
         &*virtualization_service,
+        instance_id,
         instance_image,
         &idsig,
         &idsig_manifest_apk,
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachine.java b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
index 6b03cfe..9a38acf 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachine.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
@@ -190,6 +190,9 @@
     /** Name of the instance image file for a VM. (Not implemented) */
     private static final String INSTANCE_IMAGE_FILE = "instance.img";
 
+    /** Name of the file for a VM containing Id. */
+    private static final String INSTANCE_ID_FILE = "instance_id";
+
     /** Name of the idsig file for a VM */
     private static final String IDSIG_FILE = "idsig";
 
@@ -227,6 +230,9 @@
     /** File that backs the encrypted storage - Will be null if not enabled. */
     @Nullable private final File mEncryptedStoreFilePath;
 
+    /** File that contains the Id. This is NULL iff FEATURE_LLPVM is disabled */
+    @Nullable private final File mInstanceIdPath;
+
     /**
      * Unmodifiable list of extra apks. Apks are specified by the vm config, and corresponding
      * idsigs are to be generated.
@@ -376,6 +382,16 @@
         File thisVmDir = getVmDir(context, mName);
         mVmRootPath = thisVmDir;
         mConfigFilePath = new File(thisVmDir, CONFIG_FILE);
+        try {
+            mInstanceIdPath =
+                    (mVirtualizationService
+                                    .getBinder()
+                                    .isFeatureEnabled(IVirtualizationService.FEATURE_LLPVM_CHANGES))
+                            ? new File(thisVmDir, INSTANCE_ID_FILE)
+                            : null;
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
         mInstanceFilePath = new File(thisVmDir, INSTANCE_IMAGE_FILE);
         mIdsigFilePath = new File(thisVmDir, IDSIG_FILE);
         mExtraApks = setupExtraApks(context, config, thisVmDir);
@@ -414,6 +430,10 @@
                 VirtualMachineConfig config = VirtualMachineConfig.from(vmDescriptor.getConfigFd());
                 vm = new VirtualMachine(context, name, config, VirtualizationService.getInstance());
                 config.serialize(vm.mConfigFilePath);
+                if (vm.mInstanceIdPath != null) {
+                    vm.importInstanceIdFrom(vmDescriptor.getInstanceIdFd());
+                }
+
                 try {
                     vm.mInstanceFilePath.createNewFile();
                 } catch (IOException e) {
@@ -475,6 +495,21 @@
 
             IVirtualizationService service = vm.mVirtualizationService.getBinder();
 
+            if (vm.mInstanceIdPath != null) {
+                try (FileOutputStream stream = new FileOutputStream(vm.mInstanceIdPath)) {
+                    byte[] id = service.allocateInstanceId();
+                    stream.write(id);
+                } catch (FileNotFoundException e) {
+                    throw new VirtualMachineException("instance_id file missing", e);
+                } catch (IOException e) {
+                    throw new VirtualMachineException("failed to persist instance_id", e);
+                } catch (RemoteException e) {
+                    throw e.rethrowAsRuntimeException();
+                } catch (ServiceSpecificException | IllegalArgumentException e) {
+                    throw new VirtualMachineException("failed to create instance_id", e);
+                }
+            }
+
             try {
                 service.initializeWritablePartition(
                         ParcelFileDescriptor.open(vm.mInstanceFilePath, MODE_READ_WRITE),
@@ -530,6 +565,9 @@
         VirtualMachine vm =
                 new VirtualMachine(context, name, config, VirtualizationService.getInstance());
 
+        if (vm.mInstanceIdPath != null && !vm.mInstanceIdPath.exists()) {
+            throw new VirtualMachineException("instance_id file missing");
+        }
         if (!vm.mInstanceFilePath.exists()) {
             throw new VirtualMachineException("instance image missing");
         }
@@ -547,6 +585,7 @@
             // if a new VM is created with the same name (and files) that's unrelated.
             mWasDeleted = true;
         }
+        // TODO(b/294177871): Request deletion of VM secrets.
         deleteVmDirectory(context, name);
     }
 
@@ -814,6 +853,18 @@
                 VirtualMachineAppConfig appConfig =
                         vmConfig.toVsConfig(mContext.getPackageManager());
                 appConfig.name = mName;
+                if (mInstanceIdPath != null) {
+                    appConfig.instanceId = Files.readAllBytes(mInstanceIdPath.toPath());
+                    appConfig.instanceImage =
+                            ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE);
+                } else {
+                    // FEATURE_LLPVM_CHANGES is disabled, instance_id is not used.
+                    appConfig.instanceId = new byte[64];
+                }
+                if (mEncryptedStoreFilePath != null) {
+                    appConfig.encryptedStorageImage =
+                            ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_WRITE);
+                }
 
                 if (!vmConfig.getExtraApks().isEmpty()) {
                     // Extra APKs were specified directly, rather than via config file.
@@ -835,7 +886,7 @@
                 }
 
                 try {
-                    createIdSigs(service, appConfig);
+                    createIdSigsAndUpdateConfig(service, appConfig);
                 } catch (FileNotFoundException e) {
                     throw new VirtualMachineException("Failed to generate APK signature", e);
                 }
@@ -850,6 +901,8 @@
                 mVirtualMachine.registerCallback(new CallbackTranslator(service));
                 mContext.registerComponentCallbacks(mMemoryManagementCallbacks);
                 mVirtualMachine.start();
+            } catch (IOException e) {
+                throw new VirtualMachineException("failed to persist files", e);
             } catch (IllegalStateException | ServiceSpecificException e) {
                 throw new VirtualMachineException(e);
             } catch (RemoteException e) {
@@ -858,7 +911,8 @@
         }
     }
 
-    private void createIdSigs(IVirtualizationService service, VirtualMachineAppConfig appConfig)
+    private void createIdSigsAndUpdateConfig(
+            IVirtualizationService service, VirtualMachineAppConfig appConfig)
             throws RemoteException, FileNotFoundException {
         // Fill the idsig file by hashing the apk
         service.createOrUpdateIdsigFile(
@@ -872,11 +926,6 @@
 
         // Re-open idsig files in read-only mode
         appConfig.idsig = ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_ONLY);
-        appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE);
-        if (mEncryptedStoreFilePath != null) {
-            appConfig.encryptedStorageImage =
-                    ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_WRITE);
-        }
         List<ParcelFileDescriptor> extraIdsigs = new ArrayList<>();
         for (ExtraApkSpec extraApk : mExtraApks) {
             extraIdsigs.add(ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_ONLY));
@@ -1249,6 +1298,7 @@
             try {
                 return new VirtualMachineDescriptor(
                         ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY),
+                        ParcelFileDescriptor.open(mInstanceIdPath, MODE_READ_ONLY),
                         ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY),
                         mEncryptedStoreFilePath != null
                                 ? ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_ONLY)
@@ -1384,6 +1434,16 @@
         return extraApks;
     }
 
+    private void importInstanceIdFrom(@NonNull ParcelFileDescriptor instanceIdFd)
+            throws VirtualMachineException {
+        try (FileChannel idOutput = new FileOutputStream(mInstanceIdPath).getChannel();
+                FileChannel idInput = new AutoCloseInputStream(instanceIdFd).getChannel()) {
+            idOutput.transferFrom(idInput, /*position=*/ 0, idInput.size());
+        } catch (IOException e) {
+            throw new VirtualMachineException("failed to copy instance_id", e);
+        }
+    }
+
     private void importInstanceFrom(@NonNull ParcelFileDescriptor instanceFd)
             throws VirtualMachineException {
         try (FileChannel instance = new FileOutputStream(mInstanceFilePath).getChannel();
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineDescriptor.java b/java/framework/src/android/system/virtualmachine/VirtualMachineDescriptor.java
index 710925d..ee79256 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineDescriptor.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineDescriptor.java
@@ -40,6 +40,9 @@
 public final class VirtualMachineDescriptor implements Parcelable, AutoCloseable {
     private volatile boolean mClosed = false;
     @NonNull private final ParcelFileDescriptor mConfigFd;
+    // File descriptor of the file containing the instance id - will be null iff
+    // FEATURE_LLPVM_CHANGES is disabled.
+    @Nullable private final ParcelFileDescriptor mInstanceIdFd;
     @NonNull private final ParcelFileDescriptor mInstanceImgFd;
     // File descriptor of the image backing the encrypted storage - Will be null if encrypted
     // storage is not enabled. */
@@ -54,6 +57,7 @@
     public void writeToParcel(@NonNull Parcel out, int flags) {
         checkNotClosed();
         out.writeParcelable(mConfigFd, flags);
+        out.writeParcelable(mInstanceIdFd, flags);
         out.writeParcelable(mInstanceImgFd, flags);
         out.writeParcelable(mEncryptedStoreFd, flags);
     }
@@ -80,6 +84,15 @@
     }
 
     /**
+     * @return File descriptor of the file containing instance_id of the VM.
+     */
+    @Nullable
+    ParcelFileDescriptor getInstanceIdFd() {
+        checkNotClosed();
+        return mInstanceIdFd;
+    }
+
+    /**
      * @return File descriptor of the instance.img of the VM.
      */
     @NonNull
@@ -100,15 +113,18 @@
 
     VirtualMachineDescriptor(
             @NonNull ParcelFileDescriptor configFd,
+            @Nullable ParcelFileDescriptor instanceIdFd,
             @NonNull ParcelFileDescriptor instanceImgFd,
             @Nullable ParcelFileDescriptor encryptedStoreFd) {
         mConfigFd = requireNonNull(configFd);
+        mInstanceIdFd = instanceIdFd;
         mInstanceImgFd = requireNonNull(instanceImgFd);
         mEncryptedStoreFd = encryptedStoreFd;
     }
 
     private VirtualMachineDescriptor(Parcel in) {
         mConfigFd = requireNonNull(readParcelFileDescriptor(in));
+        mInstanceIdFd = readParcelFileDescriptor(in);
         mInstanceImgFd = requireNonNull(readParcelFileDescriptor(in));
         mEncryptedStoreFd = readParcelFileDescriptor(in);
     }
@@ -127,6 +143,7 @@
         mClosed = true;
         // Let the compiler do the work: close everything, throw if any of them fail, skipping null.
         try (mConfigFd;
+                mInstanceIdFd;
                 mInstanceImgFd;
                 mEncryptedStoreFd) {
         } catch (IOException ignored) {
diff --git a/microdroid/README.md b/microdroid/README.md
index c82ef0b..6e7b20c 100644
--- a/microdroid/README.md
+++ b/microdroid/README.md
@@ -107,6 +107,7 @@
 PATH_TO_YOUR_APP \
 $TEST_ROOT/MyApp.apk.idsig \
 $TEST_ROOT/instance.img \
+--instance-id-file $TEST_ROOT/instance_id \
 --payload-binary-name MyMicrodroidPayload.so
 ```
 
diff --git a/service_vm/demo_apk/README.md b/service_vm/demo_apk/README.md
index 551d47b..8f67f6e 100644
--- a/service_vm/demo_apk/README.md
+++ b/service_vm/demo_apk/README.md
@@ -42,7 +42,9 @@
   --config-path assets/config.json --debug full \
   $(adb shell pm path com.android.virt.vm_attestation.demo | cut -c 9-) \
   $TEST_ROOT/VmAttestationDemoApp.apk.idsig \
-  $TEST_ROOT/instance.vm_attestation.debug.img --protected
+  $TEST_ROOT/instance.vm_attestation.debug.img \
+  --instance-id-file $TEST_ROOT/instance_id \
+  --protected
 ```
 
 Please note that remote attestation is only available for protected VMs.
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index 9e19b7d..2abf110 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -52,6 +52,7 @@
     private static final int TEST_VM_ADB_PORT = 8000;
     private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT;
     private static final String INSTANCE_IMG = "instance.img";
+    protected static final String VIRT_APEX = "/apex/com.android.virt/";
 
     private static final long MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES = 5;
     protected static final long MICRODROID_COMMAND_TIMEOUT_MILLIS = 30000;
@@ -186,6 +187,12 @@
         return ret;
     }
 
+    public boolean isFeatureEnabled(String feature) throws Exception {
+        CommandRunner android = new CommandRunner(getDevice());
+        String result = android.run(VIRT_APEX + "bin/vm", "check-feature-enabled", feature);
+        return result.contains("enabled");
+    }
+
     public List<String> getAssignableDevices() throws Exception {
         return parseStringArrayFieldsFromVmInfo("Assignable devices: ");
     }
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 4503cd3..9b95461 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -616,6 +616,7 @@
         final String apkPath = getPathForPackage(PACKAGE_NAME);
         final String idsigPath = TEST_ROOT + "idsig";
         final String instanceImgPath = TEST_ROOT + "instance.img";
+        final String instanceIdPath = TEST_ROOT + "instance_id";
         List<String> cmd =
                 new ArrayList<>(
                         Arrays.asList(
@@ -626,6 +627,11 @@
                                 apkPath,
                                 idsigPath,
                                 instanceImgPath));
+        if (isFeatureEnabled("com.android.kvm.LLPVM_CHANGES")) {
+            cmd.add("--instance-id-file");
+            cmd.add(instanceIdPath);
+        }
+        ;
         if (protectedVm) {
             cmd.add("--protected");
         }
@@ -886,7 +892,6 @@
         final String apkPath = getPathForPackage(PACKAGE_NAME);
         final String idSigPath = TEST_ROOT + "idsig";
         android.run(VIRT_APEX + "bin/vm", "create-idsig", apkPath, idSigPath);
-
         // Create the instance image for the VM
         final String instanceImgPath = TEST_ROOT + "instance.img";
         android.run(
@@ -896,17 +901,22 @@
                 instanceImgPath,
                 Integer.toString(10 * 1024 * 1024));
 
-        final String ret =
-                android.runForResult(
+        List<String> cmd =
+                new ArrayList<>(
+                        Arrays.asList(
                                 VIRT_APEX + "bin/vm",
                                 "run-app",
                                 "--payload-binary-name",
                                 "./MicrodroidTestNativeLib.so",
                                 apkPath,
                                 idSigPath,
-                                instanceImgPath)
-                        .getStderr()
-                        .trim();
+                                instanceImgPath));
+        if (isFeatureEnabled("com.android.kvm.LLPVM_CHANGES")) {
+            cmd.add("--instance-id-file");
+            cmd.add(TEST_ROOT + "instance_id");
+        }
+
+        final String ret = android.runForResult(String.join(" ", cmd)).getStderr().trim();
 
         assertThat(ret).contains("Payload binary name must not specify a path");
     }
diff --git a/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java b/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
index b84d7dc..26f5993 100644
--- a/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
+++ b/tests/pvmfw/java/com/android/pvmfw/test/DebugPolicyHostTests.java
@@ -301,7 +301,7 @@
 
     // Try to launch protected non-debuggable VM for a while and quit.
     // Non-debuggable VM might not enable adb, so there's no ITestDevice instance of it.
-    private CommandResult tryLaunchProtectedNonDebuggableVm() throws DeviceNotAvailableException {
+    private CommandResult tryLaunchProtectedNonDebuggableVm() throws Exception {
         // Can't use MicrodroidBuilder because it expects adb connection
         // but non-debuggable VM may not enable adb.
         CommandRunner runner = new CommandRunner(mAndroidDevice);
@@ -314,7 +314,7 @@
         String command =
                 String.join(
                         " ",
-                        "/apex/com.android.virt/bin/vm",
+                        VIRT_APEX + "bin/vm",
                         "run-app",
                         "--log",
                         MICRODROID_LOG_PATH,
@@ -324,6 +324,9 @@
                         TEST_ROOT + "instance.img",
                         "--config-path",
                         MICRODROID_CONFIG_PATH);
+        if (isFeatureEnabled("com.android.kvm.LLPVM_CHANGES")) {
+            command = String.join(" ", command, "--instance-id-file", TEST_ROOT + "instance_id");
+        }
         return mAndroidDevice.executeShellV2Command(
                 command, CONSOLE_OUTPUT_WAIT_MS, TimeUnit.MILLISECONDS, /* retryAttempts= */ 0);
     }
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 6ba5cf4..7a1ce37 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -229,6 +229,11 @@
         ret
     }
 
+    /// Allocate a new instance_id to the VM
+    fn allocateInstanceId(&self) -> binder::Result<[u8; 64]> {
+        GLOBAL_SERVICE.allocateInstanceId()
+    }
+
     /// Initialise an empty partition image of the given size to be used as a writable partition.
     fn initializeWritablePartition(
         &self,
@@ -398,9 +403,9 @@
             vec![]
         };
 
+        let instance_id;
         let untrusted_props = if cfg!(llpvm_changes) {
-            // TODO(b/291213394): Replace this with a per-VM instance Id.
-            let instance_id = b"sixtyfourbyteslonghardcoded_indeed_sixtyfourbyteslonghardcoded_h";
+            instance_id = extract_instance_id(config);
             vec![(cstr!("instance-id"), &instance_id[..])]
         } else {
             vec![]
@@ -1268,6 +1273,13 @@
     }
 }
 
+fn extract_instance_id(config: &VirtualMachineConfig) -> [u8; 64] {
+    match config {
+        VirtualMachineConfig::RawConfig(config) => config.instanceId,
+        VirtualMachineConfig::AppConfig(config) => config.instanceId,
+    }
+}
+
 fn extract_gdb_port(config: &VirtualMachineConfig) -> Option<NonZeroU16> {
     match config {
         VirtualMachineConfig::RawConfig(config) => NonZeroU16::new(config.gdbPort as u16),
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 843873b..2cbc805 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -31,12 +31,14 @@
         "libanyhow",
         "libavflog",
         "libbinder_rs",
+        "libhex",
         "libhypervisor_props",
         "liblazy_static",
         "liblibc",
         "liblog_rust",
         "libnix",
         "libopenssl",
+        "librand",
         "librkpd_client",
         "librustutils",
         "libstatslog_virtualization_rust",
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index f7bbf55..e11d8b8 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -41,6 +41,11 @@
             in @nullable ParcelFileDescriptor osLogFd);
 
     /**
+     * Allocate an instance_id to the (newly created) VM.
+     */
+    byte[64] allocateInstanceId();
+
+    /**
      * Initialise an empty partition image of the given size to be used as a writable partition.
      *
      * The file must be open with both read and write permissions, and should be a new empty file.
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
index 8302a2f..29232ff 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
@@ -23,6 +23,9 @@
     /** Name of VM */
     String name;
 
+    /** Id of the VM instance */
+    byte[64] instanceId;
+
     /** Main APK */
     ParcelFileDescriptor apk;
 
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
index 6be2833..b2116c4 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
@@ -23,6 +23,9 @@
     /** Name of VM */
     String name;
 
+    /** Id of the VM instance */
+    byte[64] instanceId;
+
     /** The kernel image, if any. */
     @nullable ParcelFileDescriptor kernel;
 
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index abfc45a..fc36190 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -91,4 +91,9 @@
 
     /** Returns a read-only file descriptor of the VM DTBO file. */
     ParcelFileDescriptor getDtboFile();
+
+    /**
+     * Allocate an instance_id to the (newly created) VM.
+     */
+    byte[64] allocateInstanceId();
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index c7ae5f0..79ff89a 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -35,6 +35,7 @@
 use log::{error, info, warn};
 use nix::unistd::{chown, Uid};
 use openssl::x509::X509;
+use rand::Fill;
 use rkpd_client::get_rkpd_attestation_key;
 use rustutils::system_properties;
 use serde::Deserialize;
@@ -378,6 +379,17 @@
         let file = state.get_dtbo_file().or_service_specific_exception(-1)?;
         Ok(ParcelFileDescriptor::new(file))
     }
+
+    // TODO(b/294177871) Persist this Id, along with client uuid.
+    fn allocateInstanceId(&self) -> binder::Result<[u8; 64]> {
+        let mut id = [0u8; 64];
+        id.try_fill(&mut rand::thread_rng())
+            .context("Failed to allocate instance_id")
+            .or_service_specific_exception(-1)?;
+        let uid = get_calling_uid();
+        info!("Allocated a VM's instance_id: {:?}, for uid: {:?}", hex::encode(id), uid);
+        Ok(id)
+    }
 }
 
 // KEEP IN SYNC WITH assignable_devices.xsd
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 355e193..063f992 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -22,6 +22,8 @@
     CpuTopology::CpuTopology, IVirtualizationService::IVirtualizationService,
     PartitionType::PartitionType, VirtualMachineAppConfig::DebugLevel::DebugLevel,
 };
+#[cfg(not(llpvm_changes))]
+use anyhow::anyhow;
 use anyhow::{Context, Error};
 use binder::{ProcessState, Strong};
 use clap::{Args, Parser};
@@ -162,6 +164,11 @@
     /// Path to the instance image. Created if not exists.
     instance: PathBuf,
 
+    /// Path to file containing instance_id. Required iff llpvm feature is enabled.
+    #[cfg(llpvm_changes)]
+    #[arg(long = "instance-id-file")]
+    instance_id: PathBuf,
+
     /// Path to VM config JSON within APK (e.g. assets/vm_config.json)
     #[arg(long)]
     config_path: Option<String>,
@@ -192,6 +199,27 @@
     fn extra_apks(&self) -> &[PathBuf] {
         &[]
     }
+
+    #[cfg(llpvm_changes)]
+    fn instance_id(&self) -> Result<PathBuf, Error> {
+        Ok(self.instance_id.clone())
+    }
+
+    #[cfg(not(llpvm_changes))]
+    fn instance_id(&self) -> Result<PathBuf, Error> {
+        Err(anyhow!("LLPVM feature is disabled, --instance_id flag not supported"))
+    }
+
+    #[cfg(llpvm_changes)]
+    fn set_instance_id(&mut self, instance_id_file: PathBuf) -> Result<(), Error> {
+        self.instance_id = instance_id_file;
+        Ok(())
+    }
+
+    #[cfg(not(llpvm_changes))]
+    fn set_instance_id(&mut self, _: PathBuf) -> Result<(), Error> {
+        Err(anyhow!("LLPVM feature is disabled, --instance_id flag not supported"))
+    }
 }
 
 #[derive(Args, Default)]
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 5a4a459..57b7641 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -35,6 +35,7 @@
 use std::fs;
 use std::fs::File;
 use std::io;
+use std::io::{Read, Write};
 use std::os::unix::io::{AsRawFd, FromRawFd};
 use std::path::{Path, PathBuf};
 use vmclient::{ErrorCode, VmInstance};
@@ -84,6 +85,24 @@
         )?;
     }
 
+    let instance_id = if cfg!(llpvm_changes) {
+        let id_file = config.instance_id()?;
+        if id_file.exists() {
+            let mut id = [0u8; 64];
+            let mut instance_id_file = File::open(id_file)?;
+            instance_id_file.read_exact(&mut id)?;
+            id
+        } else {
+            let id = service.allocateInstanceId().context("Failed to allocate instance_id")?;
+            let mut instance_id_file = File::create(id_file)?;
+            instance_id_file.write_all(&id)?;
+            id
+        }
+    } else {
+        // if llpvm feature flag is disabled, instance_id is not used.
+        [0u8; 64]
+    };
+
     let storage = if let Some(ref path) = config.microdroid.storage {
         if !path.exists() {
             command_create_partition(
@@ -153,6 +172,7 @@
         idsig: idsig_fd.into(),
         extraIdsigs: extra_idsig_fds,
         instanceImage: open_parcel_file(&config.instance, true /* writable */)?.into(),
+        instanceId: instance_id,
         encryptedStorageImage: storage,
         payload,
         debugLevel: config.debug.debug,
@@ -204,7 +224,7 @@
     let instance_img = work_dir.join("instance.img");
     println!("instance.img path: {}", instance_img.display());
 
-    let app_config = RunAppConfig {
+    let mut app_config = RunAppConfig {
         common: config.common,
         debug: config.debug,
         microdroid: config.microdroid,
@@ -214,6 +234,12 @@
         payload_binary_name: Some("MicrodroidEmptyPayloadJniLib.so".to_owned()),
         ..Default::default()
     };
+
+    if cfg!(llpvm_changes) {
+        app_config.set_instance_id(work_dir.join("instance_id"))?;
+        println!("instance_id file path: {}", app_config.instance_id()?.display());
+    }
+
     command_run_app(app_config)
 }