hypervisor kernel integrity enforcement via EPT/NPT write interception for Xen/HVM guests
4.8 kB
181 lines
1use std::fs::{File, OpenOptions};
2use std::os::unix::io::AsRawFd;
3
4use crate::elf::KernelLayout;
5use crate::error::{Result, TurnstileError};
6
7const PAGE_SIZE: usize = 4096;
8const PAGE_SHIFT: u32 = 12;
9
10pub const KERNEL_VIRT_BASE: u64 = 0xffffffff80000000;
11const PHYSMAP_BASE: u64 = 0xffff800000000000;
12
13pub const KERNEL_PHYS_START: u64 = 0x100_0000;
14const KERNEL_PHYS_END: u64 = 0x4000_0000;
15const KERNEL_PHYS_ALIGN: u64 = 0x20_0000;
16
17#[repr(C)]
18struct PrivcmdMmapEntry {
19 va: u64,
20 mfn: u64,
21 npages: u64,
22}
23
24#[repr(C)]
25struct PrivcmdMmap {
26 num: i32,
27 dom: u16,
28 entry: *mut PrivcmdMmapEntry,
29}
30
31nix::ioctl_readwrite!(privcmd_mmap, b'P', 2, PrivcmdMmap);
32
33pub struct MappedPage {
34 ptr: *mut u8,
35 size: usize,
36}
37
38impl MappedPage {
39 pub fn as_slice(&self) -> &[u8] {
40 unsafe { std::slice::from_raw_parts(self.ptr, self.size) }
41 }
42}
43
44impl Drop for MappedPage {
45 fn drop(&mut self) {
46 if !self.ptr.is_null() {
47 unsafe {
48 libc::munmap(self.ptr as *mut libc::c_void, self.size);
49 }
50 }
51 }
52}
53
54pub struct GuestMemory {
55 privcmd: File,
56 domid: u16,
57}
58
59impl GuestMemory {
60 pub fn new(domid: u16) -> Result<Self> {
61 let privcmd = OpenOptions::new()
62 .read(true)
63 .write(true)
64 .open("/dev/xen/privcmd")?;
65 Ok(Self { privcmd, domid })
66 }
67
68 pub fn map_page(&self, gfn: u64) -> Result<MappedPage> {
69 let ptr = unsafe {
70 libc::mmap(
71 std::ptr::null_mut(),
72 PAGE_SIZE,
73 libc::PROT_READ,
74 libc::MAP_SHARED,
75 self.privcmd.as_raw_fd(),
76 0,
77 )
78 };
79
80 if ptr == libc::MAP_FAILED {
81 return Err(TurnstileError::Io(std::io::Error::last_os_error()));
82 }
83
84 let mut entry = PrivcmdMmapEntry {
85 va: ptr as u64,
86 mfn: gfn,
87 npages: 1,
88 };
89
90 let mut cmd = PrivcmdMmap {
91 num: 1,
92 dom: self.domid,
93 entry: &mut entry,
94 };
95
96 let ret = unsafe { privcmd_mmap(self.privcmd.as_raw_fd(), &mut cmd) };
97
98 if let Err(errno) = ret {
99 unsafe {
100 libc::munmap(ptr, PAGE_SIZE);
101 }
102 return Err(TurnstileError::Io(std::io::Error::from(errno)));
103 }
104
105 Ok(MappedPage {
106 ptr: ptr as *mut u8,
107 size: PAGE_SIZE,
108 })
109 }
110
111 pub fn read_gpa(&self, gpa: u64, buf: &mut [u8]) -> Result<()> {
112 let mut offset = 0usize;
113 let mut remaining = buf.len();
114 let mut current_gpa = gpa;
115
116 while remaining > 0 {
117 let gfn = current_gpa >> PAGE_SHIFT;
118 let page_offset = (current_gpa & (PAGE_SIZE as u64 - 1)) as usize;
119 let chunk = std::cmp::min(remaining, PAGE_SIZE - page_offset);
120
121 let page = self.map_page(gfn)?;
122 let src = &page.as_slice()[page_offset..page_offset + chunk];
123 buf[offset..offset + chunk].copy_from_slice(src);
124
125 offset += chunk;
126 remaining -= chunk;
127 current_gpa += chunk as u64;
128 }
129
130 Ok(())
131 }
132
133 fn check_pattern_at(&self, gpa: u64, pattern: &[u8; 32]) -> Result<bool> {
134 let mut buf = [0u8; 32];
135 self.read_gpa(gpa, &mut buf)?;
136 Ok(buf == *pattern)
137 }
138
139 pub fn detect_kaslr_slide(&self, layout: &KernelLayout) -> Result<i64> {
140 let pattern = &layout.kaslr_pattern;
141 let expected_vaddr = layout.kaslr_pattern_vaddr;
142
143 for gpa in (KERNEL_PHYS_START..KERNEL_PHYS_END).step_by(KERNEL_PHYS_ALIGN as usize) {
144 if let Ok(found) = self.check_pattern_at(gpa, pattern) {
145 if found {
146 let expected_phys = expected_vaddr - KERNEL_VIRT_BASE;
147 let slide = gpa as i64 - expected_phys as i64;
148 return Ok(slide);
149 }
150 }
151 }
152
153 Err(TurnstileError::KaslrDetectionFailed)
154 }
155}
156
157fn vaddr_to_gpa(vaddr: u64) -> Option<u64> {
158 if vaddr >= KERNEL_VIRT_BASE {
159 Some(vaddr - KERNEL_VIRT_BASE)
160 } else if vaddr >= PHYSMAP_BASE {
161 Some(vaddr - PHYSMAP_BASE)
162 } else {
163 None
164 }
165}
166
167pub fn calculate_protected_gpas(layout: &KernelLayout, slide: i64) -> Vec<(u64, u64)> {
168 let mut ranges = Vec::new();
169
170 let text_vaddr = (layout.text.vaddr as i64 + slide) as u64;
171 if let Some(text_gpa) = vaddr_to_gpa(text_vaddr) {
172 ranges.push((text_gpa, layout.text.size));
173 }
174
175 let rodata_vaddr = (layout.rodata.vaddr as i64 + slide) as u64;
176 if let Some(rodata_gpa) = vaddr_to_gpa(rodata_vaddr) {
177 ranges.push((rodata_gpa, layout.rodata.size));
178 }
179
180 ranges
181}