Merge "sign_virt_apex: do not use truncate (/usr/bin)"
diff --git a/libs/vmconfig/Android.bp b/libs/vmconfig/Android.bp
index 321eba0..1aee1ce 100644
--- a/libs/vmconfig/Android.bp
+++ b/libs/vmconfig/Android.bp
@@ -10,8 +10,9 @@
     rustlibs: [
         "android.system.virtualizationservice-rust",
         "libanyhow",
-        "libserde_json",
+        "libsemver",
         "libserde",
+        "libserde_json",
     ],
     apex_available: [
         "com.android.virt",
diff --git a/libs/vmconfig/src/lib.rs b/libs/vmconfig/src/lib.rs
index 9c3e671..607b347 100644
--- a/libs/vmconfig/src/lib.rs
+++ b/libs/vmconfig/src/lib.rs
@@ -22,6 +22,7 @@
 };
 
 use anyhow::{bail, Context, Error, Result};
+use semver::VersionReq;
 use serde::{Deserialize, Serialize};
 use std::convert::TryInto;
 use std::fs::{File, OpenOptions};
@@ -51,6 +52,9 @@
     /// The amount of RAM to give the VM, in MiB.
     #[serde(default)]
     pub memory_mib: Option<NonZeroU32>,
+    /// Version or range of versions of the virtual platform that this config is compatible with.
+    /// The format follows SemVer (https://semver.org).
+    pub platform_version: VersionReq,
 }
 
 impl VmConfig {
@@ -95,6 +99,7 @@
             disks: self.disks.iter().map(DiskImage::to_parcelable).collect::<Result<_, Error>>()?,
             protectedVm: self.protected,
             memoryMib: memory_mib,
+            platformVersion: self.platform_version.to_string(),
             ..Default::default()
         })
     }
diff --git a/microdroid/microdroid.json b/microdroid/microdroid.json
index cb27a24..aff0b7b 100644
--- a/microdroid/microdroid.json
+++ b/microdroid/microdroid.json
@@ -37,5 +37,6 @@
       "writable": true
     }
   ],
-  "memory_mib": 256
+  "memory_mib": 256,
+  "platform_version": "~1.0"
 }
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 1218b68..58935d8 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -122,6 +122,7 @@
         int memory_mib;
         @SerializedName("protected")
         boolean isProtected;
+        String platform_version;
     }
 
     static class Disk {
diff --git a/tests/vsock_test.cc b/tests/vsock_test.cc
index c478f64..0fc451d 100644
--- a/tests/vsock_test.cc
+++ b/tests/vsock_test.cc
@@ -48,6 +48,7 @@
 static constexpr const char kVmInitrdPath[] = "/data/local/tmp/virt-test/initramfs";
 static constexpr const char kVmParams[] = "rdinit=/bin/init bin/vsock_client 2 45678 HelloWorld";
 static constexpr const char kTestMessage[] = "HelloWorld";
+static constexpr const char kPlatformVersion[] = "~1.0";
 
 /** Returns true if the kernel supports unprotected VMs. */
 bool isUnprotectedVmSupported() {
@@ -82,6 +83,7 @@
     raw_config.initrd = ParcelFileDescriptor(unique_fd(open(kVmInitrdPath, O_RDONLY | O_CLOEXEC)));
     raw_config.params = kVmParams;
     raw_config.protectedVm = false;
+    raw_config.platformVersion = kPlatformVersion;
 
     VirtualMachineConfig config(std::move(raw_config));
     sp<IVirtualMachine> vm;
@@ -112,4 +114,17 @@
     ASSERT_EQ(msg, kTestMessage);
 }
 
+TEST_F(VirtualizationTest, RejectIncompatiblePlatformVersion) {
+    VirtualMachineRawConfig raw_config;
+    raw_config.kernel = ParcelFileDescriptor(unique_fd(open(kVmKernelPath, O_RDONLY | O_CLOEXEC)));
+    raw_config.initrd = ParcelFileDescriptor(unique_fd(open(kVmInitrdPath, O_RDONLY | O_CLOEXEC)));
+    raw_config.params = kVmParams;
+    raw_config.platformVersion = "~2.0"; // The current platform version is 1.0.0.
+
+    VirtualMachineConfig config(std::move(raw_config));
+    sp<IVirtualMachine> vm;
+    auto status = mVirtualizationService->createVm(config, std::nullopt, std::nullopt, &vm);
+    ASSERT_FALSE(status.isOk());
+}
+
 } // namespace virt
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index b82064b..981a041 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -37,6 +37,7 @@
         "libonce_cell",
         "libregex",
         "librustutils",
