@@ -19,10 +19,14 @@ use std::sync::LazyLock;
1919#[ cfg( gdb) ]
2020use kvm_bindings:: kvm_guest_debug;
2121use kvm_bindings:: {
22- kvm_debugregs, kvm_fpu, kvm_regs, kvm_sregs, kvm_userspace_memory_region, kvm_xsave,
22+ kvm_debugregs, kvm_enable_cap, kvm_fpu, kvm_regs, kvm_sregs, kvm_userspace_memory_region,
23+ kvm_xsave,
2324} ;
2425use kvm_ioctls:: Cap :: UserMemory ;
25- use kvm_ioctls:: { Kvm , VcpuExit , VcpuFd , VmFd } ;
26+ use kvm_ioctls:: {
27+ Cap , Kvm , MsrExitReason , MsrFilterDefaultAction , MsrFilterRange , MsrFilterRangeFlags , VcpuExit ,
28+ VcpuFd , VmFd ,
29+ } ;
2630use tracing:: { Span , instrument} ;
2731#[ cfg( feature = "trace_guest" ) ]
2832use tracing_opentelemetry:: OpenTelemetrySpanExt ;
@@ -136,6 +140,44 @@ impl KvmVm {
136140 debug_regs : kvm_guest_debug:: default ( ) ,
137141 } )
138142 }
143+
144+ /// Enable MSR filtering: tell KVM to exit to userspace on filtered MSR
145+ /// access, then install a deny-all filter so every RDMSR/WRMSR traps.
146+ ///
147+ /// Requires KVM_CAP_X86_USER_SPACE_MSR and KVM_CAP_X86_MSR_FILTER
148+ pub ( crate ) fn enable_msr_filter ( & self ) -> std:: result:: Result < ( ) , CreateVmError > {
149+ let hv = KVM . as_ref ( ) . map_err ( |e| e. clone ( ) ) ?;
150+ if !hv. check_extension ( Cap :: X86UserSpaceMsr ) || !hv. check_extension ( Cap :: X86MsrFilter ) {
151+ tracing:: error!(
152+ "KVM does not support KVM_CAP_X86_USER_SPACE_MSR or KVM_CAP_X86_MSR_FILTER."
153+ ) ;
154+ return Err ( CreateVmError :: MsrFilterNotSupported ) ;
155+ }
156+
157+ let cap = kvm_enable_cap {
158+ cap : Cap :: X86UserSpaceMsr as u32 ,
159+ args : [ MsrExitReason :: Filter . bits ( ) as u64 , 0 , 0 , 0 ] ,
160+ ..Default :: default ( )
161+ } ;
162+ self . vm_fd
163+ . enable_cap ( & cap)
164+ . map_err ( |e| CreateVmError :: InitializeVm ( e. into ( ) ) ) ?;
165+
166+ // At least one range is required when using KVM_MSR_FILTER_DEFAULT_DENY.
167+ let bitmap = [ 0u8 ; 1 ] ; // 1 byte covers 8 MSRs, all bits 0 (deny)
168+ self . vm_fd
169+ . set_msr_filter (
170+ MsrFilterDefaultAction :: DENY ,
171+ & [ MsrFilterRange {
172+ flags : MsrFilterRangeFlags :: READ | MsrFilterRangeFlags :: WRITE ,
173+ base : 0 ,
174+ msr_count : 1 ,
175+ bitmap : & bitmap,
176+ } ] ,
177+ )
178+ . map_err ( |e| CreateVmError :: InitializeVm ( e. into ( ) ) ) ?;
179+ Ok ( ( ) )
180+ }
139181}
140182
141183impl VirtualMachine for KvmVm {
@@ -176,6 +218,40 @@ impl VirtualMachine for KvmVm {
176218 Ok ( VcpuExit :: IoOut ( port, data) ) => Ok ( VmExit :: IoOut ( port, data. to_vec ( ) ) ) ,
177219 Ok ( VcpuExit :: MmioRead ( addr, _) ) => Ok ( VmExit :: MmioRead ( addr) ) ,
178220 Ok ( VcpuExit :: MmioWrite ( addr, _) ) => Ok ( VmExit :: MmioWrite ( addr) ) ,
221+ // KVM_EXIT_X86_RDMSR / KVM_EXIT_X86_WRMSR (KVM API §5, kvm_run structure):
222+ //
223+ // The "index" field tells userspace which MSR the guest wants to
224+ // read/write. If the request was unsuccessful, userspace indicates
225+ // that with a "1" in the "error" field. "This will inject a #GP
226+ // into the guest when the VCPU is executed again."
227+ //
228+ // "for KVM_EXIT_IO, KVM_EXIT_MMIO, [...] KVM_EXIT_X86_RDMSR and
229+ // KVM_EXIT_X86_WRMSR the corresponding operations are complete
230+ // (and guest state is consistent) only after userspace has
231+ // re-entered the kernel with KVM_RUN."
232+ //
233+ // We set error=1 and then re-run with `immediate_exit` to let KVM
234+ // inject the #GP without executing further guest code. From the
235+ // kvm_run docs: "[immediate_exit] is polled once when KVM_RUN
236+ // starts; if non-zero, KVM_RUN exits immediately, returning
237+ // -EINTR."
238+ Ok ( VcpuExit :: X86Rdmsr ( msr_exit) ) => {
239+ let msr_index = msr_exit. index ;
240+ * msr_exit. error = 1 ;
241+ self . vcpu_fd . set_kvm_immediate_exit ( 1 ) ;
242+ let _ = self . vcpu_fd . run ( ) ;
243+ self . vcpu_fd . set_kvm_immediate_exit ( 0 ) ;
244+ Ok ( VmExit :: MsrRead ( msr_index) )
245+ }
246+ Ok ( VcpuExit :: X86Wrmsr ( msr_exit) ) => {
247+ let msr_index = msr_exit. index ;
248+ let value = msr_exit. data ;
249+ * msr_exit. error = 1 ;
250+ self . vcpu_fd . set_kvm_immediate_exit ( 1 ) ;
251+ let _ = self . vcpu_fd . run ( ) ;
252+ self . vcpu_fd . set_kvm_immediate_exit ( 0 ) ;
253+ Ok ( VmExit :: MsrWrite { msr_index, value } )
254+ }
179255 #[ cfg( gdb) ]
180256 Ok ( VcpuExit :: Debug ( debug_exit) ) => Ok ( VmExit :: Debug {
181257 dr6 : debug_exit. dr6 ,
0 commit comments