This commit is contained in:
nattthebear 2020-06-20 10:30:41 -04:00
parent 4f09ffcda9
commit be81bc12c2
8 changed files with 325 additions and 13 deletions

View File

@ -1,16 +1,34 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "libc"
version = "0.2.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
[[package]]
name = "page_size"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "waterboxhost"
version = "0.1.0"
dependencies = [
"bitflags",
"libc",
"page_size",
"winapi",
]

View File

@ -3,13 +3,16 @@ name = "waterboxhost"
version = "0.1.0"
authors = ["nattthebear <goyuken@gmail.com>"]
edition = "2018"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bitflags = "1.2.1"
page_size = "0.4.2"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.8", features = ["memoryapi"] }
winapi = { version = "0.3.8", features = ["memoryapi", "handleapi", "errhandlingapi"] }
[target.'cfg(unix)'.dependencies]
libc = "0.2.71"

View File

@ -0,0 +1 @@
// TODO https://sourceware.org/gdb/current/onlinedocs/gdb/Declarations.html#Declarations

View File

@ -9,7 +9,7 @@ mod memory_block;
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
fn test_pagesize() {
assert_eq!(crate::PAGESIZE, page_size::get());
}
}

View File

@ -1 +1,40 @@
mod snapshot;
mod pageblock;
mod pal;
use pageblock::PageBlock;
use bitflags::bitflags;
bitflags! {
struct PageFlags: u32 {
const R = 1;
const W = 2;
const X = 4;
/// This page is mapped in the waterbox right now
const ALLOCATED = 8;
/// The contents of this page have changed since the dirty flag was set
const DIRTY = 16;
/// rsp might point here. On some OSes, use an alternate method of dirt detection
const STACK = 32;
}
}
enum Snapshot {
None,
ZeroFilled,
Data(PageBlock),
}
/// Information about a single page of memory
struct Page {
pub flags: PageFlags,
pub snapshot: Snapshot,
}
struct MemoryBlock {
pub pages: Vec<Page>,
pub start: usize,
pub length: usize,
pub end: usize,
pub sealed: bool,
}

View File