+        "libsemver",
         "libselinux_bindgen",
         "libserde",
         "libserde_json",
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
index bb4eef0..dfd3bff 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
@@ -57,4 +57,10 @@
      * Default is no mask which means a vCPU can run on any host CPU.
      */
     @nullable String cpuAffinity;
+
+    /**
+     * A version or range of versions of the virtual platform that this config is compatible with.
+     * The format follows SemVer.
+     */
+    @utf8InCpp String platformVersion;
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 89c6e8a..d9825dc 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -52,6 +52,7 @@
 use log::{debug, error, info, warn, trace};
 use microdroid_payload_config::VmPayloadConfig;
 use rustutils::system_properties;
+use semver::VersionReq;
 use statslog_virtualization_rust::vm_creation_requested::{stats_write, Hypervisor};
 use std::convert::TryInto;
 use std::ffi::CStr;
@@ -239,6 +240,7 @@
             console_fd,
             log_fd,
             indirect_files,
+            platform_version: parse_platform_version_req(&config.platformVersion)?,
         };
         let instance = Arc::new(
             VmInstance::new(
@@ -893,6 +895,16 @@
     ParcelFileDescriptor::new(f)
 }
 
+/// Parses the platform version requirement string.
+fn parse_platform_version_req(s: &str) -> Result<VersionReq, Status> {
+    VersionReq::parse(s).map_err(|e| {
+        new_binder_exception(
+            ExceptionCode::BAD_PARCELABLE,
+            format!("Invalid platform version requirement {}: {}", s, e),
+        )
+    })
+}
+
 /// Simple utility for referencing Borrowed or Owned. Similar to std::borrow::Cow, but
 /// it doesn't require that T implements Clone.
 enum BorrowedOrOwned<'a, T> {
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index df7a7d2..94cb78f 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -19,6 +19,7 @@
 use anyhow::{bail, Error};
 use command_fds::CommandFdExt;
 use log::{debug, error, info};
+use semver::{Version, VersionReq};
 use shared_child::SharedChild;
 use std::fs::{remove_dir_all, File};
 use std::io;
@@ -36,6 +37,12 @@
 
 const CROSVM_PATH: &str = "/apex/com.android.virt/bin/crosvm";
 
+/// Version of the platform that crosvm currently implements. The format follows SemVer. This
+/// should be updated when there is a platform change in the crosvm side. Having this value here is
+/// fine because virtualizationservice and crosvm are supposed to be updated together in the virt
+/// APEX.
+const CROSVM_PLATFORM_VERSION: &str = "1.0.0";
+
 /// The exit status which crosvm returns when it has an error starting a VM.
 const CROSVM_ERROR_STATUS: i32 = 1;
 /// The exit status which crosvm returns when a VM requests a reboot.
@@ -59,6 +66,7 @@
     pub console_fd: Option<File>,
     pub log_fd: Option<File>,
     pub indirect_files: Vec<File>,
+    pub platform_version: VersionReq,
 }
 
 /// A disk image to pass to crosvm for a VM.
@@ -365,6 +373,16 @@
     if config.bootloader.is_some() && (config.kernel.is_some() || config.initrd.is_some()) {
         bail!("Can't have both bootloader and kernel/initrd image.");
     }
+    let version = Version::parse(CROSVM_PLATFORM_VERSION).unwrap();
+    if !config.platform_version.matches(&version) {
+        bail!(
+            "Incompatible platform version. The config is compatible with platform version(s) \
+              {}, but the actual platform version is {}",
+            config.platform_version,
+            version
+        );
+    }
+
     Ok(())
 }