hypervisor kernel integrity enforcement via EPT/NPT write interception for Xen/HVM guests
14

Configure Feed

Select the types of activity you want to include in your feed.

turnstile / turnstile-ctl / src / guest.rs
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}