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 31, 2025
1 parent ce8173c commit 323b364
Show file tree
Hide file tree
Showing 16 changed files with 586 additions and 243 deletions.
6 changes: 4 additions & 2 deletions Cargo.lock

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

7 changes: 5 additions & 2 deletions 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 @@ -48,7 +49,7 @@ either = "1.13"
env_logger = "0.11"
gdbstub = "0.7"
gdbstub_arch = "0.3"
hermit-entry = { version = "0.10", features = ["loader"] }
hermit-entry = { version = "0.10.3", features = ["loader"] }
libc = "0.2"
log = "0.4"
mac_address = "1.1"
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
170 changes: 115 additions & 55 deletions src/arch/aarch64/mod.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
use std::mem::size_of;

use align_address::Align;
use bitflags::bitflags;
use uhyve_interface::{GuestPhysAddr, GuestVirtAddr};

use crate::{
consts::{BOOT_INFO_ADDR, BOOT_PGT},
consts::{PAGETABLES_END, PAGETABLES_OFFSET, PGT_OFFSET},
mem::MmapMemory,
paging::PagetableError,
paging::{BumpAllocator, PagetableError},
};

pub const RAM_START: GuestPhysAddr = GuestPhysAddr::new(0x00);
pub(crate) const RAM_START: GuestPhysAddr = GuestPhysAddr::new(0x00);

pub const PT_DEVICE: u64 = 0x707;
pub const PT_PT: u64 = 0x713;
pub const PT_MEM: u64 = 0x713;
pub const PT_MEM_CD: u64 = 0x70F;
const SIZE_4KIB: u64 = 0x1000;

// PageTableEntry Flags
/// Present + 4KiB + device memory + inner_sharable + accessed
pub const PT_DEVICE: u64 = 0b11100000111;
/// Present + 4KiB + normal + inner_sharable + accessed
pub const PT_PT: u64 = 0b11100010011;
/// Present + 4KiB + normal + inner_sharable + accessed
pub const PT_MEM: u64 = 0b11100010011;
/// Present + 4KiB + device + inner_sharable + accessed
pub const PT_MEM_CD: u64 = 0b11100001111;
/// Self reference flag
pub const PT_SELF: u64 = 1 << 55;

