Skip to content

Commit 967003a

Browse files
committed
Fix cargo feature 'crashdump'
1 parent 8d0cb80 commit 967003a

File tree

10 files changed

+113
-98
lines changed

10 files changed

+113
-98
lines changed

.github/workflows/dep_rust.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ jobs:
131131
# with only one driver enabled (driver mshv/kvm feature is ignored on windows) + seccomp + inprocess
132132
just test-rust ${{ matrix.config }} inprocess,seccomp,${{ matrix.hypervisor == 'mshv' && 'mshv' || 'kvm' }}
133133
134+
# make sure certain cargo features compile
135+
cargo check -p hyperlight-host --features dump_on_crash
136+
134137
# without any driver (shouldn't compile)
135138
just test-rust-feature-compilation-fail ${{ matrix.config }}
136139

src/hyperlight_host/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,15 @@ cfg_aliases = "0.2.1"
112112
built = { version = "0.7.0", features = ["chrono", "git2"] }
113113

114114
[features]
115-
default = ["kvm", "mshv", "seccomp"]
115+
default = ["kvm", "mshv", "seccomp", "crashdump"]
116116
seccomp = ["dep:seccompiler"]
117117
function_call_metrics = []
118118
executable_heap = []
119119
# This feature enables printing of debug information to stdout in debug builds
120120
print_debug = []
121121
# This feature enables dunping of the VMs details to a file when an unexpected or error exit occurs in a VM in debug mode
122122
# the name of the file is output to stdout and logged.
123-
dump_on_crash = ["dep:tempfile"]
123+
crashdump = ["dep:tempfile"]
124124
kvm = ["dep:kvm-bindings", "dep:kvm-ioctls"]
125125
mshv = ["dep:mshv-bindings", "dep:mshv-ioctls"]
126126
inprocess = []

src/hyperlight_host/build.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ fn main() -> Result<()> {
9494
// inprocess feature is aliased with debug_assertions to make it only available in debug-builds.
9595
// You should never use #[cfg(feature = "inprocess")] in the codebase. Use #[cfg(inprocess)] instead.
9696
inprocess: { all(feature = "inprocess", debug_assertions) },
97+
// crashdump feature is aliased with debug_assertions to make it only available in debug-builds.
98+
crashdump: { all(feature = "crashdump", debug_assertions) },
9799
}
98100

99101
write_built_file()?;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use std::io::Write;
2+
3+
use tempfile::NamedTempFile;
4+
5+
use super::Hypervisor;
6+
use crate::{new_error, Result};
7+
8+
/// Dump registers + memory regions + raw memory to a tempfile
9+
#[cfg(crashdump)]
10+
pub(crate) fn crashdump_to_tempfile(hv: &dyn Hypervisor) -> Result<()> {
11+
let mem_regions = hv.get_memory_regions();
12+
let mem_size = mem_regions
13+
.iter()
14+
.map(|region| region.host_region.len())
15+
.sum();
16+
let mem_start_addr = mem_regions[0].host_region.start as *const u8;
17+
18+
if mem_start_addr.is_null() || mem_size == 0 {
19+
return Err(new_error!(
20+
"Invalid address or size while creating crashdump"
21+
));
22+
}
23+
24+
let mut temp_file = NamedTempFile::with_prefix("mem")?;
25+
26+
let hv_details = format!("{:#x?}", hv);
27+
28+
// write hypervisor details such as registers, memory regions, etc.
29+
temp_file.write_all(hv_details.as_bytes())?;
30+
// write memory dump
31+
temp_file.write_all(b"================ MEMORY DUMP =================\n")?;
32+
// SAFETY: Address and size non-null and non-zero
33+
unsafe {
34+
let slice = std::slice::from_raw_parts(mem_start_addr, mem_size);
35+
temp_file.write_all(slice)?;
36+
temp_file.flush()?;
37+
}
38+
let persist_path = temp_file.path().with_extension("dmp");
39+
temp_file
40+
.persist(&persist_path)
41+
.map_err(|e| new_error!("Failed to persist crashdump file: {:?}", e))?;
42+
43+
println!("Memory dumped to file: {:?}", persist_path);
44+
log::error!("Memory dumped to file: {:?}", persist_path);
45+
46+
Ok(())
47+
}

