Reputation: 638
I'm trying to do some tests on page fault exception on Windows. The requirement is to put some data into the page boundary so that reading the data will trigger a page fault exception.
Concretely, the test consists of 7 bytes (for example) which are logically consecutive and allocated. I need the first 5 bytes to be located on a physically allocated page, but the next 2 bytes are located on a page which is not yet physically allocated.
So that reading this 7 bytes will trigger a page fault exception, while reading only 4 bytes will not.
I intially think that I should allocate two pages, write 7 byte data over the boundary, and page out the second page.
Can I do this using some user-mode Windows API?
Upvotes: 0
Views: 589
Reputation: 638
Basically, this answer does not add any thing more essential than one of @PeterT. It's modified a little bit to write arbitrary bytes (as we want) on the page boundary:
use std::{
mem::{transmute, MaybeUninit},
ptr,
};
use winapi::{
shared::{minwindef::TRUE, ntdef::NULL},
um::{
memoryapi::{VirtualAlloc, VirtualFree, VirtualProtect},
sysinfoapi::GetSystemInfo,
winnt::{
MEM_COMMIT, MEM_DECOMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE,
},
},
};
fn main() {
let page_size = {
let mut sys_info = MaybeUninit::uninit();
let sys_info = unsafe {
GetSystemInfo(sys_info.as_mut_ptr());
sys_info.assume_init()
};
sys_info.dwPageSize
} as usize;
let region_base = unsafe {
VirtualAlloc(
NULL,
page_size * 2,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE,
)
} as usize;
println!("Allocated region base: 0x{:x}", region_base);
let ud1_addr = region_base + page_size - 0x2;
print!("Writing 0f b9 27 (ud1 esp, [rdi]) to: 0x{:x}... ", ud1_addr);
let ud1_ptr = ud1_addr as *mut u8;
unsafe {
ptr::write(ud1_ptr, 0x0f);
ptr::write(ud1_ptr.add(1), 0xb9);
ptr::write(ud1_ptr.add(2), 0x27);
};
println!("ok");
let last_page_addr: usize = region_base + page_size;
print!("Decommitting the last page at 0x{:x}... ", last_page_addr);
let free_ok = unsafe { VirtualFree(last_page_addr as _, page_size, MEM_DECOMMIT) };
if free_ok == TRUE {
println!("ok. Executing: ud1 esp, [rdi]");
let ud1: extern "C" fn() = unsafe { transmute(ud1_ptr as *const ()) };
ud1();
} else {
println!("failed");
}
}
Upvotes: 1
Reputation: 8284
Yes, this is possible in user-land. You'd usually reserve all the pages at once with VirtualAlloc
and then commit part of it. You can change the protection back and forth with VirtualProtect
after committing.
#include <cstdint>
#include <iostream>
#include <Windows.h>
int main()
{
//Retrieve currently configured page-size
SYSTEM_INFO info;
GetSystemInfo(&info);
DWORD pageSize = info.dwPageSize;
//Reserve 2 pages
void *mem = VirtualAlloc(NULL, pageSize*2, MEM_RESERVE, PAGE_NOACCESS);
//Commit first page with read/write premissions
VirtualAlloc(mem, pageSize, MEM_COMMIT, PAGE_READWRITE);
//get pointer with 5 bytes in the first page
uint8_t* ptrAcross = (uint8_t*)mem + pageSize - 5;
//Fill first 5 bytes
FillMemory(ptrAcross, 5, 0x55);
try
{
//Try to fill 6th byte
FillMemory(ptrAcross+5, 1, 0x55);
}
catch(...) // only catches the access violation when compiled with /EHa
{
std::cout << "Access violation" << std::endl;
}
std::cout << "Program finished" <<std::endl;
VirtualFree(mem, 0, MEM_RELEASE);
return 0;
}
Upvotes: 1