1 // Copyright (C) 2019 CrowdStrike, Inc. All rights reserved. 2 // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 4 //! Helper structure for working with mmaped memory regions in Windows. 5 6 use std; 7 use std::io; 8 use std::os::windows::io::{AsRawHandle, RawHandle}; 9 use std::ptr::{null, null_mut}; 10 11 use libc::{c_void, size_t}; 12 13 use winapi::um::errhandlingapi::GetLastError; 14 15 use crate::bitmap::{Bitmap, BS}; 16 use crate::guest_memory::FileOffset; 17 use crate::mmap::NewBitmap; 18 use crate::volatile_memory::{self, compute_offset, VolatileMemory, VolatileSlice}; 19 20 #[allow(non_snake_case)] 21 #[link(name = "kernel32")] 22 extern "stdcall" { VirtualAlloc( lpAddress: *mut c_void, dwSize: size_t, flAllocationType: u32, flProtect: u32, ) -> *mut c_void23 pub fn VirtualAlloc( 24 lpAddress: *mut c_void, 25 dwSize: size_t, 26 flAllocationType: u32, 27 flProtect: u32, 28 ) -> *mut c_void; 29 VirtualFree(lpAddress: *mut c_void, dwSize: size_t, dwFreeType: u32) -> u3230 pub fn VirtualFree(lpAddress: *mut c_void, dwSize: size_t, dwFreeType: u32) -> u32; 31 CreateFileMappingA( hFile: RawHandle, lpFileMappingAttributes: *const c_void, flProtect: u32, dwMaximumSizeHigh: u32, dwMaximumSizeLow: u32, lpName: *const u8, ) -> RawHandle32 pub fn CreateFileMappingA( 33 hFile: RawHandle, // HANDLE 34 lpFileMappingAttributes: *const c_void, // LPSECURITY_ATTRIBUTES 35 flProtect: u32, // DWORD 36 dwMaximumSizeHigh: u32, // DWORD 37 dwMaximumSizeLow: u32, // DWORD 38 lpName: *const u8, // LPCSTR 39 ) -> RawHandle; // HANDLE 40 MapViewOfFile( hFileMappingObject: RawHandle, dwDesiredAccess: u32, dwFileOffsetHigh: u32, dwFileOffsetLow: u32, dwNumberOfBytesToMap: size_t, ) -> *mut c_void41 pub fn MapViewOfFile( 42 hFileMappingObject: RawHandle, 43 dwDesiredAccess: u32, 44 dwFileOffsetHigh: u32, 45 dwFileOffsetLow: u32, 46 dwNumberOfBytesToMap: size_t, 47 ) -> *mut c_void; 48 CloseHandle(hObject: RawHandle) -> u3249 pub fn CloseHandle(hObject: RawHandle) -> u32; // BOOL 50 } 51 52 const MM_HIGHEST_VAD_ADDRESS: u64 = 0x000007FFFFFDFFFF; 53 54 const MEM_COMMIT: u32 = 0x00001000; 55 const MEM_RELEASE: u32 = 0x00008000; 56 const FILE_MAP_ALL_ACCESS: u32 = 0xf001f; 57 const PAGE_READWRITE: u32 = 0x04; 58 59 pub const MAP_FAILED: *mut c_void = 0 as *mut c_void; 60 pub const INVALID_HANDLE_VALUE: RawHandle = (-1isize) as RawHandle; 61 #[allow(dead_code)] 62 pub const ERROR_INVALID_PARAMETER: i32 = 87; 63 64 /// Helper structure for working with mmaped memory regions in Unix. 65 /// 66 /// The structure is used for accessing the guest's physical memory by mmapping it into 67 /// the current process. 68 /// 69 /// # Limitations 70 /// When running a 64-bit virtual machine on a 32-bit hypervisor, only part of the guest's 71 /// physical memory may be mapped into the current process due to the limited virtual address 72 /// space size of the process. 73 #[derive(Debug)] 74 pub struct MmapRegion<B> { 75 addr: *mut u8, 76 size: usize, 77 bitmap: B, 78 file_offset: Option<FileOffset>, 79 } 80 81 // Send and Sync aren't automatically inherited for the raw address pointer. 82 // Accessing that pointer is only done through the stateless interface which 83 // allows the object to be shared by multiple threads without a decrease in 84 // safety. 85 unsafe impl<B: Send> Send for MmapRegion<B> {} 86 unsafe impl<B: Sync> Sync for MmapRegion<B> {} 87 88 impl<B: NewBitmap> MmapRegion<B> { 89 /// Creates a shared anonymous mapping of `size` bytes. 90 /// 91 /// # Arguments 92 /// * `size` - The size of the memory region in bytes. new(size: usize) -> io::Result<Self>93 pub fn new(size: usize) -> io::Result<Self> { 94 if (size == 0) || (size > MM_HIGHEST_VAD_ADDRESS as usize) { 95 return Err(io::Error::from_raw_os_error(libc::EINVAL)); 96 } 97 // This is safe because we are creating an anonymous mapping in a place not already used by 98 // any other area in this process. 99 let addr = unsafe { VirtualAlloc(0 as *mut c_void, size, MEM_COMMIT, PAGE_READWRITE) }; 100 if addr == MAP_FAILED { 101 return Err(io::Error::last_os_error()); 102 } 103 Ok(Self { 104 addr: addr as *mut u8, 105 size, 106 bitmap: B::with_len(size), 107 file_offset: None, 108 }) 109 } 110 111 /// Creates a shared file mapping of `size` bytes. 112 /// 113 /// # Arguments 114 /// * `file_offset` - The mapping will be created at offset `file_offset.start` in the file 115 /// referred to by `file_offset.file`. 116 /// * `size` - The size of the memory region in bytes. from_file(file_offset: FileOffset, size: usize) -> io::Result<Self>117 pub fn from_file(file_offset: FileOffset, size: usize) -> io::Result<Self> { 118 let handle = file_offset.file().as_raw_handle(); 119 if handle == INVALID_HANDLE_VALUE { 120 return Err(io::Error::from_raw_os_error(libc::EBADF)); 121 } 122 123 let mapping = unsafe { 124 CreateFileMappingA( 125 handle, 126 null(), 127 PAGE_READWRITE, 128 (size >> 32) as u32, 129 size as u32, 130 null(), 131 ) 132 }; 133 if mapping == 0 as RawHandle { 134 return Err(io::Error::last_os_error()); 135 } 136 137 let offset = file_offset.start(); 138 139 // This is safe because we are creating a mapping in a place not already used by any other 140 // area in this process. 141 let addr = unsafe { 142 MapViewOfFile( 143 mapping, 144 FILE_MAP_ALL_ACCESS, 145 (offset >> 32) as u32, 146 offset as u32, 147 size, 148 ) 149 }; 150 151 unsafe { 152 CloseHandle(mapping); 153 } 154 155 if addr == null_mut() { 156 return Err(io::Error::last_os_error()); 157 } 158 Ok(Self { 159 addr: addr as *mut u8, 160 size, 161 bitmap: B::with_len(size), 162 file_offset: Some(file_offset), 163 }) 164 } 165 } 166 167 impl<B: Bitmap> MmapRegion<B> { 168 /// Returns a pointer to the beginning of the memory region. Mutable accesses performed 169 /// using the resulting pointer are not automatically accounted for by the dirty bitmap 170 /// tracking functionality. 171 /// 172 /// Should only be used for passing this region to ioctls for setting guest memory. as_ptr(&self) -> *mut u8173 pub fn as_ptr(&self) -> *mut u8 { 174 self.addr 175 } 176 177 /// Returns the size of this region. size(&self) -> usize178 pub fn size(&self) -> usize { 179 self.size 180 } 181 182 /// Returns information regarding the offset into the file backing this region (if any). file_offset(&self) -> Option<&FileOffset>183 pub fn file_offset(&self) -> Option<&FileOffset> { 184 self.file_offset.as_ref() 185 } 186 187 /// Returns a reference to the inner bitmap object. bitmap(&self) -> &B188 pub fn bitmap(&self) -> &B { 189 &self.bitmap 190 } 191 } 192 193 impl<B: Bitmap> VolatileMemory for MmapRegion<B> { 194 type B = B; 195 len(&self) -> usize196 fn len(&self) -> usize { 197 self.size 198 } 199 get_slice( &self, offset: usize, count: usize, ) -> volatile_memory::Result<VolatileSlice<BS<Self::B>>>200 fn get_slice( 201 &self, 202 offset: usize, 203 count: usize, 204 ) -> volatile_memory::Result<VolatileSlice<BS<Self::B>>> { 205 let end = compute_offset(offset, count)?; 206 if end > self.size { 207 return Err(volatile_memory::Error::OutOfBounds { addr: end }); 208 } 209 210 // Safe because we checked that offset + count was within our range and we only ever hand 211 // out volatile accessors. 212 Ok(unsafe { 213 VolatileSlice::with_bitmap( 214 self.addr.add(offset), 215 count, 216 self.bitmap.slice_at(offset), 217 None, 218 ) 219 }) 220 } 221 } 222 223 impl<B> Drop for MmapRegion<B> { drop(&mut self)224 fn drop(&mut self) { 225 // This is safe because we mmap the area at addr ourselves, and nobody 226 // else is holding a reference to it. 227 // Note that the size must be set to 0 when using MEM_RELEASE, 228 // otherwise the function fails. 229 unsafe { 230 let ret_val = VirtualFree(self.addr as *mut libc::c_void, 0, MEM_RELEASE); 231 if ret_val == 0 { 232 let err = GetLastError(); 233 // We can't use any fancy logger here, yet we want to 234 // pin point memory leaks. 235 println!( 236 "WARNING: Could not deallocate mmap region. \ 237 Address: {:?}. Size: {}. Error: {}", 238 self.addr, self.size, err 239 ) 240 } 241 } 242 } 243 } 244 245 #[cfg(test)] 246 mod tests { 247 use std::os::windows::io::FromRawHandle; 248 249 use crate::bitmap::AtomicBitmap; 250 use crate::guest_memory::FileOffset; 251 use crate::mmap_windows::INVALID_HANDLE_VALUE; 252 253 type MmapRegion = super::MmapRegion<()>; 254 255 #[test] map_invalid_handle()256 fn map_invalid_handle() { 257 let file = unsafe { std::fs::File::from_raw_handle(INVALID_HANDLE_VALUE) }; 258 let file_offset = FileOffset::new(file, 0); 259 let e = MmapRegion::from_file(file_offset, 1024).unwrap_err(); 260 assert_eq!(e.raw_os_error(), Some(libc::EBADF)); 261 } 262 263 #[test] test_dirty_tracking()264 fn test_dirty_tracking() { 265 // Using the `crate` prefix because we aliased `MmapRegion` to `MmapRegion<()>` for 266 // the rest of the unit tests above. 267 let m = crate::MmapRegion::<AtomicBitmap>::new(0x1_0000).unwrap(); 268 crate::bitmap::tests::test_volatile_memory(&m); 269 } 270 } 271