src/hyperlight_host/src/hypervisor/hyperv_linux.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,6 @@ impl Hypervisor for HypervLinuxDriver {
311311
"mshv MMIO unmapped GPA -Details: Address: {} \n {:#?}",
312312
addr, &self
313313
);
314-
#[cfg(all(debug_assertions, feature = "dump_on_crash"))]
315-
self.dump_on_crash(self.mem_regions.clone());
316314
HyperlightExit::Mmio(addr)
317315
}
318316
INVALID_GPA_ACCESS_MESSAGE => {
@@ -323,8 +321,6 @@ impl Hypervisor for HypervLinuxDriver {
323321
"mshv MMIO invalid GPA access -Details: Address: {} \n {:#?}",
324322
gpa, &self
325323
);
326-
#[cfg(all(debug_assertions, feature = "dump_on_crash"))]
327-
self.dump_on_crash(self.mem_regions.clone());
328324
match self.get_memory_access_violation(
329325
gpa as usize,
330326
&self.mem_regions,
@@ -336,8 +332,6 @@ impl Hypervisor for HypervLinuxDriver {
336332
}
337333
other => {
338334
debug!("mshv Other Exit: Exit: {:#?} \n {:#?}", other, &self);
339-
#[cfg(all(debug_assertions, feature = "dump_on_crash"))]
340-
self.dump_on_crash(self.mem_regions.clone());
341335
log_then_return!("unknown Hyper-V run message type {:?}", other);
342336
}
343337
},
@@ -347,8 +341,6 @@ impl Hypervisor for HypervLinuxDriver {
347341
libc::EAGAIN => HyperlightExit::Retry(),
348342
_ => {
349343
debug!("mshv Error - Details: Error: {} \n {:#?}", e, &self);
350-
#[cfg(all(debug_assertions, feature = "dump_on_crash"))]
351-
self.dump_on_crash(self.mem_regions.clone());
352344
log_then_return!("Error running VCPU {:?}", e);
353345
}
354346
},
@@ -360,6 +352,11 @@ impl Hypervisor for HypervLinuxDriver {
360352
fn as_mut_hypervisor(&mut self) -> &mut dyn Hypervisor {
361353
self as &mut dyn Hypervisor
362354
}
355+
356+
#[cfg(crashdump)]
357+
fn get_memory_regions(&self) -> &[MemoryRegion] {
358+
&self.mem_regions
359+
}
363360
}
364361

365362
impl Drop for HypervLinuxDriver {

src/hyperlight_host/src/hypervisor/inprocess.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ use std::fmt::Debug;
1818
use std::os::raw::c_void;
1919

2020
use super::{HyperlightExit, Hypervisor};
21+
#[cfg(crashdump)]
22+
use crate::mem::memory_region::MemoryRegion;
2123
use crate::sandbox::leaked_outb::LeakedOutBWrapper;
2224
use crate::Result;
2325

@@ -123,4 +125,9 @@ impl<'a> Hypervisor for InprocessDriver<'a> {
123125
fn get_partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE {
124126
unimplemented!("get_partition_handle should not be needed since we are in in-process mode")
125127
}
128+
129+
#[cfg(crashdump)]
130+
fn get_memory_regions(&self) -> &[MemoryRegion] {
131+
unimplemented!("get_memory_regions is not supported since we are in in-process mode")
132+
}
126133
}

src/hyperlight_host/src/hypervisor/kvm.rs

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ limitations under the License.
1717
use std::convert::TryFrom;
1818
use std::fmt::Debug;
1919

20-
use cfg_if::cfg_if;
2120
use kvm_bindings::{kvm_fpu, kvm_regs, kvm_userspace_memory_region, KVM_MEM_READONLY};
2221
use kvm_ioctls::Cap::UserMemory;
2322
use kvm_ioctls::{Kvm, VcpuExit, VcpuFd, VmFd};
@@ -283,11 +282,9 @@ impl Hypervisor for KVMDriver {
283282
}
284283
Ok(VcpuExit::MmioRead(addr, _)) => {
285284
debug!("KVM MMIO Read -Details: Address: {} \n {:#?}", addr, &self);
286-
#[cfg(all(debug_assertions, feature = "dump_on_crash"))]
287-
self.dump_on_crash(self.mem_regions.clone());
288-
let gpa = addr as usize;
285+
289286
match self.get_memory_access_violation(
290-
gpa,
287+
addr as usize,
291288
&self.mem_regions,
292289
MemoryRegionFlags::READ,
293290
) {
@@ -297,11 +294,9 @@ impl Hypervisor for KVMDriver {
297294
}
298295
Ok(VcpuExit::MmioWrite(addr, _)) => {
299296
debug!("KVM MMIO Write -Details: Address: {} \n {:#?}", addr, &self);
300-
#[cfg(all(debug_assertions, feature = "dump_on_crash"))]
301-
self.dump_on_crash(self.mem_regions.clone());
302-
let gpa = addr as usize;
297+
303298
match self.get_memory_access_violation(
304-
gpa,
299+
addr as usize,
305300
&self.mem_regions,
306301
MemoryRegionFlags::WRITE,
307302
) {
@@ -315,25 +310,12 @@ impl Hypervisor for KVMDriver {
315310
libc::EAGAIN => HyperlightExit::Retry(),
316311
_ => {
317312
debug!("KVM Error -Details: Address: {} \n {:#?}", e, &self);
318-
#[cfg(all(debug_assertions, feature = "dump_on_crash"))]
319-
self.dump_on_crash(self.mem_regions.clone());
320313
log_then_return!("Error running VCPU {:?}", e);
321314
}
322315
},
323316
Ok(other) => {
324-
cfg_if! {
325-
if #[cfg(all(feature = "print_debug", debug_assertions))] {
326-
let _ = other;
327-
debug!("KVM Other Exit: \n {:#?}", &self);
328-
HyperlightExit::Unknown("Unexpected KVM Exit".to_string())
329-
} else if #[cfg(all(feature = "dump_on_crash", debug_assertions))] {
330-
self.dump_on_crash(self.mem_regions.clone());
331-
HyperlightExit::Unknown(format!("Unexpected KVM Exit {:?}", other))
332-
} else{
333-
debug!("KVM Other Exit {:?}", other);
334-
HyperlightExit::Unknown(format!("Unexpected KVM Exit {:?}", other))
335-
}
336-
}
317+
debug!("KVM Other Exit {:?}", other);
318+
HyperlightExit::Unknown(format!("Unexpected KVM Exit {:?}", other))
337319
}
338320
};
339321
Ok(result)
@@ -343,6 +325,11 @@ impl Hypervisor for KVMDriver {
343325
fn as_mut_hypervisor(&mut self) -> &mut dyn Hypervisor {
344326
self as &mut dyn Hypervisor
345327
}
328+
329+
#[cfg(crashdump)]
330+
fn get_memory_regions(&self) -> &[MemoryRegion] {
331+
&self.mem_regions
332+
}
346333
}
347334

348335
#[cfg(test)]

src/hyperlight_host/src/hypervisor/mod.rs

Lines changed: 9 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ pub(crate) mod windows_hypervisor_platform;
5555
#[cfg(target_os = "windows")]
5656
pub(crate) mod wrappers;
5757

58+
#[cfg(crashdump)]
59+
pub(crate) mod crashdump;
60+
5861
use std::fmt::Debug;
5962
use std::sync::{Arc, Mutex};
6063

@@ -184,70 +187,8 @@ pub(crate) trait Hypervisor: Debug + Sync + Send {
184187
#[cfg(target_os = "windows")]
185188
fn get_partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;
186189

187-
/// Dump memory to a file on crash
188-
#[cfg(all(debug_assertions, feature = "dump_on_crash", target_os = "linux"))]
189-
fn dump_on_crash(&self, mem_regions: Vec<MemoryRegion>) {
190-
let memory_size = mem_regions
191-
.iter()
192-
.map(|region| region.guest_region.end - region.guest_region.start)
193-
.sum();
194-
if let Err(e) = unsafe {
195-
self.write_dump_file(
196-
mem_regions.clone(),
197-
mem_regions[0].host_region.start as *const u8,
198-
memory_size,
199-
)
200-
} {
201-
println!("Error dumping memory: {}", e);
202-
}
203-
}
204-
205-
/// A function that takes an address and a size and writes the memory at that address to a file in the temp/tmp directory
206-
/// # Safety
207-
/// This function is unsafe because it is writing memory to a file, make sure that the address is valid and that the size is correct
208-
/// This function is only available when the `dump_on_crash` feature is enabled and running in debug mode
209-
#[cfg(all(feature = "dump_on_crash", debug_assertions))]
210-
unsafe fn write_dump_file(
211-
&self,
212-
regions: Vec<MemoryRegion>,
213-
address: *const u8,
214-
size: usize,
215-
) -> Result<()> {
216-
use std::io::Write;
217-
218-
use tempfile::NamedTempFile;
219-
220-
if address.is_null() || size == 0 {
221-
return Err(new_error!("Invalid address or size"));
222-
}
223-
224-
let hv_details = format!("{:#?}", self);
225-
let regions_details = format!("{:#?}", regions);
226-
227-
// Create a temporary file
228-
let mut file = NamedTempFile::with_prefix("mem")?;
229-
let temp_path = file.path().to_path_buf();
230-
231-
file.write_all(hv_details.as_bytes())?;
232-
file.write_all(b"\n")?;
233-
file.write_all(regions_details.as_bytes())?;
234-
file.write_all(b"\n")?;
235-
236-
// SAFETY: Ensure the address and size are valid and accessible
237-
unsafe {
238-
let slice = std::slice::from_raw_parts(address, size);
239-
file.write_all(slice)?;
240-
file.flush()?;
241-
}
242-
let persist_path = temp_path.with_extension("dmp");
243-
file.persist(&persist_path)
244-
.map_err(|e| new_error!("Failed to persist file: {:?}", e))?;
245-
246-
print!("Memory dumped to file: {:?}", persist_path);
247-
log::error!("Memory dumped to file: {:?}", persist_path);
248-
249-
Ok(())
250-
}
190+
#[cfg(crashdump)]
191+
fn get_memory_regions(&self) -> &[MemoryRegion];
251192
}
252193

253194
/// A virtual CPU that can be run until an exit occurs
@@ -271,11 +212,15 @@ impl VirtualCPU {
271212
hv.handle_io(port, data, rip, instruction_length, outb_handle_fn.clone())?
272213
}
273214
HyperlightExit::Mmio(addr) => {
215+
#[cfg(crashdump)]
216+
crashdump::crashdump_to_tempfile(hv)?;
217+
274218
mem_access_fn
275219
.clone()
276220
.try_lock()
277221
.map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
278222
.call()?;
223+
279224
log_then_return!("MMIO access address {:#x}", addr);
280225
}
281226
HyperlightExit::AccessViolation(addr, tried, region_permission) => {

src/hyperlight_host/tests/integration_test.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,17 @@ fn recursive_stack_allocate_overflow() {
493493
assert!(matches!(res, HyperlightError::StackOverflow()));
494494
}
495495

496+
#[test]
497+
fn read_invalid_address() {
498+
let sbox1: SingleUseSandbox = new_uninit().unwrap().evolve(Noop::default()).unwrap();
499+
500+
let res = sbox1
501+
.call_guest_function_by_name("MmioRead", ReturnType::Int, None)
502+
.unwrap_err();
503+
504+
assert!(matches!(res, HyperlightError::Error(msg) if msg.starts_with("MMIO access address")));
505+
}
506+
496507
// Check that log messages are emitted correctly from the guest
497508
// This test is ignored as it sets a logger and therefore maybe impacted by other tests running concurrently
498509
// or it may impact other tests.

src/tests/rust_guests/simpleguest/src/main.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,14 @@ fn add(function_call: &FunctionCall) -> Result<Vec<u8>> {
724724
}
725725
}
726726

727+
// Used to test the behavior of the sandbox when reading from an invalid address.
728+
// Currently, the vm will exit with an MMIO exit
729+
fn mmio_read(_: &FunctionCall) -> Result<Vec<u8>> {
730+
let ptr: *const i32 = core::ptr::null();
731+
let val = unsafe { *(ptr.add(5000000)) };
732+
Ok(get_flatbuffer_result_from_int(val))
733+
}
734+
727735
#[no_mangle]
728736
pub extern "C" fn hyperlight_main() {
729737
let set_static_def = GuestFunctionDefinition::new(
@@ -1105,6 +1113,14 @@ pub extern "C" fn hyperlight_main() {
11051113
add as i64,
11061114
);
11071115
register_function(add_def);
1116+
1117+
let mmio_read_def = GuestFunctionDefinition::new(
1118+
"MmioRead".to_string(),
1119+
Vec::new(),
1120+
ReturnType::Int,
1121+
mmio_read as i64,
1122+
);
1123+
register_function(mmio_read_def);
11081124
}
11091125

11101126
#[no_mangle]

0 commit comments

Comments
 (0)