Skip to content

Commit 8b43e9f

Browse files
committed
Distinguish between safe and unsafe set_msr_filter
Signed-off-by: Ludvig Liljenberg <[email protected]>
1 parent 93b1b92 commit 8b43e9f

File tree

2 files changed

+158
-13
lines changed

2 files changed

+158
-13
lines changed

kvm-ioctls/src/ioctls/vm.rs

Lines changed: 156 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// Use of this source code is governed by a BSD-style license that can be
88
// found in the THIRD-PARTY file.
99

10+
#[cfg(target_arch = "x86_64")]
11+
use bitflags::bitflags;
1012
use kvm_bindings::*;
1113
use std::fs::File;
1214
use std::os::raw::c_void;
@@ -54,6 +56,48 @@ impl From<NoDatamatch> for u64 {
5456
}
5557
}
5658

59+
/// Helper structure describing one MSR filter range consumed by
60+
/// [`VmFd::set_msr_filter`].
61+
#[cfg(target_arch = "x86_64")]
62+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63+
pub struct MsrFilterRange<'a> {
64+
/// Flags specifying which MSR operations are filtered in this range.
65+
pub flags: MsrFilterRangeFlags,
66+
/// Base MSR index of the range.
67+
pub base: u32,
68+
/// Number of MSRs in the range.
69+
pub msr_count: u32,
70+
/// Bitmap specifying allowed operations for each MSR in the range.
71+
pub bitmap: &'a [u8],
72+
}
73+
74+
#[cfg(target_arch = "x86_64")]
75+
bitflags! {
76+
/// Flags selecting which MSR operations are filtered for a range.
77+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
78+
pub struct MsrFilterRangeFlags: u32 {
79+
/// Filter read accesses to MSRs using the given bitmap.
80+
/// A 0 in the bitmap indicates that read accesses should be denied,
81+
/// while a 1 indicates that a read for a particular MSR should be allowed regardless of the default filter action.
82+
const READ = KVM_MSR_FILTER_READ;
83+
/// Filter write accesses to MSRs using the given bitmap.
84+
/// A 0 in the bitmap indicates that write accesses should be denied,
85+
/// while a 1 indicates that a write for a particular MSR should be allowed regardless of the default filter action.
86+
const WRITE = KVM_MSR_FILTER_WRITE;
87+
}
88+
}
89+
90+
/// Default action for MSR filtering.
91+
#[cfg(target_arch = "x86_64")]
92+
#[repr(u32)]
93+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
94+
pub enum MsrFilterDefaultAction {
95+
/// If no filter range matches an MSR index that is getting accessed, KVM will allow accesses to all MSRs by default.
96+
ALLOW = KVM_MSR_FILTER_DEFAULT_ALLOW,
97+
/// If no filter range matches an MSR index that is getting accessed, KVM will deny accesses to all MSRs by default.
98+
DENY = KVM_MSR_FILTER_DEFAULT_DENY,
99+
}
100+
57101
/// Wrapper over KVM VM ioctls.
58102
#[derive(Debug)]
59103
pub struct VmFd {
@@ -544,23 +588,21 @@ impl VmFd {
544588
///
545589
/// # Safety
546590
///
547-
/// The caller must ensure that the given `kvm_msr_filter` is valid. Specifically any `bitmap` pointers in the `kvm_msr_filter_range`
548-
/// structures within `filter.ranges` must point to valid memory of sufficient size.
591+
/// This is the unsafe version of [`VmFd::set_msr_filter`]. The caller must ensure that the given `kvm_msr_filter` is valid.
592+
/// Specifically any `bitmap` pointers in the `kvm_msr_filter_range` structures within `filter.ranges` must point to valid memory of sufficient size.
549593
/// # Example
550594
///
551595
/// ```rust
552-
/// # extern crate kvm_bindings;
553-
/// # extern crate kvm_ioctls;
554596
/// # use kvm_bindings::kvm_msr_filter;
555597
/// # use kvm_ioctls::Kvm;
556598
/// let kvm = Kvm::new().unwrap();
557599
/// let vm = kvm.create_vm().unwrap();
558-
/// let mut filter = kvm_msr_filter::default();
600+
/// let filter = kvm_msr_filter::default();
559601
/// // Safety: filter is valid
560-
/// unsafe { vm.set_msr_filter(&mut filter).unwrap() };
602+
/// unsafe { vm.set_msr_filter_unchecked(&filter).unwrap() };
561603
/// ```
562604
#[cfg(target_arch = "x86_64")]
563-
pub unsafe fn set_msr_filter(&self, filter: &kvm_msr_filter) -> Result<()> {
605+
pub unsafe fn set_msr_filter_unchecked(&self, filter: &kvm_msr_filter) -> Result<()> {
564606
// SAFETY: Safe because we call this with a Vm fd and we trust the kernel, and the caller
565607
// has promised validity of the filter structure.
566608
let ret = unsafe { ioctl_with_ref(self, KVM_SET_MSR_FILTER(), filter) };
@@ -571,6 +613,68 @@ impl VmFd {
571613
}
572614
}
573615

616+
/// Sets the MSR filter as per the `KVM_X86_SET_MSR_FILTER` ioctl.
617+
///
618+
/// See the documentation for `KVM_X86_SET_MSR_FILTER` in the
619+
/// [KVM API doc](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt).
620+
///
621+
/// # Arguments
622+
///
623+
/// * `filter` - MSR filter configuration to be set.
624+
///
625+
/// # Example
626+
///
627+
/// ```rust
628+
/// # use kvm_ioctls::Kvm;
629+
/// # use kvm_ioctls::{
630+
/// # MsrFilterDefaultAction,
631+
/// # MsrFilterRange,
632+
/// # MsrFilterRangeFlags,
633+
/// # };
634+
/// let kvm = Kvm::new().unwrap();
635+
/// let vm = kvm.create_vm().unwrap();
636+
/// let mut bitmap = [0xffu8; 4];
637+
/// let mut range = MsrFilterRange {
638+
/// flags: MsrFilterRangeFlags::READ | MsrFilterRangeFlags::WRITE,
639+
/// base: 0,
640+
/// msr_count: 32,
641+
/// bitmap: &bitmap,
642+
/// };
643+
/// vm.set_msr_filter(MsrFilterDefaultAction::DENY, &[range])
644+
/// .unwrap();
645+
/// ```
646+
#[cfg(target_arch = "x86_64")]
647+
pub fn set_msr_filter(
648+
&self,
649+
default_action: MsrFilterDefaultAction,
650+
ranges: &[MsrFilterRange<'_>],
651+
) -> Result<()> {
652+
if ranges.len() > KVM_MSR_FILTER_MAX_RANGES as usize {
653+
return Err(errno::Error::new(libc::EINVAL));
654+
}
655+
656+
let mut raw_filter = kvm_msr_filter {
657+
flags: default_action as u32,
658+
..Default::default()
659+
};
660+
661+
for (dst, src) in raw_filter.ranges.iter_mut().zip(ranges.iter()) {
662+
// Validate that the provided bitmap is large enough to hold the specified number of MSRs.
663+
let required_bytes = src.msr_count.div_ceil(8) as usize;
664+
if src.bitmap.len() < required_bytes {
665+
return Err(errno::Error::new(libc::EINVAL));
666+
}
667+
668+
dst.flags = src.flags.bits();
669+
dst.nmsrs = src.msr_count;
670+
dst.base = src.base;
671+
dst.bitmap = src.bitmap.as_ptr() as *mut u8; // TODO: is this cast ok? kvm_msr_filter_range.bitmap is __*mut__ u8. Ideally I don't want to require input parameters to be mutable unless it's necessary
672+
}
673+
674+
// SAFETY: We checked the length of all bitmaps above.
675+
unsafe { self.set_msr_filter_unchecked(&raw_filter) }
676+
}
677+
574678
/// Directly injects a MSI message as per the `KVM_SIGNAL_MSI` ioctl.
575679
///
576680
/// See the documentation for `KVM_SIGNAL_MSI`.
@@ -2941,20 +3045,20 @@ mod tests {
29413045

29423046
#[test]
29433047
#[cfg(target_arch = "x86_64")]
2944-
fn test_set_msr_filter() {
3048+
fn test_set_msr_filter_unchecked() {
29453049
let kvm = Kvm::new().unwrap();
2946-
let vm = kvm.create_vm().unwrap();
2947-
29483050
if !kvm.check_extension(Cap::X86MsrFilter) {
29493051
return;
29503052
}
29513053

3054+
let vm = kvm.create_vm().unwrap();
3055+
29523056
let empty_filter = kvm_msr_filter {
29533057
flags: KVM_MSR_FILTER_DEFAULT_ALLOW,
29543058
ranges: [kvm_msr_filter_range::default(); 16],
29553059
};
29563060
// Safety: empty_filter is valid
2957-
unsafe { vm.set_msr_filter(&empty_filter).unwrap() };
3061+
unsafe { vm.set_msr_filter_unchecked(&empty_filter).unwrap() };
29583062

29593063
// From KVM API:
29603064
// Calling this ioctl with an empty set of ranges (all nmsrs == 0) disables MSR filtering. In that mode, KVM_MSR_FILTER_DEFAULT_DENY is invalid and causes an error.
@@ -2963,7 +3067,7 @@ mod tests {
29633067
ranges: [kvm_msr_filter_range::default(); 16],
29643068
};
29653069
// Safety: empty_deny_filter is invalid
2966-
unsafe { vm.set_msr_filter(&empty_deny_filter).unwrap_err() };
3070+
unsafe { vm.set_msr_filter_unchecked(&empty_deny_filter).unwrap_err() };
29673071

29683072
// disable access to all except 1 MSR
29693073
let mut filter = kvm_msr_filter {
@@ -2976,6 +3080,45 @@ mod tests {
29763080
filter.ranges[0].nmsrs = 1;
29773081
filter.ranges[0].bitmap = &mut bitmap;
29783082
// Safety: bitmap is valid 8 bits, and nmsrs is 1
2979-
unsafe { vm.set_msr_filter(&filter).unwrap() };
3083+
unsafe { vm.set_msr_filter_unchecked(&filter).unwrap() };
3084+
}
3085+
3086+
#[test]
3087+
#[cfg(target_arch = "x86_64")]
3088+
fn test_set_msr_filter_safe() {
3089+
let kvm = Kvm::new().unwrap();
3090+
if !kvm.check_extension(Cap::X86MsrFilter) {
3091+
return;
3092+
}
3093+
3094+
let vm = kvm.create_vm().unwrap();
3095+
3096+
// Empty ranges with the default allow action disable filtering.
3097+
vm.set_msr_filter(MsrFilterDefaultAction::ALLOW, &[])
3098+
.unwrap();
3099+
3100+
// Empty ranges with default deny should surface an error from KVM.
3101+
vm.set_msr_filter(MsrFilterDefaultAction::DENY, &[])
3102+
.unwrap_err();
3103+
3104+
// A bitmap that is too short for the requested range is rejected locally.
3105+
let bad_range = MsrFilterRange {
3106+
flags: MsrFilterRangeFlags::READ,
3107+
base: 0,
3108+
msr_count: 9,
3109+
bitmap: &[0xff],
3110+
};
3111+
vm.set_msr_filter(MsrFilterDefaultAction::ALLOW, &[bad_range])
3112+
.unwrap_err();
3113+
3114+
// Valid deny-list that allows a single MSR succeeds.
3115+
let allow_range = MsrFilterRange {
3116+
flags: MsrFilterRangeFlags::READ | MsrFilterRangeFlags::WRITE,
3117+
base: 0x10,
3118+
msr_count: 1,
3119+
bitmap: &[0x1],
3120+
};
3121+
vm.set_msr_filter(MsrFilterDefaultAction::DENY, &[allow_range])
3122+
.unwrap();
29803123
}
29813124
}

kvm-ioctls/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ pub use ioctls::vcpu::{HypercallExit, VcpuExit, VcpuFd};
243243
pub use ioctls::vcpu::{KvmNestedStateBuffer, MsrExitReason, ReadMsrExit, SyncReg, WriteMsrExit};
244244

245245
pub use ioctls::vm::{IoEventAddress, NoDatamatch, VmFd};
246+
#[cfg(target_arch = "x86_64")]
247+
pub use ioctls::vm::{MsrFilterDefaultAction, MsrFilterRange, MsrFilterRangeFlags};
246248
// The following example is used to verify that our public
247249
// structures are exported properly.
248250
/// # Example

0 commit comments

Comments
 (0)