/*
Expand Down Expand Up @@ -115,7 +124,7 @@ fn is_valid_address(virtual_address: GuestVirtAddr) -> bool {
pub fn virt_to_phys(
addr: GuestVirtAddr,
mem: &MmapMemory,
pagetable_l0: GuestPhysAddr,
pgt: GuestPhysAddr,
) -> Result<GuestPhysAddr, PagetableError> {
if !is_valid_address(addr) {
return Err(PagetableError::InvalidAddress);
Expand All @@ -132,9 +141,7 @@ pub fn virt_to_phys(
// - Our indices can't be larger than 512, so we stay in the borders of the page.
// - 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(),
)
std::mem::transmute::<&[u8], &[PageTableEntry]>(mem.slice_at(pgt, 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.
for level in 0..3 {
Expand All @@ -155,71 +162,124 @@ 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, length: u64) {
warn!("aarch64 pagetable initialization is untested!");

let mem_addr = std::ptr::addr_of_mut!(mem[0]);

assert!(mem.len() >= BOOT_PGT.as_u64() 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)
};
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;
assert!(mem.len() >= PGT_OFFSET as usize + 512 * size_of::<u64>());

assert!(mem.len() >= BOOT_PGT.as_u64() 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,
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() + 0x2000 + PT_PT;
pgt_slice[511] = (guest_address + PGT_OFFSET) | PT_PT | PT_SELF;

assert!(mem.len() >= BOOT_PGT.as_u64() 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,
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;
let mut boot_frame_allocator = BumpAllocator::<SIZE_4KIB>::new(
guest_address + PAGETABLES_OFFSET,
(PAGETABLES_END - PAGETABLES_OFFSET) / SIZE_4KIB,
);

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 0x3000 + 512 * size_of::<u64>());
let pgt_slice = unsafe {
// Hypercalls are MMIO reads/writes in the lowest 4KiB of address space. Thus, we need to provide pagetable entries for this region.
let pgd0_addr = boot_frame_allocator.allocate().unwrap().as_u64();
pgt_slice[0] = pgd0_addr | PT_PT;
let pgd0_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset(BOOT_PGT.as_u64() as isize + 0x3000) as *mut u64,
mem_addr.offset((pgd0_addr - guest_address.as_u64()) as isize) 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;
pgd0_slice.fill(0);
let pud0_addr = boot_frame_allocator.allocate().unwrap().as_u64();
pgd0_slice[0] = pud0_addr | PT_PT;

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 0x4000 + 512 * size_of::<u64>());
let pgt_slice = unsafe {
let pud0_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset(BOOT_PGT.as_u64() as isize + 0x4000) as *mut u64,
mem_addr.offset((pud0_addr - guest_address.as_u64()) as isize) as *mut u64,
512,
)
};
for (idx, i) in pgt_slice.iter_mut().enumerate() {
*i = 0x200000u64 + (idx * PAGE_SIZE) as u64 + PT_MEM;
}
pud0_slice.fill(0);
let pmd0_addr = boot_frame_allocator.allocate().unwrap().as_u64();
pud0_slice[0] = pmd0_addr | PT_PT;

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 0x5000 + 512 * size_of::<u64>());
let pgt_slice = unsafe {
let pmd0_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset(BOOT_PGT.as_u64() as isize + 0x5000) as *mut u64,
mem_addr.offset((pmd0_addr - guest_address.as_u64()) as isize) as *mut u64,
512,
)
};
for (idx, i) in pgt_slice.iter_mut().enumerate() {
*i = 0x400000u64 + (idx * PAGE_SIZE) as u64 + PT_MEM;
pmd0_slice.fill(0);
// Hypercall/IO mapping
pmd0_slice[0] = PT_MEM;

for frame_addr in (guest_address.align_down(SIZE_4KIB).as_u64()
..(guest_address + length).align_up(SIZE_4KIB).as_u64())
.step_by(SIZE_4KIB as usize)
{
let idx_l4 = (frame_addr as usize / (0x80_0000_0000)) & (0xFFF);
let idx_l3 = (frame_addr as usize / (0x4000_0000)) & (0xFFF);
let idx_l2 = (frame_addr as usize / (0x20_0000)) & (0xFFF);
let idx_l1 = (frame_addr as usize / (0x1000)) & (0xFFF);
debug!("mapping frame {frame_addr:x} to pagetable {idx_l4}-{idx_l3}-{idx_l2}-{idx_l1}");

let (pgd_addr, new) = if pgt_slice[idx_l4] == 0 {
(boot_frame_allocator.allocate().unwrap() | PT_PT, true)
} else {
(
PageTableEntry::from(pgt_slice[idx_l4]).address().as_u64(),
false,
)
};
let pgd_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset((pgd_addr - guest_address.as_u64()) as isize) as *mut u64,
512,
)
};
if new {
pgd_slice.fill(0);
pgt_slice[idx_l4] = pgd_addr | PT_PT;
}

let (pud_addr, new) = if pgd_slice[idx_l3] == 0 {
(boot_frame_allocator.allocate().unwrap() | PT_PT, true)
} else {
(
PageTableEntry::from(pgd_slice[idx_l3]).address().as_u64(),
false,
)
};
let pud_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset((pud_addr - guest_address.as_u64()) as isize) as *mut u64,
512,
)
};
if new {
pud_slice.fill(0);
pgd_slice[idx_l3] = pud_addr | PT_PT;
}

let (pmd_addr, new) = if pud_slice[idx_l2] == 0 {
(boot_frame_allocator.allocate().unwrap() | PT_PT, true)
} else {
(
PageTableEntry::from(pud_slice[idx_l2]).address().as_u64(),
false,
)
};
let pmd_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset((pmd_addr - guest_address.as_u64()) as isize) as *mut u64,
512,
)
};
if new {
pmd_slice.fill(0);
pud_slice[idx_l2] = pmd_addr | PT_PT;
}

pmd_slice[idx_l1] = frame_addr | PT_MEM
}
}
60 changes: 36 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,22 @@ 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,
legacy_mapping: bool,
) {
// 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, legacy_mapping);
}

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

use super::*;
use crate::consts::{BOOT_PDE, BOOT_PDPTE, BOOT_PML4};
use crate::consts::{MIN_PHYSMEM_SIZE, PAGETABLES_END, PAGETABLES_OFFSET, PML4_OFFSET};

#[test]
fn test_virt_to_phys() {
Expand All @@ -72,38 +77,45 @@ 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,
false,
);
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 323b364

Please sign in to comment.