Skip to content

Commit 68751e4

Browse files
committed
Disallow read/write of MSRs
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent 84e396a commit 68751e4

14 files changed

Lines changed: 309 additions & 19 deletions

File tree

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: 26 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:
@@ -940,6 +963,7 @@ impl HyperlightVm {
940963
.set_sregs(&CommonSpecialRegisters::standard_real_mode_defaults())?;
941964
}
942965

966+
// MSRs are not reset because we disallow read/write of MSRs, unless explicitly unsafely allowed by the user.
943967
Ok(())
944968
}
945969

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

Lines changed: 70 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,

src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ pub(crate) enum VmExit {
130130
MmioRead(u64),
131131
/// The vCPU tried to write to the given (unmapped) addr
132132
MmioWrite(u64),
133+
/// The vCPU tried to read from the given MSR
134+
MsrRead(u32),
135+
/// The vCPU tried to write to the given MSR with the given value
136+
MsrWrite { msr_index: u32, value: u64 },
133137
/// The vCPU execution has been cancelled
134138
Cancelled(),
135139
/// The vCPU has exited for a reason that is not handled by Hyperlight
@@ -170,6 +174,8 @@ pub enum CreateVmError {
170174
CreateVcpuFd(HypervisorError),
171175
#[error("VM creation failed: {0}")]
172176
CreateVmFd(HypervisorError),
177+
#[error("Failed to enable MSR intercept: {0}")]
178+
EnableMsrIntercept(HypervisorError),
173179
#[error("Hypervisor is not available: {0}")]
174180
HypervisorNotAvailable(HypervisorError),
175181
#[error("Initialize VM failed: {0}")]
@@ -184,8 +190,10 @@ pub enum CreateVmError {
184190
/// RunVCPU error
185191
#[derive(Debug, Clone, thiserror::Error)]
186192
pub enum RunVcpuError {
187-
#[error("Failed to decode message type: {0}")]
193+
#[error("Failed to decode IO message type: {0}")]
188194
DecodeIOMessage(u32),
195+
#[error("Failed to decode MSR message type: {0}")]
196+
DecodeMsrMessage(u32),
189197
#[cfg(gdb)]
190198
#[error("Failed to get DR6 debug register: {0}")]
191199
GetDr6(HypervisorError),
@@ -344,6 +352,10 @@ pub(crate) trait VirtualMachine: Debug + Send {
344352
#[cfg(feature = "init-paging")]
345353
fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError>;
346354

355+
/// Enable MSR intercepts for this VM. When enabled, all MSR reads and
356+
/// writes by the guest will cause a VM exit instead of being executed.
357+
fn enable_msr_intercept(&mut self) -> std::result::Result<(), CreateVmError>;
358+
347359
/// Get partition handle
348360
#[cfg(target_os = "windows")]
349361
fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;

src/hyperlight_host/src/hypervisor/virtual_machine/mshv.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,16 @@ use std::sync::LazyLock;
2121
#[cfg(gdb)]
2222
use mshv_bindings::{DebugRegisters, hv_message_type_HVMSG_X64_EXCEPTION_INTERCEPT};
2323
use mshv_bindings::{
24-
FloatingPointUnit, SpecialRegisters, StandardRegisters, XSave, hv_message_type,
24+
FloatingPointUnit, HV_INTERCEPT_ACCESS_MASK_READ, HV_INTERCEPT_ACCESS_MASK_WRITE,
25+
HV_INTERCEPT_ACCESS_READ, HV_INTERCEPT_ACCESS_WRITE, SpecialRegisters, StandardRegisters,
26+
XSave, hv_intercept_type_HV_INTERCEPT_TYPE_X64_MSR, hv_message_type,
2527
hv_message_type_HVMSG_GPA_INTERCEPT, hv_message_type_HVMSG_UNMAPPED_GPA,
2628
hv_message_type_HVMSG_X64_HALT, hv_message_type_HVMSG_X64_IO_PORT_INTERCEPT,
29+
hv_message_type_HVMSG_X64_MSR_INTERCEPT,
2730
hv_partition_property_code_HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES,
2831
hv_partition_synthetic_processor_features, hv_register_assoc,
29-
hv_register_name_HV_X64_REGISTER_RIP, hv_register_value, mshv_user_mem_region,
32+
hv_register_name_HV_X64_REGISTER_RIP, hv_register_value, mshv_install_intercept,
33+
mshv_user_mem_region,
3034
};
3135
use mshv_ioctls::{Mshv, VcpuFd, VmFd};
3236
use tracing::{Span, instrument};
@@ -108,6 +112,18 @@ impl MshvVm {
108112
}
109113

110114
impl VirtualMachine for MshvVm {
115+
fn enable_msr_intercept(&mut self) -> std::result::Result<(), CreateVmError> {
116+
let intercept = mshv_install_intercept {
117+
access_type_mask: HV_INTERCEPT_ACCESS_MASK_WRITE | HV_INTERCEPT_ACCESS_MASK_READ,
118+
intercept_type: hv_intercept_type_HV_INTERCEPT_TYPE_X64_MSR,
119+
intercept_parameter: Default::default(),
120+
};
121+
self.vm_fd
122+
.install_intercept(intercept)
123+
.map_err(|e| CreateVmError::EnableMsrIntercept(e.into()))?;
124+
Ok(())
125+
}
126+
111127
unsafe fn map_memory(
112128
&mut self,
113129
(_slot, region): (u32, &MemoryRegion),
@@ -137,6 +153,7 @@ impl VirtualMachine for MshvVm {
137153
hv_message_type_HVMSG_X64_IO_PORT_INTERCEPT;
138154
const UNMAPPED_GPA_MESSAGE: hv_message_type = hv_message_type_HVMSG_UNMAPPED_GPA;
139155
const INVALID_GPA_ACCESS_MESSAGE: hv_message_type = hv_message_type_HVMSG_GPA_INTERCEPT;
156+
const MSR_MESSAGE: hv_message_type = hv_message_type_HVMSG_X64_MSR_INTERCEPT;
140157
#[cfg(gdb)]
141158
const EXCEPTION_INTERCEPT: hv_message_type = hv_message_type_HVMSG_X64_EXCEPTION_INTERCEPT;
142159

@@ -196,6 +213,23 @@ impl VirtualMachine for MshvVm {
196213
_ => VmExit::Unknown("Unknown MMIO access".to_string()),
197214
}
198215
}
216+
MSR_MESSAGE => {
217+
let msr_message = m
218+
.to_msr_info()
219+
.map_err(|_| RunVcpuError::DecodeMsrMessage(m.header.message_type))?;
220+
let edx = msr_message.rdx;
221+
let eax = msr_message.rax;
222+
let written_value = (edx << 32) | eax;
223+
let access = msr_message.header.intercept_access_type as u32;
224+
match access {
225+
HV_INTERCEPT_ACCESS_READ => VmExit::MsrRead(msr_message.msr_number),
226+
HV_INTERCEPT_ACCESS_WRITE => VmExit::MsrWrite {
227+
msr_index: msr_message.msr_number,
228+
value: written_value,
229+
},
230+
_ => VmExit::Unknown(format!("Unknown MSR access type={}", access)),
231+
}
232+
}
199233
#[cfg(gdb)]
200234
EXCEPTION_INTERCEPT => {
201235
let ex_info = m

src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,23 @@ impl WhpVm {
132132
}
133133

134134
impl VirtualMachine for WhpVm {
135+
fn enable_msr_intercept(&mut self) -> std::result::Result<(), CreateVmError> {
136+
let mut extended_exits_property = WHV_PARTITION_PROPERTY::default();
137+
// X64MsrExit bit position (bit 1) in WHV_EXTENDED_VM_EXITS
138+
// See https://learn.microsoft.com/en-us/virtualization/api/hypervisor-platform/funcs/whvpartitionpropertydatatypes
139+
extended_exits_property.ExtendedVmExits.AsUINT64 = 1 << 1;
140+
unsafe {
141+
WHvSetPartitionProperty(
142+
self.partition,
143+
WHvPartitionPropertyCodeExtendedVmExits,
144+
&extended_exits_property as *const _ as *const _,
145+
std::mem::size_of::<WHV_PARTITION_PROPERTY>() as _,
146+
)
147+
.map_err(|e| CreateVmError::EnableMsrIntercept(e.into()))?
148+
};
149+
Ok(())
150+
}
151+
135152
unsafe fn map_memory(
136153
&mut self,
137154
(_slot, region): (u32, &MemoryRegion),
@@ -271,6 +288,21 @@ impl VirtualMachine for WhpVm {
271288
}
272289
// Execution was cancelled by the host.
273290
WHvRunVpExitReasonCanceled => VmExit::Cancelled(),
291+
WHvRunVpExitReasonX64MsrAccess => {
292+
let msr_access = unsafe { exit_context.Anonymous.MsrAccess };
293+
let eax = msr_access.Rax;
294+
let edx = msr_access.Rdx;
295+
let written_value = (edx << 32) | eax;
296+
let access = unsafe { msr_access.AccessInfo.AsUINT32 };
297+
match access {
298+
0 => VmExit::MsrRead(msr_access.MsrNumber),
299+
1 => VmExit::MsrWrite {
300+
msr_index: msr_access.MsrNumber,
301+
value: written_value,
302+
},
303+
_ => VmExit::Unknown(format!("Unknown MSR access type={}", access)),
304+
}
305+
}
274306
#[cfg(gdb)]
275307
WHvRunVpExitReasonException => {
276308
let exception = unsafe { exit_context.Anonymous.VpException };

0 commit comments

Comments
 (0)