more wip
This commit is contained in:
parent
4f09ffcda9
commit
be81bc12c2
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
// TODO https://sourceware.org/gdb/current/onlinedocs/gdb/Declarations.html#Declarations
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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() {
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue