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 hermit-os#719.

Co-authored-by: Jonathan <[email protected]>
  • Loading branch information
n0toose and jounathaen committed Jan 31, 2025
1 parent ce8173c commit df40d63
Show file tree
Hide file tree
Showing 16 changed files with 583 additions and 244 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
175 changes: 120 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,129 @@ 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,
_legacy_mapping: bool,
) {
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
}
}
Loading

0 comments on commit df40d63

Please sign in to comment.