Skip to content

Commit

Permalink
Implement ASLR for uhyve.
Browse files Browse the repository at this point in the history
- Kernel is loaded to a random physical address
- Pagetables are created for the kernel region instead of just the first
  gigabyte

Fixes #719.

Co-authored-by: Jonathan <github@jonathanklimt.de>
  • Loading branch information
n0toose and jounathaen committed Jan 29, 2025
1 parent 6a583ae commit 13a1799
Show file tree
Hide file tree
Showing 14 changed files with 396 additions and 202 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ path = "benches/benchmarks.rs"
harness = false

[features]
default = []
default = ["aslr"]
aslr = ["dep:rand"]
instrument = ["rftrace", "rftrace-frontend"]

[dependencies]
Expand All @@ -59,12 +60,14 @@ uhyve-interface = { version = "0.1.1", path = "uhyve-interface", features = ["st
virtio-bindings = { version = "0.2", features = ["virtio-v4_14_0"] }
rftrace = { version = "0.1", optional = true }
rftrace-frontend = { version = "0.1", optional = true }
rand = { version = "0.8.5", optional = true }
shell-words = "1"
sysinfo = { version = "0.33.1", default-features = false, features = ["system"] }
vm-fdt = "0.3"
tempfile = "3.15.0"
uuid = { version = "1.12.1", features = ["fast-rng", "v4"]}
clean-path = "0.2.1"
align-address = "0.3.0"

[target.'cfg(target_os = "linux")'.dependencies]
kvm-bindings = "0.11"
Expand Down
47 changes: 24 additions & 23 deletions src/arch/aarch64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use bitflags::bitflags;
use uhyve_interface::{GuestPhysAddr, GuestVirtAddr};

use crate::{
consts::{BOOT_INFO_ADDR, BOOT_PGT},
consts::{BOOT_INFO_OFFSET, PGT_OFFSET},
mem::MmapMemory,
paging::PagetableError,
};
Expand Down Expand Up @@ -115,12 +115,13 @@ fn is_valid_address(virtual_address: GuestVirtAddr) -> bool {
pub fn virt_to_phys(
addr: GuestVirtAddr,
mem: &MmapMemory,
pagetable_l0: GuestPhysAddr,
) -> Result<GuestPhysAddr, PagetableError> {
if !is_valid_address(addr) {
return Err(PagetableError::InvalidAddress);
}

let guest_address = *crate::vm::GUEST_ADDRESS.get().unwrap();

// Assumptions:
// - We use 4KiB granule
// - We use maximum VA length
Expand All @@ -133,7 +134,7 @@ pub fn virt_to_phys(
// - We are page_aligned, and thus also PageTableEntry aligned.
let mut pagetable: &[PageTableEntry] = unsafe {
std::mem::transmute::<&[u8], &[PageTableEntry]>(
mem.slice_at(pagetable_l0, PAGE_SIZE).unwrap(),
mem.slice_at(guest_address, PAGE_SIZE).unwrap(),
)
};
// TODO: Depending on the virtual address length and granule (defined in TCR register by TG and TxSZ), we could reduce the number of pagetable walks. Hermit doesn't do this at the moment.
Expand All @@ -155,67 +156,67 @@ pub fn virt_to_phys(
Ok(pte.address())
}

pub fn init_guest_mem(mem: &mut [u8]) {
pub fn init_guest_mem(mem: &mut [u8], _guest_address: GuestPhysAddr) {
let mem_addr = std::ptr::addr_of_mut!(mem[0]);

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 512 * size_of::<u64>());
assert!(mem.len() >= PGT_OFFSET as usize + 512 * size_of::<u64>());
let pgt_slice = unsafe {
std::slice::from_raw_parts_mut(mem_addr.offset(BOOT_PGT.as_u64() as isize) as *mut u64, 512)
std::slice::from_raw_parts_mut(mem_addr.offset(PGT_OFFSET as isize) as *mut u64, 512)
};
pgt_slice.fill(0);
pgt_slice[0] = BOOT_PGT.as_u64() + 0x1000 + PT_PT;
pgt_slice[511] = BOOT_PGT.as_u64() + PT_PT + PT_SELF;
pgt_slice[0] = PGT_OFFSET + 0x1000 + PT_PT;
pgt_slice[511] = PGT_OFFSET + PT_PT + PT_SELF;

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 0x1000 + 512 * size_of::<u64>());
assert!(mem.len() >= PGT_OFFSET as usize + 0x1000 + 512 * size_of::<u64>());
let pgt_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset(BOOT_PGT.as_u64() as isize + 0x1000) as *mut u64,
mem_addr.offset(PGT_OFFSET as isize + 0x1000) as *mut u64,
512,
)
};
pgt_slice.fill(0);
pgt_slice[0] = BOOT_PGT.as_u64() + 0x2000 + PT_PT;
pgt_slice[0] = PGT_OFFSET + 0x2000 + PT_PT;

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 0x2000 + 512 * size_of::<u64>());
assert!(mem.len() >= PGT_OFFSET as usize + 0x2000 + 512 * size_of::<u64>());
let pgt_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset(BOOT_PGT.as_u64() as isize + 0x2000) as *mut u64,
mem_addr.offset(PGT_OFFSET as isize + 0x2000) as *mut u64,
512,
)
};
pgt_slice.fill(0);
pgt_slice[0] = BOOT_PGT.as_u64() + 0x3000 + PT_PT;
pgt_slice[1] = BOOT_PGT.as_u64() + 0x4000 + PT_PT;
pgt_slice[2] = BOOT_PGT.as_u64() + 0x5000 + PT_PT;
pgt_slice[0] = PGT_OFFSET + 0x3000 + PT_PT;
pgt_slice[1] = PGT_OFFSET + 0x4000 + PT_PT;
pgt_slice[2] = PGT_OFFSET + 0x5000 + PT_PT;

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 0x3000 + 512 * size_of::<u64>());
assert!(mem.len() >= PGT_OFFSET as usize + 0x3000 + 512 * size_of::<u64>());
let pgt_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset(BOOT_PGT.as_u64() as isize + 0x3000) as *mut u64,
mem_addr.offset(PGT_OFFSET as isize + 0x3000) as *mut u64,
512,
)
};
pgt_slice.fill(0);
// map Uhyve ports into the virtual address space
pgt_slice[0] = PT_MEM_CD;
// map BootInfo into the virtual address space
pgt_slice[BOOT_INFO_ADDR.as_u64() as usize / PAGE_SIZE] = BOOT_INFO_ADDR.as_u64() + PT_MEM;
pgt_slice[BOOT_INFO_OFFSET as usize / PAGE_SIZE] = BOOT_INFO_OFFSET + PT_MEM;

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 0x4000 + 512 * size_of::<u64>());
assert!(mem.len() >= PGT_OFFSET as usize + 0x4000 + 512 * size_of::<u64>());
let pgt_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset(BOOT_PGT.as_u64() as isize + 0x4000) as *mut u64,
mem_addr.offset(PGT_OFFSET as isize + 0x4000) as *mut u64,
512,
)
};
for (idx, i) in pgt_slice.iter_mut().enumerate() {
*i = 0x200000u64 + (idx * PAGE_SIZE) as u64 + PT_MEM;
}

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 0x5000 + 512 * size_of::<u64>());
assert!(mem.len() >= PGT_OFFSET as usize + 0x5000 + 512 * size_of::<u64>());
let pgt_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset(BOOT_PGT.as_u64() as isize + 0x5000) as *mut u64,
mem_addr.offset(PGT_OFFSET as isize + 0x5000) as *mut u64,
512,
)
};
Expand Down
53 changes: 29 additions & 24 deletions src/arch/x86_64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub const RAM_START: GuestPhysAddr = GuestPhysAddr::new(0x00);
pub fn virt_to_phys(
addr: GuestVirtAddr,
mem: &MmapMemory,
pagetable_l0: GuestPhysAddr,
pml4: GuestPhysAddr,
) -> Result<GuestPhysAddr, PagetableError> {
/// Number of Offset bits of a virtual address for a 4 KiB page, which are shifted away to get its Page Frame Number (PFN).
pub const PAGE_BITS: u64 = 12;
Expand All @@ -25,7 +25,7 @@ pub fn virt_to_phys(
pub const PAGE_MAP_BITS: usize = 9;

let mut page_table =
unsafe { (mem.host_address(pagetable_l0).unwrap() as *mut PageTable).as_mut() }.unwrap();
unsafe { (mem.host_address(pml4).unwrap() as *mut PageTable).as_mut() }.unwrap();
let mut page_bits = 39;
let mut entry = PageTableEntry::new();

Expand Down Expand Up @@ -53,17 +53,16 @@ pub fn virt_to_phys(
Ok((entry.addr() + (addr.as_u64() & !((!0u64) << PAGE_BITS))).into())
}

pub fn init_guest_mem(mem: &mut [u8]) {
pub fn init_guest_mem(mem: &mut [u8], guest_address: GuestPhysAddr, length: u64) {
// TODO: we should maybe return an error on failure (e.g., the memory is too small)
initialize_pagetables(mem);
initialize_pagetables(mem, guest_address, length);
}

#[cfg(test)]
mod tests {
use x86_64::structures::paging::PageTableFlags;

use super::*;
use crate::consts::{BOOT_PDE, BOOT_PDPTE, BOOT_PML4};

#[test]
fn test_virt_to_phys() {
Expand All @@ -72,38 +71,44 @@ mod tests {
.is_test(true)
.try_init();

let mem = MmapMemory::new(
0,
align_up!(paging::MIN_PHYSMEM_SIZE * 2, 0x20_0000),
GuestPhysAddr::zero(),
true,
true,
let guest_address = GuestPhysAddr::new(0x11111000);

let mem = MmapMemory::new(0, MIN_PHYSMEM_SIZE * 2, guest_address, true, true);
println!("mmap memory created {mem:x?}");

init_guest_mem(
unsafe { mem.as_slice_mut() }.try_into().unwrap(),
guest_address,
MIN_PHYSMEM_SIZE as u64 * 2,
);
println!("mmap memory created {mem:?}");
initialize_pagetables(unsafe { mem.as_slice_mut() }.try_into().unwrap());

// Get the address of the first entry in PML4 (the address of the PML4 itself)
let virt_addr = GuestVirtAddr::new(0xFFFFFFFFFFFFF000);
let p_addr = virt_to_phys(virt_addr, &mem, BOOT_PML4).unwrap();
assert_eq!(p_addr, BOOT_PML4);
let p_addr = virt_to_phys(virt_addr, &mem, guest_address + PML4_OFFSET).unwrap();
assert_eq!(p_addr, guest_address + PML4_OFFSET);

// The last entry on the PML4 is the address of the PML4 with flags
let virt_addr = GuestVirtAddr::new(0xFFFFFFFFFFFFF000 | (4096 - 8));
let p_addr = virt_to_phys(virt_addr, &mem, BOOT_PML4).unwrap();
let p_addr = virt_to_phys(virt_addr, &mem, guest_address + PML4_OFFSET).unwrap();
assert_eq!(
mem.read::<u64>(p_addr).unwrap(),
BOOT_PML4.as_u64() | (PageTableFlags::PRESENT | PageTableFlags::WRITABLE).bits()
(guest_address + PML4_OFFSET).as_u64()
| (PageTableFlags::PRESENT | PageTableFlags::WRITABLE).bits()
);

// the first entry on the 3rd level entry in the pagetables is the address of the boot pdpte
let virt_addr = GuestVirtAddr::new(0xFFFFFFFFFFE00000);
let p_addr = virt_to_phys(virt_addr, &mem, BOOT_PML4).unwrap();
assert_eq!(p_addr, BOOT_PDPTE);

// the first entry on the 2rd level entry in the pagetables is the address of the boot pde
let virt_addr = GuestVirtAddr::new(0xFFFFFFFFC0000000);
let p_addr = virt_to_phys(virt_addr, &mem, BOOT_PML4).unwrap();
assert_eq!(p_addr, BOOT_PDE);
let p_addr = virt_to_phys(virt_addr, &mem, guest_address + PML4_OFFSET).unwrap();
assert!(p_addr.as_u64() - guest_address.as_u64() >= PAGETABLES_OFFSET);
assert!(p_addr.as_u64() - guest_address.as_u64() <= PAGETABLES_END);

// the idx2 entry on the 2rd level entry in the pagetables is the address of the boot pde
let idx2 = GuestVirtAddr::new(guest_address.as_u64()).p2_index();
let virt_addr = GuestVirtAddr::new(0xFFFFFFFFC0000000)
+ u64::from(idx2) * size_of::<PageTableEntry>() as u64;
let p_addr = virt_to_phys(virt_addr, &mem, guest_address + PML4_OFFSET).unwrap();
assert!(p_addr.as_u64() - guest_address.as_u64() >= PAGETABLES_OFFSET);
assert!(p_addr.as_u64() - guest_address.as_u64() <= PAGETABLES_END);
// That address points to a huge page
assert!(
PageTableFlags::from_bits_truncate(mem.read::<u64>(p_addr).unwrap()).contains(
Expand Down
Loading

0 comments on commit 13a1799

Please sign in to comment.