Skip to content

Commit

Permalink
ASLR: Lay down the foundations
Browse files Browse the repository at this point in the history
- Move x86_64-related paging code to src/arch/x86_64/paging
- Tests: x86_64-related paging tests should use a guest_address that is
  not 0
- Tests: Move them in separate files, use appropriate 'use' directives
- Fix kernel memory loading
- Add guest_address getter in UhyveVm
- Change names of constants to clarify their purpose
- Use u64 for arch::RAM_START instead of GuestVirtAddr
- Remove pagetable_l0 from virt_to_phys function
- Various `cargo fmt`-related changes

We currently rely on guest_address in MmapMemory to calculate the
offsets during the initialization of the VM and when converting
virtual addresses to physical addresses. The latter case is intended
to be temporary - we should read the value from the CR3 register at
a later point.

Given this change, the arch::RAM_START's type was changed from
GuestVirtAddr to u64.

Although this current revision does work with relocatable binaries, it
is not making use of this functionality _just_ yet.

Fixes hermit-os#719.

Co-authored-by: Jonathan <[email protected]>
  • Loading branch information
n0toose and jounathaen committed Jul 6, 2024
1 parent e6ffffa commit 85ebaea
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 213 deletions.
190 changes: 36 additions & 154 deletions src/arch/x86_64/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod paging;
pub mod registers;

use core::arch::x86_64::_rdtsc as rdtsc;
Expand All @@ -10,17 +11,14 @@ use log::{debug, warn};
use raw_cpuid::{CpuId, CpuIdReaderNative};
use thiserror::Error;
use uhyve_interface::{GuestPhysAddr, GuestVirtAddr};
use x86_64::{
structures::paging::{
page_table::{FrameError, PageTableEntry},
Page, PageTable, PageTableFlags, PageTableIndex, Size2MiB,
},
PhysAddr,
use x86_64::structures::paging::{
page_table::{FrameError, PageTableEntry},
PageTable, PageTableIndex,
};

use crate::{consts::*, mem::MmapMemory, paging::PagetableError};
use crate::{consts::PML4_OFFSET, mem::MmapMemory, paging::PagetableError};

pub const RAM_START: GuestPhysAddr = GuestPhysAddr::new(0x00);
pub const RAM_START: u64 = 0;
const MHZ_TO_HZ: u64 = 1000000;
const KHZ_TO_HZ: u64 = 1000;

Expand Down Expand Up @@ -111,92 +109,10 @@ pub fn get_cpu_frequency_from_os() -> std::result::Result<u32, FrequencyDetectio
}
}

// Constructor for a conventional segment GDT (or LDT) entry
pub fn create_gdt_entry(flags: u64, base: u64, limit: u64) -> u64 {
((base & 0xff000000u64) << (56 - 24))
| ((flags & 0x0000f0ffu64) << 40)
| ((limit & 0x000f0000u64) << (48 - 16))
| ((base & 0x00ffffffu64) << 16)
| (limit & 0x0000ffffu64)
}

pub const MIN_PHYSMEM_SIZE: usize = BOOT_PDE.as_u64() as usize + 0x1000;

/// Creates the pagetables and the GDT in the guest memory space.
///
/// The memory slice must be larger than [`MIN_PHYSMEM_SIZE`].
/// Also, the memory `mem` needs to be zeroed for [`PAGE_SIZE`] bytes at the
/// offsets [`BOOT_PML4`] and [`BOOT_PDPTE`], otherwise the integrity of the
/// pagetables and thus the integrity of the guest's memory is not ensured
pub fn initialize_pagetables(mem: &mut [u8]) {
assert!(mem.len() >= MIN_PHYSMEM_SIZE);
let mem_addr = std::ptr::addr_of_mut!(mem[0]);

let (gdt_entry, pml4, pdpte, pde);
// Safety:
// We only operate in `mem`, which is plain bytes and we have ownership of
// these and it is asserted to be large enough.
unsafe {
gdt_entry = mem_addr
.add(BOOT_GDT.as_u64() as usize)
.cast::<[u64; 3]>()
.as_mut()
.unwrap();

pml4 = mem_addr
.add(BOOT_PML4.as_u64() as usize)
.cast::<PageTable>()
.as_mut()
.unwrap();
pdpte = mem_addr
.add(BOOT_PDPTE.as_u64() as usize)
.cast::<PageTable>()
.as_mut()
.unwrap();
pde = mem_addr
.add(BOOT_PDE.as_u64() as usize)
.cast::<PageTable>()
.as_mut()
.unwrap();

/* For simplicity we currently use 2MB pages and only a single
PML4/PDPTE/PDE. */

// per default is the memory zeroed, which we allocate by the system
// call mmap, so the following is not necessary:
/*libc::memset(pml4 as *mut _ as *mut libc::c_void, 0x00, PAGE_SIZE);
libc::memset(pdpte as *mut _ as *mut libc::c_void, 0x00, PAGE_SIZE);
libc::memset(pde as *mut _ as *mut libc::c_void, 0x00, PAGE_SIZE);*/
}
// initialize GDT
gdt_entry[BOOT_GDT_NULL] = 0;
gdt_entry[BOOT_GDT_CODE] = create_gdt_entry(0xA09B, 0, 0xFFFFF);
gdt_entry[BOOT_GDT_DATA] = create_gdt_entry(0xC093, 0, 0xFFFFF);

pml4[0].set_addr(
BOOT_PDPTE,
PageTableFlags::PRESENT | PageTableFlags::WRITABLE,
);
pml4[511].set_addr(
BOOT_PML4,
PageTableFlags::PRESENT | PageTableFlags::WRITABLE,
);
pdpte[0].set_addr(BOOT_PDE, PageTableFlags::PRESENT | PageTableFlags::WRITABLE);

for i in 0..512 {
let addr = PhysAddr::new(i as u64 * Page::<Size2MiB>::SIZE);
pde[i].set_addr(
addr,
PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::HUGE_PAGE,
);
}
}