@ -3,18 +3,18 @@ use core::ffi::c_void;
use crate::*;
/// wraps the allocation of a single PAGESIZE bytes of ram, and is safe-ish to call within a signal handler
pub struct Snapshot {
pub struct PageBlock {
ptr: NonNull<u8>,
}
impl Snapshot {
pub fn new() -> Snapshot {
impl PageBlock {
pub fn new() -> PageBlock {
unsafe {
let ptr = alloc();
if ptr == null_mut() {
panic!("Snapshot could not allocate memory!");
panic!("PageBlock could not allocate memory!");
} else {
Snapshot {
PageBlock {
ptr: NonNull::new_unchecked(ptr as *mut u8)
}
}
@ -33,12 +33,12 @@ impl Snapshot {
}
}
impl Drop for Snapshot {
impl Drop for PageBlock {
fn drop(&mut self) {
unsafe {
let res = free(self.ptr.as_ptr() as *mut c_void);
if !res {
eprintln!("Snapshot could not free memory!");
panic!("PageBlock could not free memory!");
}
}
}
@ -54,7 +54,7 @@ unsafe fn alloc() -> *mut c_void {
}
#[cfg(windows)]
unsafe fn free(ptr: *mut c_void) -> bool {
match VirtualFree(ptr as *mut winapi::ctypes::c_void, PAGESIZE, MEM_RELEASE) {
match VirtualFree(ptr as *mut winapi::ctypes::c_void, 0, MEM_RELEASE) {
0 => false,
_ => true
}
@ -82,7 +82,7 @@ unsafe fn free(ptr: *mut c_void) -> bool {
#[cfg(test)]
#[test]
fn basic_test() {
let mut s = Snapshot::new();
let mut s = PageBlock::new();
for x in s.slice().iter() {

View File

@ -0,0 +1,203 @@
// Platform abstraction layer over mmap/etc. Doesn't do much checking, not meant for general consumption
pub struct Handle(usize);
pub enum Protection {
None,
R,
RW,
RX,
RWX,
RWStack
}
#[cfg(windows)]
pub use win::*;
#[cfg(windows)]
mod win {
use winapi::um::memoryapi::*;
use winapi::um::winnt::*;
use winapi::um::handleapi::*;
use super::*;
use std::ptr::{null, null_mut};
use winapi::ctypes::c_void;
fn error() {
unsafe {
let err = winapi::um::errhandlingapi::GetLastError();
eprintln!("WinApi failure code: {}", err);
}
}
pub fn open(size: usize) -> Option<Handle> {
unsafe {
let res = CreateFileMappingW(
INVALID_HANDLE_VALUE,
null_mut(),
PAGE_EXECUTE_READWRITE,
(size >> 32) as u32,
size as u32,
null()
);
if res == null_mut() {
error();
None
} else {
Some(Handle(res as usize))
}
}
}
pub unsafe fn close(handle: Handle) -> bool {
CloseHandle(handle.0 as *mut c_void) != 0
}
pub fn map(handle: &Handle, start: usize, size: usize) -> bool {
unsafe {
let res = MapViewOfFileEx(
handle.0 as *mut c_void,
FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE,
0,
0,
size,
start as *mut c_void
);
if res == start as *mut c_void {
true
} else {
error();
false
}
}
}
pub unsafe fn unmap(start: usize, _size: usize) -> bool {
UnmapViewOfFile(start as *mut c_void) != 0
}
pub unsafe fn protect(start: usize, size: usize, prot: Protection) -> bool {
let p = match prot {
Protection::None => PAGE_NOACCESS,
Protection::R => PAGE_READONLY,
Protection::RW => PAGE_READWRITE,
Protection::RX => PAGE_EXECUTE_READ,
Protection::RWX => PAGE_EXECUTE_READWRITE,
Protection::RWStack => PAGE_READWRITE | PAGE_GUARD,
};
let mut old_protect: u32 = 0;
VirtualProtect(start as *mut c_void, size, p, &mut old_protect) != 0
}
}
#[cfg(unix)]
pub use nix::*;
#[cfg(unix)]
mod nix {
use super::*;
use libc::*;
fn error() {
unsafe {
let err = *__errno_location();
eprintln!("Libc failure code: {}", err);
}
}
pub fn open(size: usize) -> Option<Handle> {
unsafe {
let s = std::ffi::CString::new("MemoryBlockUnix").unwrap();
let fd = syscall(SYS_memfd_create, s.as_ptr(), MFD_CLOEXEC) as i32;
if fd == -1 {
error();
return None
}
if ftruncate(fd, size as i64) != 0 {
error();
None
} else {
Some(Handle(fd as usize))
}
}
}
pub unsafe fn close(handle: Handle) -> bool {
libc::close(handle.0 as i32) == 0
}
pub fn map(handle: &Handle, start: usize, size: usize) -> bool {
unsafe {
let res = mmap(start as *mut c_void,
size,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_SHARED | MAP_FIXED,
handle.0 as i32,
0
);
if res == start as *mut c_void {
true
} else {
error();
false
}
}
}
pub unsafe fn unmap(start: usize, size: usize) -> bool {
munmap(start as *mut c_void, size) == 0
}
pub unsafe fn protect(start: usize, size: usize, prot: Protection) -> bool {
let p = match prot {
Protection::None => PROT_NONE,
Protection::R => PROT_READ,
Protection::RW => PROT_READ | PROT_WRITE,
Protection::RX => PROT_READ | PROT_EXEC,
Protection::RWX => PROT_READ | PROT_WRITE | PROT_EXEC,
Protection::RWStack => panic!("RWStack should not be passed to pal layer"),
};
mprotect(start as *mut c_void, size, p) == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::transmute;
#[test]
fn basic_test() {
assert!(crate::PAGESIZE == 0x1000);
// can't test the fault states (RWStack, R prohibits write, etc.) without cooperation of tripguard, so do that elsewhere
unsafe {
let size = 0x20000usize;
let start = 0x36a00000000usize;
let handle = open(size).unwrap();
assert!(map(&handle, start, size));
assert!(protect(start, size, Protection::RW));
*((start + 0x14795) as *mut u8) = 42;
assert!(unmap(start, size));
assert!(map(&handle, start, size));
assert!(protect(start, size, Protection::R));
assert_eq!(*((start + 0x14795) as *const u8), 42);
assert!(unmap(start + 0x14000, 0x2000));
assert!(map(&handle, start, size));
assert!(protect(start, size, Protection::RW));
*(start as *mut u8) = 0xc3; // RET
assert!(protect(start, size, Protection::RX));
transmute::<usize, extern fn() -> ()>(start)();
assert!(protect(start, size, Protection::RWX));
*(start as *mut u8) = 0x90; // NOP
*((start + 1) as *mut u8) = 0xb0; // MOV AL
*((start + 2) as *mut u8) = 0x7b; // 123
*((start + 3) as *mut u8) = 0xc3; // RET
let i = transmute::<usize, extern fn() -> u8>(start)();
assert_eq!(i, 123);
assert!(unmap(start, size));
assert!(close(handle));
}
}
}

View File

@ -0,0 +1,48 @@
static global_data = Mutex::new(GlobalData {
initialized: false,
activeBlocks: Vec::new(),
});
struct GlobalData {
initialized: bool,
activeBlocks: Vec<*mut MemoryBlock>,
}
pub unsafe fn register(block: *mut MemoryBlock) {
let mut data = global_data.lock().unwrap();
if !data.initialized {
initialize();
data.initialized = true;
}
data.activeBlocks.push(block);
}
pub unsafe fn unregister(block: *mut MemoryBlock) {
let mut data = global_data.lock().unwrap();
let pos = data.activeBlocks.into_iter().position(|x| x == block).unwrap();
data.activeBlocks.remove(pos);
}
enum TripResult {
Handled,
NotHandled,
}
unsafe fn trip(addr: usize) -> TripResult {
let mut data = global_data.lock().unwrap();
let mut memoryBlock = match data.activeBlocks
.into_iter()
.find(|x| addr >= x.start && addr < x.end) {
Some(x) => x,
None => return NotHandled,
}
let pageStartAddr = addr & ~PAGEMASK;
let mut page = &mut memoryBlock.pages[(addr - memoryBlock.start) >> PAGESHIFT];
if !page.flags.contains(PageFlags::W) {
NotHandled
}
if memoryBlock.sealed && page.snapshot == Snapshot::None {
// take snapshot now
}
}