From 159925a27b1c35398f4164df0e26b589fe1aedd1 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 16 Sep 2025 08:07:07 +0200 Subject: [PATCH] kvm-ioctls: backport kvm nested state to v0.22.1 This is a backport of #322 [0] from the latest state to v0.22.1 to enable an easy way to use this in cloud-hypervisor by simply bumping the patch version of the component. The backport is done in two steps, this is step 2/2. This commit only does the necessary steps for kvm-ioctls. [0] https://github.com/rust-vmm/kvm/pull/322 Signed-off-by: Philipp Schuster On-behalf-of: SAP philipp.schuster@sap.com --- kvm-ioctls/CHANGELOG.md | 7 ++ kvm-ioctls/Cargo.toml | 4 +- kvm-ioctls/src/cap.rs | 2 + kvm-ioctls/src/ioctls/vcpu.rs | 127 +++++++++++++++++++++++++++++++++- kvm-ioctls/src/kvm_ioctls.rs | 6 ++ kvm-ioctls/src/lib.rs | 2 +- 6 files changed, 144 insertions(+), 4 deletions(-) diff --git a/kvm-ioctls/CHANGELOG.md b/kvm-ioctls/CHANGELOG.md index 16b5832d..d01619e8 100644 --- a/kvm-ioctls/CHANGELOG.md +++ b/kvm-ioctls/CHANGELOG.md @@ -2,6 +2,13 @@ ## Upcoming Release +## v0.22.1 + +### Added + +- Upgrade kvm-bindings to v0.12.1 +- `Vcpu::get_nested_state()` and `Vcpu::set_nested_state()` + ## v0.22.0 ### Changed diff --git a/kvm-ioctls/Cargo.toml b/kvm-ioctls/Cargo.toml index ef179e50..ea07d442 100644 --- a/kvm-ioctls/Cargo.toml +++ b/kvm-ioctls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kvm-ioctls" -version = "0.22.0" +version = "0.22.1" authors = ["Amazon Firecracker Team "] description = "Safe wrappers over KVM ioctls" repository = "https://github.com/rust-vmm/kvm" @@ -11,7 +11,7 @@ edition = "2021" [dependencies] libc = "0.2.39" -kvm-bindings = { path = "../kvm-bindings", version = "0.12.0", features = ["fam-wrappers"] } +kvm-bindings = { version = "~0.12.1", features = ["fam-wrappers"] } vmm-sys-util = { workspace = true } bitflags = "2.4.1" diff --git a/kvm-ioctls/src/cap.rs b/kvm-ioctls/src/cap.rs index 311570db..3f5d6123 100644 --- a/kvm-ioctls/src/cap.rs +++ b/kvm-ioctls/src/cap.rs @@ -165,4 +165,6 @@ pub enum Cap { UserMemory2 = KVM_CAP_USER_MEMORY2, GuestMemfd = KVM_CAP_GUEST_MEMFD, MemoryAttributes = KVM_CAP_MEMORY_ATTRIBUTES, + #[cfg(target_arch = "x86_64")] + NestedState = KVM_CAP_NESTED_STATE, } diff --git a/kvm-ioctls/src/ioctls/vcpu.rs b/kvm-ioctls/src/ioctls/vcpu.rs index f450a6d3..559d7c3e 100644 --- a/kvm-ioctls/src/ioctls/vcpu.rs +++ b/kvm-ioctls/src/ioctls/vcpu.rs @@ -7,6 +7,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the THIRD-PARTY file. +// Part of public API +#[cfg(target_arch = "x86_64")] +pub use kvm_bindings::nested::KvmNestedStateBuffer; + use kvm_bindings::*; use libc::EINVAL; use std::fs::File; @@ -17,7 +21,10 @@ use crate::kvm_ioctls::*; use vmm_sys_util::errno; use vmm_sys_util::ioctl::{ioctl, ioctl_with_mut_ref, ioctl_with_ref}; #[cfg(target_arch = "x86_64")] -use vmm_sys_util::ioctl::{ioctl_with_mut_ptr, ioctl_with_ptr, ioctl_with_val}; +use { + std::num::NonZeroUsize, + vmm_sys_util::ioctl::{ioctl_with_mut_ptr, ioctl_with_ptr, ioctl_with_val}, +}; /// Helper method to obtain the size of the register through its id #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] @@ -1983,6 +1990,94 @@ impl VcpuFd { } } + /// Returns the nested guest state using the `KVM_GET_NESTED_STATE` ioctl. + /// + /// This only works when `KVM_CAP_NESTED_STATE` is available. + /// + /// # Arguments + /// + /// - `buffer`: The buffer to be filled with the new nested state. + /// + /// # Return Value + /// If this returns `None`, KVM doesn't have nested state. Otherwise, the + /// actual length of the state is returned. + /// + /// # Example + /// + /// ```rust + /// # use kvm_ioctls::{Kvm, Cap, KvmNestedStateBuffer}; + /// let kvm = Kvm::new().unwrap(); + /// let vm = kvm.create_vm().unwrap(); + /// let vcpu = vm.create_vcpu(0).unwrap(); + /// let mut state_buffer = KvmNestedStateBuffer::empty(); + /// if kvm.check_extension(Cap::NestedState) { + /// vcpu.get_nested_state(&mut state_buffer).unwrap(); + /// // Next, serialize the actual state into a file or so. + /// } + /// ``` + /// + /// [`Kvm::check_extension_int`]: kvm_ioctls::Kvm::check_extension_int + #[cfg(target_arch = "x86_64")] + pub fn get_nested_state( + &self, + buffer: &mut KvmNestedStateBuffer, + ) -> Result> { + assert_ne!(buffer.size, 0, "buffer should not report a size of zero"); + + // SAFETY: Safe because we call this with a Vcpu fd and we trust the kernel. + let ret = unsafe { ioctl_with_mut_ref(self, KVM_GET_NESTED_STATE(), buffer) }; + match ret { + 0 => { + let size = buffer.size as usize; + let just_hdr_size = size_of::(); + if size <= just_hdr_size { + Ok(None) + } else { + Ok(Some(NonZeroUsize::new(size).unwrap())) + } + } + _ => Err(errno::Error::last()), + } + } + + /// Sets the nested guest state using the `KVM_SET_NESTED_STATE` ioctl. + /// + /// This only works when `KVM_CAP_NESTED_STATE` is available. + /// + /// # Arguments + /// + /// - `state`: The new state to be put into KVM. The header must report the + /// `size` of the state properly. The state must be retrieved first using + /// [`Self::get_nested_state`]. + /// + /// # Example + /// + /// ```rust + /// # use kvm_ioctls::{Kvm, Cap, KvmNestedStateBuffer}; + /// let kvm = Kvm::new().unwrap(); + /// let vm = kvm.create_vm().unwrap(); + /// let vcpu = vm.create_vcpu(0).unwrap(); + /// if kvm.check_extension(Cap::NestedState) { + /// let mut state_buffer = KvmNestedStateBuffer::empty(); + /// vcpu.get_nested_state(&mut state_buffer).unwrap(); + /// // Rename the variable to better reflect the role. + /// let old_state = state_buffer; + /// + /// // now assume we transfer the state to a new location + /// // and load it back into kvm: + /// vcpu.set_nested_state(&old_state).unwrap(); + /// } + /// ``` + #[cfg(target_arch = "x86_64")] + pub fn set_nested_state(&self, state: &KvmNestedStateBuffer) -> Result<()> { + // SAFETY: Safe because we call this with a Vcpu fd and we trust the kernel. + let ret = unsafe { ioctl_with_ref(self, KVM_SET_NESTED_STATE(), state) }; + match ret { + 0 => Ok(()), + _ => Err(errno::Error::last()), + } + } + /// Queues an NMI on the thread's vcpu. Only usable if `KVM_CAP_USER_NMI` /// is available. /// @@ -3609,4 +3704,34 @@ mod tests { assert_eq!(addr, ADDR); assert_eq!(data, (DATA as u16).to_le_bytes()); } + + #[test] + #[cfg(target_arch = "x86_64")] + fn test_get_and_set_nested_state() { + let kvm = Kvm::new().unwrap(); + let vm = kvm.create_vm().unwrap(); + let vcpu = vm.create_vcpu(0).unwrap(); + + // Ensure that KVM also during runtime never wants more memory than we have pre-allocated + // by the helper type. KVM is expected to report: + // - 128+4096==4224 on SVM + // - 128+8192==8320 on VMX + let kvm_nested_state_size = kvm.check_extension_int(Cap::NestedState) as usize; + assert!(kvm_nested_state_size <= size_of::()); + + let mut state_buffer = KvmNestedStateBuffer::default(); + // Ensure that header shows full buffer length. + assert_eq!( + state_buffer.size as usize, + size_of::() + ); + + vcpu.get_nested_state(&mut state_buffer).unwrap(); + let old_state = state_buffer; + + // There is no nested guest in this test, so there is no payload. + assert_eq!(state_buffer.size as usize, size_of::()); + + vcpu.set_nested_state(&old_state).unwrap(); + } } diff --git a/kvm-ioctls/src/kvm_ioctls.rs b/kvm-ioctls/src/kvm_ioctls.rs index 9045b9d5..ba12cb7c 100644 --- a/kvm-ioctls/src/kvm_ioctls.rs +++ b/kvm-ioctls/src/kvm_ioctls.rs @@ -262,6 +262,12 @@ ioctl_iow_nr!( kvm_memory_attributes ); +#[cfg(target_arch = "x86_64")] +ioctl_iowr_nr!(KVM_GET_NESTED_STATE, KVMIO, 0xbe, kvm_nested_state); + +#[cfg(target_arch = "x86_64")] +ioctl_iow_nr!(KVM_SET_NESTED_STATE, KVMIO, 0xbf, kvm_nested_state); + // Device ioctls. /* Available with KVM_CAP_DEVICE_CTRL */ diff --git a/kvm-ioctls/src/lib.rs b/kvm-ioctls/src/lib.rs index 278e21b7..8ea71878 100644 --- a/kvm-ioctls/src/lib.rs +++ b/kvm-ioctls/src/lib.rs @@ -249,7 +249,7 @@ pub use ioctls::vcpu::reg_size; pub use ioctls::vcpu::{HypercallExit, VcpuExit, VcpuFd}; #[cfg(target_arch = "x86_64")] -pub use ioctls::vcpu::{MsrExitReason, ReadMsrExit, SyncReg, WriteMsrExit}; +pub use ioctls::vcpu::{KvmNestedStateBuffer, MsrExitReason, ReadMsrExit, SyncReg, WriteMsrExit}; pub use ioctls::vm::{IoEventAddress, NoDatamatch, VmFd}; // The following example is used to verify that our public