/// Converts a virtual address in the guest to a physical address in the guest
pub fn virt_to_phys(
addr: GuestVirtAddr,
mem: &MmapMemory,
pagetable_l0: 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 @@ -205,7 +121,9 @@ 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();
// TODO: Too cursed?
unsafe { (mem.host_address(GuestPhysAddr::new(mem.guest_address.as_u64() + PML4_OFFSET)).unwrap() as *mut PageTable).as_mut() }
.unwrap();
let mut page_bits = 39;
let mut entry = PageTableEntry::new();

Expand All @@ -232,14 +150,16 @@ pub fn virt_to_phys(
Ok(entry.addr() + (addr.as_u64() & !((!0u64) << PAGE_BITS)))
}

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

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

use super::*;
use crate::{
consts::{MIN_PHYSMEM_SIZE, PDE_OFFSET, PDPTE_OFFSET, PML4_OFFSET},
x86_64::paging::initialize_pagetables,
};

// test is derived from
// https://github.com/gz/rust-cpuid/blob/master/examples/tsc_frequency.rs
#[test]
Expand Down Expand Up @@ -320,81 +240,43 @@ mod tests {
}

#[test]
fn test_pagetable_initialization() {
let mut mem: Vec<u8> = vec![0; MIN_PHYSMEM_SIZE];
initialize_pagetables((&mut mem[0..MIN_PHYSMEM_SIZE]).try_into().unwrap());

// Test pagetable setup
let addr_pdpte = u64::from_le_bytes(
mem[(BOOT_PML4.as_u64() as usize)..(BOOT_PML4.as_u64() as usize + 8)]
.try_into()
.unwrap(),
);
assert_eq!(
addr_pdpte,
BOOT_PDPTE.as_u64() | (PageTableFlags::PRESENT | PageTableFlags::WRITABLE).bits()
);
let addr_pde = u64::from_le_bytes(
mem[(BOOT_PDPTE.as_u64() as usize)..(BOOT_PDPTE.as_u64() as usize + 8)]
.try_into()
.unwrap(),
fn test_virt_to_phys() {
let guest_address = 0x11111000;
let mem = MmapMemory::new(
0,
MIN_PHYSMEM_SIZE * 2,
GuestPhysAddr::new(guest_address),
true,
true,
);
assert_eq!(
addr_pde,
BOOT_PDE.as_u64() | (PageTableFlags::PRESENT | PageTableFlags::WRITABLE).bits()
initialize_pagetables(
unsafe { mem.as_slice_mut() }.try_into().unwrap(),
guest_address,
);

for i in (0..4096).step_by(8) {
let addr = BOOT_PDE.as_u64() as usize + i;
let entry = u64::from_le_bytes(mem[addr..(addr + 8)].try_into().unwrap());
assert!(
PageTableFlags::from_bits_truncate(entry)
.difference(
PageTableFlags::PRESENT
| PageTableFlags::WRITABLE
| PageTableFlags::HUGE_PAGE
)
.is_empty(),
"Pagetable bits at {addr:#x} are incorrect"
)
}

// Test GDT
let gdt_results = [0x0, 0xAF9B000000FFFF, 0xCF93000000FFFF];
for (i, res) in gdt_results.iter().enumerate() {
let gdt_addr = BOOT_GDT.as_u64() as usize + i * 8;
let gdt_entry = u64::from_le_bytes(mem[gdt_addr..gdt_addr + 8].try_into().unwrap());
assert_eq!(*res, gdt_entry);
}
}

#[test]
fn test_virt_to_phys() {
let mem = MmapMemory::new(0, MIN_PHYSMEM_SIZE * 2, GuestPhysAddr::new(0), true, true);
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).unwrap();
assert_eq!(p_addr, GuestPhysAddr::new(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).unwrap();
assert_eq!(
mem.read::<u64>(p_addr).unwrap(),
BOOT_PML4.as_u64() | (PageTableFlags::PRESENT | PageTableFlags::WRITABLE).bits()
(guest_address + PML4_OFFSET)
| (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);
let p_addr = virt_to_phys(virt_addr, &mem).unwrap();
assert_eq!(p_addr, GuestPhysAddr::new(guest_address + PDPTE_OFFSET));

// 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).unwrap();
assert_eq!(p_addr, GuestPhysAddr::new(guest_address + PDE_OFFSET));
// 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 85ebaea

Please sign in to comment.