Skip to content

Commit a63808c

Browse files
committed
Step 4: memory isolation with EPT
1 parent 169e8d2 commit a63808c

16 files changed

Lines changed: 851 additions & 33 deletions

File tree

hypervisor/src/hv/gconfig.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use rvm::GuestPhysAddr;
2+
3+
pub const GUEST_PHYS_MEMORY_BASE: GuestPhysAddr = 0;
4+
pub const GUEST_PHYS_MEMORY_SIZE: usize = 0x100_0000; // 16M
5+
6+
pub const GUEST_PT1: GuestPhysAddr = 0x1000;
7+
pub const GUEST_PT2: GuestPhysAddr = 0x2000;
8+
pub const GUEST_ENTRY: GuestPhysAddr = 0x8000;
9+
pub const GUEST_STACK_TOP: GuestPhysAddr = 0x7000;

hypervisor/src/hv/gpm.rs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
use alloc::collections::BTreeMap;
2+
use core::fmt::{Debug, Formatter, Result};
3+
4+
use rvm::{GuestPhysAddr, HostPhysAddr, MemFlags, NestedPageTable, RvmError, RvmResult};
5+
6+
use super::hal::RvmHalImpl;
7+
use crate::mm::{address::is_aligned, PAGE_SIZE};
8+
9+
#[derive(Debug)]
10+
enum Mapper {
11+
Offset(usize),
12+
}
13+
14+
#[derive(Debug)]
15+
pub struct GuestMemoryRegion {
16+
pub gpa: GuestPhysAddr,
17+
pub hpa: HostPhysAddr,
18+
pub size: usize,
19+
pub flags: MemFlags,
20+
}
21+
22+
pub struct MapRegion {
23+
pub start: GuestPhysAddr,
24+
pub size: usize,
25+
pub flags: MemFlags,
26+
mapper: Mapper,
27+
}
28+
29+
impl MapRegion {
30+
pub fn new_offset(
31+
start_gpa: GuestPhysAddr,
32+
start_hpa: HostPhysAddr,
33+
size: usize,
34+
flags: MemFlags,
35+
) -> Self {
36+
assert!(is_aligned(start_gpa));
37+
assert!(is_aligned(start_hpa));
38+
assert!(is_aligned(size));
39+
let offset = start_gpa - start_hpa;
40+
Self {
41+
start: start_gpa,
42+
size,
43+
flags,
44+
mapper: Mapper::Offset(offset),
45+
}
46+
}
47+
48+
fn is_overlap_with(&self, other: &Self) -> bool {
49+
let s0 = self.start;
50+
let e0 = s0 + self.size;
51+
let s1 = other.start;
52+
let e1 = s1 + other.size;
53+
!(e0 <= s1 || e1 <= s0)
54+
}
55+
56+
fn target(&self, gpa: GuestPhysAddr) -> HostPhysAddr {
57+
match self.mapper {
58+
Mapper::Offset(off) => gpa.wrapping_sub(off),
59+
}
60+
}
61+
62+
fn map_to(&self, npt: &mut NestedPageTable<RvmHalImpl>) -> RvmResult {
63+
let mut start = self.start;
64+
let end = start + self.size;
65+
while start < end {
66+
let target = self.target(start);
67+
npt.map(start, target, self.flags)?;
68+
start += PAGE_SIZE;
69+
}
70+
Ok(())
71+
}
72+
73+
fn unmap_to(&self, npt: &mut NestedPageTable<RvmHalImpl>) -> RvmResult {
74+
let mut start = self.start;
75+
let end = start + self.size;
76+
while start < end {
77+
npt.unmap(start)?;
78+
start += PAGE_SIZE;
79+
}
80+
Ok(())
81+
}
82+
}
83+
84+
impl Debug for MapRegion {
85+
fn fmt(&self, f: &mut Formatter) -> Result {
86+
f.debug_struct("MapRegion")
87+
.field("range", &(self.start..self.start + self.size))
88+
.field("size", &self.size)
89+
.field("flags", &self.flags)
90+
.field("mapper", &self.mapper)
91+
.finish()
92+
}
93+
}
94+
95+
impl From<GuestMemoryRegion> for MapRegion {
96+
fn from(r: GuestMemoryRegion) -> Self {
97+
Self::new_offset(r.gpa, r.hpa, r.size, r.flags)
98+
}
99+
}
100+
101+
pub struct GuestPhysMemorySet {
102+
regions: BTreeMap<GuestPhysAddr, MapRegion>,
103+
npt: NestedPageTable<RvmHalImpl>,
104+
}
105+
106+
impl GuestPhysMemorySet {
107+
pub fn new() -> RvmResult<Self> {
108+
Ok(Self {
109+
npt: NestedPageTable::new()?,
110+
regions: BTreeMap::new(),
111+
})
112+
}
113+
114+
pub fn nest_page_table_root(&self) -> HostPhysAddr {
115+
self.npt.root_paddr()
116+
}
117+
118+
fn test_free_area(&self, other: &MapRegion) -> bool {
119+
if let Some((_, before)) = self.regions.range(..other.start).last() {
120+
if before.is_overlap_with(other) {
121+
return false;
122+
}
123+
}
124+
if let Some((_, after)) = self.regions.range(other.start..).next() {
125+
if after.is_overlap_with(other) {
126+
return false;
127+
}
128+
}
129+
true
130+
}
131+
132+
pub fn map_region(&mut self, region: MapRegion) -> RvmResult {
133+
if region.size == 0 {
134+
return Ok(());
135+
}
136+
if !self.test_free_area(&region) {
137+
warn!(
138+
"MapRegion({:#x}..{:#x}) overlapped in:\n{:#x?}",
139+
region.start,
140+
region.start + region.size,
141+
self
142+
);
143+
return Err(RvmError::InvalidParam);
144+
}
145+
region.map_to(&mut self.npt)?;
146+
self.regions.insert(region.start, region);
147+
Ok(())
148+
}
149+
150+
pub fn clear(&mut self) {
151+
for region in self.regions.values() {
152+
region.unmap_to(&mut self.npt).unwrap();
153+
}
154+
self.regions.clear();
155+
}
156+
}
157+
158+
impl Drop for GuestPhysMemorySet {
159+
fn drop(&mut self) {
160+
self.clear();
161+
}
162+
}
163+
164+
impl Debug for GuestPhysMemorySet {
165+
fn fmt(&self, f: &mut Formatter) -> Result {
166+
f.debug_struct("GuestPhysMemorySet")
167+
.field("page_table_root", &self.nest_page_table_root())
168+
.field("regions", &self.regions)
169+
.finish()
170+
}
171+
}

