@@ -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 ;
@@ -139,6 +143,36 @@ impl KvmVm {
139143}
140144
141145impl VirtualMachine for KvmVm {
146+ fn enable_msr_intercept ( & mut self ) -> std:: result:: Result < ( ) , CreateVmError > {
147+ let cap = kvm_enable_cap {
148+ cap : Cap :: X86UserSpaceMsr as u32 ,
149+ args : [ MsrExitReason :: Filter . bits ( ) as u64 , 0 , 0 , 0 ] ,
150+ ..Default :: default ( )
151+ } ;
152+ self . vm_fd
153+ . enable_cap ( & cap)
154+ . map_err ( |e| CreateVmError :: EnableMsrIntercept ( e. into ( ) ) ) ?;
155+
156+ // Install a deny-all MSR filter (KVM_X86_SET_MSR_FILTER).
157+ // At least one range is required when using KVM_MSR_FILTER_DEFAULT_DENY;
158+ // from the docs: "Calling this ioctl with an empty set of ranges
159+ // (all nmsrs == 0) disables MSR filtering. In that mode,
160+ // KVM_MSR_FILTER_DEFAULT_DENY is invalid and causes an error."
161+ let bitmap = [ 0u8 ; 1 ] ; // 1 byte covers 8 MSRs, all bits 0 (deny)
162+ self . vm_fd
163+ . set_msr_filter (
164+ MsrFilterDefaultAction :: DENY ,
165+ & [ MsrFilterRange {
166+ flags : MsrFilterRangeFlags :: READ | MsrFilterRangeFlags :: WRITE ,
167+ base : 0 ,
168+ msr_count : 1 ,
169+ bitmap : & bitmap,
170+ } ] ,
171+ )
172+ . map_err ( |e| CreateVmError :: EnableMsrIntercept ( e. into ( ) ) ) ?;
173+ Ok ( ( ) )
174+ }
175+
142176 unsafe fn map_memory (
143177 & mut self ,
144178 ( slot, region) : ( u32 , & MemoryRegion ) ,
@@ -176,6 +210,40 @@ impl VirtualMachine for KvmVm {
176210 Ok ( VcpuExit :: IoOut ( port, data) ) => Ok ( VmExit :: IoOut ( port, data. to_vec ( ) ) ) ,
177211 Ok ( VcpuExit :: MmioRead ( addr, _) ) => Ok ( VmExit :: MmioRead ( addr) ) ,
178212 Ok ( VcpuExit :: MmioWrite ( addr, _) ) => Ok ( VmExit :: MmioWrite ( addr) ) ,
213+ // KVM_EXIT_X86_RDMSR / KVM_EXIT_X86_WRMSR (KVM API §5, kvm_run structure):
214+ //
215+ // The "index" field tells userspace which MSR the guest wants to
216+ // read/write. If the request was unsuccessful, userspace indicates
217+ // that with a "1" in the "error" field. "This will inject a #GP
218+ // into the guest when the VCPU is executed again."
219+ //
220+ // "for KVM_EXIT_IO, KVM_EXIT_MMIO, [...] KVM_EXIT_X86_RDMSR and
221+ // KVM_EXIT_X86_WRMSR the corresponding operations are complete
222+ // (and guest state is consistent) only after userspace has
223+ // re-entered the kernel with KVM_RUN."
224+ //
225+ // We set error=1 and then re-run with `immediate_exit` to let KVM
226+ // inject the #GP without executing further guest code. From the
227+ // kvm_run docs: "[immediate_exit] is polled once when KVM_RUN
228+ // starts; if non-zero, KVM_RUN exits immediately, returning
229+ // -EINTR."
230+ Ok ( VcpuExit :: X86Rdmsr ( msr_exit) ) => {
231+ let msr_index = msr_exit. index ;
232+ * msr_exit. error = 1 ;
233+ self . vcpu_fd . set_kvm_immediate_exit ( 1 ) ;
234+ let _ = self . vcpu_fd . run ( ) ;
235+ self . vcpu_fd . set_kvm_immediate_exit ( 0 ) ;
236+ Ok ( VmExit :: MsrRead ( msr_index) )
237+ }
238+ Ok ( VcpuExit :: X86Wrmsr ( msr_exit) ) => {
239+ let msr_index = msr_exit. index ;
240+ let value = msr_exit. data ;
241+ * msr_exit. error = 1 ;
242+ self . vcpu_fd . set_kvm_immediate_exit ( 1 ) ;
243+ let _ = self . vcpu_fd . run ( ) ;
244+ self . vcpu_fd . set_kvm_immediate_exit ( 0 ) ;
245+ Ok ( VmExit :: MsrWrite { msr_index, value } )
246+ }
179247 #[ cfg( gdb) ]
180248 Ok ( VcpuExit :: Debug ( debug_exit) ) => Ok ( VmExit :: Debug {
181249 dr6 : debug_exit. dr6 ,
0 commit comments