Skip to content

Commit bae8bc7

Browse files
zulinx86ShadowCurse
authored andcommitted
Mark set_xsave() unsafe
The C `kvm_xsave` strut was extended to have a flexible array member (FAM) at the end in Linux 5.17. The size can vary depending on features that have been dynamically enabled via `arch_prctl()` and the required size can be retrieved via `KVM_CHECK_EXTENSION(KVM_CAP_XSAVE2)`. That means `KVM_SET_XSAVE` may copy data beyond the size of the traditional C `kvm_xsave` struct (i.e. 4096 bytes) now, possibly causing undefined behavior. It is safe if used on Linux prior to 5.17, if no XSTATE features are enabled dynamically or if the required size is still within the traditional 4096 bytes even with dynamically enabled features. However, if any features are enabled dynamically, it is recommended to use `set_xsave2()` instead. Signed-off-by: Takahiro Itazuri <[email protected]>
1 parent 65a665a commit bae8bc7

File tree

2 files changed

+42
-8
lines changed

2 files changed

+42
-8
lines changed

kvm-ioctls/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
- [[#310](https://github.com/rust-vmm/kvm/pull/310)]: Added support for
88
`KVM_CAP_XSAVE2` and the `KVM_GET_XSAVE2` ioctl.
99

10+
### Changed
11+
12+
- [[#310](https://github.com/rust-vmm/kvm/pull/310)]: Changed `set_xsave()`
13+
`unsafe` because the C `kvm_xsave` struct was extended to have a flexible
14+
array member (FAM) in the end in Linux 5.16 and `KVM_SET_XSAVE` may copy data
15+
beyond the traditional size (i.e. 4096 bytes). If any features are enabled
16+
dynamically on Linux 5.16+, it is recommended to use `set_xsave2()` instead.
17+
1018
## v0.20.0
1119

1220
### Added

kvm-ioctls/src/ioctls/vcpu.rs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,29 @@ impl VcpuFd {
923923
///
924924
/// # Arguments
925925
///
926-
/// * `kvm_xsave` - xsave struct to be written.
926+
/// * `xsave` - xsave struct to be written.
927+
///
928+
/// # Safety
929+
///
930+
/// The C `kvm_xsave` struct was extended to have a flexible array member (FAM) at the end in
931+
/// Linux 5.17. The size can vary depending on features that have been dynamically enabled via
932+
/// `arch_prctl()` and the required size can be retrieved via
933+
/// `KVM_CHECK_EXTENSION(KVM_CAP_XSAVE2)`. That means `KVM_SET_XSAVE` may copy data beyond the
934+
/// size of the traditional C `kvm_xsave` struct (i.e. 4096 bytes) now.
935+
///
936+
/// It is safe if used on Linux prior to 5.17, if no XSTATE features are enabled dynamically or
937+
/// if the required size is still within the traditional 4096 bytes even with dynamically
938+
/// enabled features. However, if any features are enabled dynamically, it is recommended to use
939+
/// `set_xsave2()` instead.
940+
///
941+
/// See the documentation for dynamically enabled XSTATE features in the
942+
/// [kernel doc](https://docs.kernel.org/arch/x86/xstate.html).
943+
///
944+
/// Theoretically, it can be made safe by checking which features are enabled in the bit vector
945+
/// of the XSTATE header and validating the required size is less than or equal to 4096 bytes.
946+
/// However, to do it properly, we would need to extract the XSTATE header from the `kvm_xsave`
947+
/// struct, check enabled features, retrieve the required size for each enabled feature (like
948+
/// `setup_xstate_cache()` do in Linux) and calculate the total size.
927949
///
928950
/// # Example
929951
///
@@ -935,10 +957,10 @@ impl VcpuFd {
935957
/// let vcpu = vm.create_vcpu(0).unwrap();
936958
/// let xsave = Default::default();
937959
/// // Your `xsave` manipulation here.
938-
/// vcpu.set_xsave(&xsave).unwrap();
960+
/// unsafe { vcpu.set_xsave(&xsave).unwrap() };
939961
/// ```
940962
#[cfg(target_arch = "x86_64")]
941-
pub fn set_xsave(&self, xsave: &kvm_xsave) -> Result<()> {
963+
pub unsafe fn set_xsave(&self, xsave: &kvm_xsave) -> Result<()> {
942964
// SAFETY: Here we trust the kernel not to read past the end of the kvm_xsave struct.
943965
let ret = unsafe { ioctl_with_ref(self, KVM_SET_XSAVE(), xsave) };
944966
if ret != 0 {
@@ -2325,7 +2347,8 @@ mod tests {
23252347
let vm = kvm.create_vm().unwrap();
23262348
let vcpu = vm.create_vcpu(0).unwrap();
23272349
let xsave = vcpu.get_xsave().unwrap();
2328-
vcpu.set_xsave(&xsave).unwrap();
2350+
// SAFETY: Safe because no features are enabled dynamically and `xsave` is large enough.
2351+
unsafe { vcpu.set_xsave(&xsave).unwrap() };
23292352
let other_xsave = vcpu.get_xsave().unwrap();
23302353
assert_eq!(&xsave.region[..], &other_xsave.region[..]);
23312354

@@ -2844,10 +2867,13 @@ mod tests {
28442867
badf_errno
28452868
);
28462869
assert_eq!(
2847-
faulty_vcpu_fd
2848-
.set_xsave(&kvm_xsave::default())
2849-
.unwrap_err()
2850-
.errno(),
2870+
// SAFETY: It fails before it copies data and any features are not enabled dynamically.
2871+
unsafe {
2872+
faulty_vcpu_fd
2873+
.set_xsave(&kvm_xsave::default())
2874+
.unwrap_err()
2875+
.errno()
2876+
},
28512877
badf_errno
28522878
);
28532879
assert_eq!(faulty_vcpu_fd.get_xcrs().unwrap_err().errno(), badf_errno);

0 commit comments

Comments
 (0)