hypervisor/src/hv/mod.rs

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,68 @@
1+
mod gconfig;
2+
mod gpm;
13
mod hal;
24
mod vmexit;
35

4-
use rvm::RvmPerCpu;
6+
use rvm::{GuestPhysAddr, HostVirtAddr, MemFlags, RvmPerCpu, RvmResult};
57

8+
use self::gconfig::*;
9+
use self::gpm::{GuestMemoryRegion, GuestPhysMemorySet};
610
use self::hal::RvmHalImpl;
11+
use crate::mm::address::virt_to_phys;
12+
13+
#[repr(align(4096))]
14+
struct AlignedMemory<const LEN: usize>([u8; LEN]);
15+
16+
static mut GUEST_PHYS_MEMORY: AlignedMemory<GUEST_PHYS_MEMORY_SIZE> =
17+
AlignedMemory([0; GUEST_PHYS_MEMORY_SIZE]);
18+
19+
fn gpa_as_mut_ptr(guest_paddr: GuestPhysAddr) -> *mut u8 {
20+
let offset = unsafe { &GUEST_PHYS_MEMORY as *const _ as usize };
21+
let host_vaddr = guest_paddr + offset;
22+
host_vaddr as *mut u8
23+
}
24+
25+
fn setup_guest_page_table() {
26+
use x86_64::structures::paging::{PageTable, PageTableFlags as PTF};
27+
let pt1 = unsafe { &mut *(gpa_as_mut_ptr(GUEST_PT1) as *mut PageTable) };
28+
let pt2 = unsafe { &mut *(gpa_as_mut_ptr(GUEST_PT2) as *mut PageTable) };
29+
// identity mapping
30+
pt1[0].set_addr(
31+
x86_64::PhysAddr::new(GUEST_PT2 as _),
32+
PTF::PRESENT | PTF::WRITABLE,
33+
);
34+
pt2[0].set_addr(
35+
x86_64::PhysAddr::new(0),
36+
PTF::PRESENT | PTF::WRITABLE | PTF::HUGE_PAGE,
37+
);
38+
}
39+
40+
fn setup_gpm() -> RvmResult<GuestPhysMemorySet> {
41+
setup_guest_page_table();
42+
43+
// copy guest code
44+
unsafe {
45+
core::ptr::copy_nonoverlapping(
46+
test_guest as usize as *const u8,
47+
gpa_as_mut_ptr(GUEST_ENTRY),
48+
0x100,
49+
);
50+
}
51+
52+
// create nested page table and add mapping
53+
let mut gpm = GuestPhysMemorySet::new()?;
54+
let guest_memory_regions = [GuestMemoryRegion {
55+
// RAM
56+
gpa: GUEST_PHYS_MEMORY_BASE,
57+
hpa: virt_to_phys(gpa_as_mut_ptr(GUEST_PHYS_MEMORY_BASE) as HostVirtAddr),
58+
size: GUEST_PHYS_MEMORY_SIZE,
59+
flags: MemFlags::READ | MemFlags::WRITE | MemFlags::EXECUTE,
60+
}];
61+
for r in guest_memory_regions.into_iter() {
62+
gpm.map_region(r.into())?;
63+
}
64+
Ok(gpm)
65+
}
766

