Skip to content

Commit 3f30312

Browse files
committed
Disallow read/write of MSRs
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> SOme clean stuff Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Fix some tests Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent 00b0804 commit 3f30312

File tree

11 files changed

+697
-13
lines changed

11 files changed

+697
-13
lines changed

Cargo.lock

Lines changed: 2 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/hyperlight_host/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ windows-version = "0.1"
7575
lazy_static = "1.4.0"
7676

7777
[target.'cfg(unix)'.dependencies]
78-
kvm-bindings = { version = "0.14", features = ["fam-wrappers"], optional = true }
79-
kvm-ioctls = { version = "0.24", optional = true }
78+
kvm-bindings = { git = "https://github.com/rust-vmm/kvm", rev = "3ffc9b62af5978553f73cc0ec79fad13fdd47146", features = ["fam-wrappers"], optional = true }
79+
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm", rev = "3ffc9b62af5978553f73cc0ec79fad13fdd47146", optional = true }
8080
mshv-bindings = { version = "0.6", optional = true }
8181
mshv-ioctls = { version = "0.6", optional = true}
8282

src/hyperlight_host/src/error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,14 @@ pub enum HyperlightError {
144144
#[error("Memory Access Violation at address {0:#x} of type {1}, but memory is marked as {2}")]
145145
MemoryAccessViolation(u64, MemoryRegionFlags, MemoryRegionFlags),
146146

147+
/// MSR Read Violation. Guest attempted to read from a Model-Specific Register
148+
#[error("Guest attempted to read from MSR {0:#x}")]
149+
MsrReadViolation(u32),
150+
151+
/// MSR Write Violation. Guest attempted to write to a Model-Specific Register
152+
#[error("Guest attempted to write {1:#x} to MSR {0:#x}")]
153+
MsrWriteViolation(u32, u64),
154+
147155
/// Memory Allocation Failed.
148156
#[error("Memory Allocation Failed with OS Error {0:?}.")]
149157
MemoryAllocationFailed(Option<i32>),
@@ -325,6 +333,8 @@ impl HyperlightError {
325333
| HyperlightError::ExecutionAccessViolation(_)
326334
| HyperlightError::StackOverflow()
327335
| HyperlightError::MemoryAccessViolation(_, _, _)
336+
| HyperlightError::MsrReadViolation(_)
337+
| HyperlightError::MsrWriteViolation(_, _)
328338
| HyperlightError::SnapshotSizeMismatch(_, _)
329339
| HyperlightError::MemoryRegionSizeMismatch(_, _, _)
330340
// HyperlightVmError::Restore is already handled manually in restore(), but we mark it

src/hyperlight_host/src/hypervisor/hyperlight_vm.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,14 @@ impl DispatchGuestCallError {
155155
region_flags,
156156
}) => HyperlightError::MemoryAccessViolation(addr, access_type, region_flags),
157157

158+
DispatchGuestCallError::Run(RunVmError::MsrReadViolation(msr_index)) => {
159+
HyperlightError::MsrReadViolation(msr_index)
160+
}
161+
162+
DispatchGuestCallError::Run(RunVmError::MsrWriteViolation { msr_index, value }) => {
163+
HyperlightError::MsrWriteViolation(msr_index, value)
164+
}
165+
158166
// Leave others as is
159167
other => HyperlightVmError::DispatchGuestCall(other).into(),
160168
};
@@ -203,6 +211,10 @@ pub enum RunVmError {
203211
MmioReadUnmapped(u64),
204212
#[error("MMIO WRITE access to unmapped address {0:#x}")]
205213
MmioWriteUnmapped(u64),
214+
#[error("Guest attempted to read from MSR {0:#x}")]
215+
MsrReadViolation(u32),
216+
#[error("Guest attempted to write {value:#x} to MSR {msr_index:#x}")]
217+
MsrWriteViolation { msr_index: u32, value: u64 },
206218
#[error("vCPU run failed: {0}")]
207219
RunVcpu(#[from] RunVcpuError),
208220
#[error("Unexpected VM exit: {0}")]
@@ -340,7 +352,7 @@ impl HyperlightVm {
340352
_pml4_addr: u64,
341353
entrypoint: Option<u64>,
342354
rsp_gva: u64,
343-
#[cfg_attr(target_os = "windows", allow(unused_variables))] config: &SandboxConfiguration,
355+
config: &SandboxConfiguration,
344356
#[cfg(gdb)] gdb_conn: Option<DebugCommChannel<DebugResponse, DebugMsg>>,
345357
#[cfg(crashdump)] rt_cfg: SandboxRuntimeConfig,
346358
#[cfg(feature = "mem_profile")] trace_info: MemTraceInfo,
@@ -350,7 +362,7 @@ impl HyperlightVm {
350362
#[cfg(not(gdb))]
351363
type VmType = Box<dyn VirtualMachine>;
352364

353-
let vm: VmType = match get_available_hypervisor() {
365+
let mut vm: VmType = match get_available_hypervisor() {
354366
#[cfg(kvm)]
355367
Some(HypervisorType::Kvm) => Box::new(KvmVm::new().map_err(VmError::CreateVm)?),
356368
#[cfg(mshv3)]
@@ -360,6 +372,11 @@ impl HyperlightVm {
360372
None => return Err(CreateHyperlightVmError::NoHypervisorFound),
361373
};
362374

375+
// Enable MSR intercepts unless the user explicitly allows MSR access
376+
if !config.get_allow_msr() {
377+
vm.enable_msr_intercept().map_err(VmError::CreateVm)?;
378+
}
379+
363380
#[cfg(feature = "init-paging")]
364381
vm.set_sregs(&CommonSpecialRegisters::standard_64bit_defaults(_pml4_addr))
365382
.map_err(VmError::Register)?;
@@ -811,6 +828,12 @@ impl HyperlightVm {
811828
}
812829
}
813830
}
831+
Ok(VmExit::MsrRead(msr_index)) => {
832+
break Err(RunVmError::MsrReadViolation(msr_index));
833+
}
834+
Ok(VmExit::MsrWrite { msr_index, value }) => {
835+
break Err(RunVmError::MsrWriteViolation { msr_index, value });
836+
}
814837
Ok(VmExit::Cancelled()) => {
815838
// If cancellation was not requested for this specific guest function call,
816839
// the vcpu was interrupted by a stale cancellation. This can occur when:
@@ -906,6 +929,7 @@ impl HyperlightVm {
906929
}
907930

908931
/// Resets the following vCPU state:
932+
/// - MSRs (see [`VIRTUALIZED_MSRS`](super::virtual_machine::VIRTUALIZED_MSRS))
909933
/// - General purpose registers
910934
/// - Debug registers
911935
/// - XSAVE (includes FPU/SSE state with proper FCW and MXCSR defaults)
@@ -916,6 +940,8 @@ impl HyperlightVm {
916940
cr3: u64,
917941
sregs: &CommonSpecialRegisters,
918942
) -> std::result::Result<(), RegisterError> {
943+
self.vm.reset_msrs()?;
944+
919945
self.vm.set_regs(&CommonRegisters {
920946
rflags: 1 << 1, // Reserved bit always set
921947
..Default::default()

src/hyperlight_host/src/hypervisor/virtual_machine/kvm.rs

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ use std::sync::LazyLock;
1919
#[cfg(gdb)]
2020
use kvm_bindings::kvm_guest_debug;
2121
use 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
};
2425
use 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+
};
2630
use tracing::{Span, instrument};
2731
#[cfg(feature = "trace_guest")]
2832
use tracing_opentelemetry::OpenTelemetrySpanExt;
@@ -139,6 +143,36 @@ impl KvmVm {
139143
}
140144

141145
impl 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,
@@ -327,6 +395,13 @@ impl VirtualMachine for KvmVm {
327395

328396
Ok(())
329397
}
398+
399+
fn reset_msrs(&self) -> std::result::Result<(), RegisterError> {
400+
// The KVM MSR filter (KVM_MSR_FILTER_DEFAULT_DENY) blocks all guest
401+
// MSR access at the hardware level, so no MSRs can be modified by the
402+
// guest and there is nothing to reset.
403+
Ok(())
404+
}
330405
}
331406

332407
#[cfg(gdb)]

0 commit comments

Comments
 (0)