867
pub fn run() -> ! {
968
println!("Starting virtualization...");
@@ -12,25 +71,31 @@ pub fn run() -> ! {
1271
let mut percpu = RvmPerCpu::<RvmHalImpl>::new(0);
1372
percpu.hardware_enable().unwrap();
1473

15-
let mut vcpu = percpu.create_vcpu(test_guest as usize).unwrap();
74+
let gpm = setup_gpm().unwrap();
75+
info!("{:#x?}", gpm);
76+
77+
let mut vcpu = percpu
78+
.create_vcpu(GUEST_ENTRY, gpm.nest_page_table_root())
79+
.unwrap();
80+
vcpu.set_page_table_root(GUEST_PT1);
81+
vcpu.set_stack_pointer(GUEST_STACK_TOP);
1682
info!("{:#x?}", vcpu);
83+
1784
println!("Running guest...");
1885
vcpu.run();
1986
}
2087

21-
#[naked]
2288
unsafe extern "C" fn test_guest() -> ! {
23-
core::arch::asm!(
24-
"
25-
mov rax, 0
26-
mov rdi, 2
27-
mov rsi, 3
28-
mov rdx, 3
29-
mov rcx, 3
30-
2:
31-
vmcall
32-
add rax, 1
33-
jmp 2b",
34-
options(noreturn),
35-
);
89+
for i in 0..100 {
90+
core::arch::asm!(
91+
"vmcall",
92+
inout("rax") i => _,
93+
in("rdi") 2,
94+
in("rsi") 3,
95+
in("rdx") 3,
96+
in("rcx") 3,
97+
);
98+
}
99+
core::arch::asm!("mov qword ptr [$0xffff233], $2333"); // panic
100+
loop {}
36101
}

hypervisor/src/hv/vmexit.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ fn handle_hypercall(vcpu: &mut Vcpu) -> RvmResult {
1717
Ok(())
1818
}
1919

20+
fn handle_ept_violation(vcpu: &Vcpu, guest_rip: usize) -> RvmResult {
21+
let fault_info = vcpu.nested_page_fault_info()?;
22+
panic!(
23+
"VM exit: EPT violation @ {:#x}, fault_paddr={:#x}, access_flags=({:?})",
24+
guest_rip, fault_info.fault_guest_paddr, fault_info.access_flags
25+
);
26+
}
27+
2028
pub fn vmexit_handler(vcpu: &mut Vcpu) -> RvmResult {
2129
let exit_info = vcpu.exit_info()?;
2230
debug!("VM exit: {:#x?}", exit_info);
@@ -27,6 +35,7 @@ pub fn vmexit_handler(vcpu: &mut Vcpu) -> RvmResult {
2735

2836
let res = match exit_info.exit_reason {
2937
VmxExitReason::VMCALL => handle_hypercall(vcpu),
38+
VmxExitReason::EPT_VIOLATION => handle_ept_violation(vcpu, exit_info.guest_rip),
3039
_ => panic!(
3140
"Unhandled VM-Exit reason {:?}:\n{:#x?}",
3241
exit_info.exit_reason, vcpu

hypervisor/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
#![no_std]
22
#![no_main]
33
#![feature(asm_const)]
4-
#![feature(naked_functions)]
54
#![feature(panic_info_message, alloc_error_handler)]
65

76
#[macro_use]
87
extern crate log;
98

9+
extern crate alloc;
10+
1011
#[macro_use]
1112
mod logging;
1213

hypervisor/src/mm/address.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ pub const fn align_down(addr: usize) -> usize {
1919
pub const fn align_up(addr: usize) -> usize {
2020
(addr + PAGE_SIZE - 1) & !(PAGE_SIZE - 1)
2121
}
22+
23+
pub const fn is_aligned(addr: usize) -> bool {
24+
(addr & (PAGE_SIZE - 1)) == 0
25+
}

rvm/src/arch/x86_64/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ cfg_if::cfg_if! {
1414
pub(crate) use vender::{has_hardware_support, ArchPerCpuState};
1515

1616
pub use regs::GeneralRegisters;
17-
pub use vender::RvmVcpu;
17+
pub use vender::{NestedPageTable, RvmVcpu};

0 commit comments

Comments
 (0)