Skip to content

Commit 53a2d3b

Browse files
committed
feat(v2): refactor aslr, increase v2 max memory
Refactor of ASLR to support `v2` and `v1` images, which achieves the following: - Move start address derivation into `generate_guest_start_address` (f.k.a. `generate_address`) - Reduce code duplication - Remove a useless parameter - Introduce different handling between v1 and v2 images that correspondingly allows users to use v2 with higher memory amounts - Introduce relevant warnings/errors if user-provided parameters can cause Uhyve to error Fixes #1257
1 parent 21daf60 commit 53a2d3b

6 files changed

Lines changed: 147 additions & 95 deletions

File tree

src/arch/aarch64/mod.rs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ use std::mem::size_of;
22

33
use align_address::Align;
44
use bitflags::bitflags;
5-
use rand::RngExt;
65
use uhyve_interface::{GuestPhysAddr, GuestVirtAddr};
76

87
use crate::{
9-
consts::{KERNEL_OFFSET, PAGETABLES_END, PAGETABLES_OFFSET, PGT_OFFSET},
8+
consts::{PAGETABLES_END, PAGETABLES_OFFSET, PGT_OFFSET},
109
mem::MmapMemory,
1110
paging::{BumpAllocator, PagetableError},
1211
};
@@ -71,19 +70,6 @@ pub const TCR_FLAGS: u64 = TCR_IRGN_WBWA | TCR_ORGN_WBWA | TCR_SHARED;
7170
/// Number of virtual address bits for 4KB page
7271
pub const VA_BITS: u64 = 48;
7372

74-
/// Generates a random guest address for Uhyve's virtualized memory.
75-
/// This function gets invoked when a new UhyveVM gets created, provided that the object file is relocatable.
76-
pub(crate) fn generate_address(object_mem_size: usize) -> GuestPhysAddr {
77-
let mut rng = rand::rng();
78-
let start_address_upper_bound: u64 =
79-
0x0000_0010_0000_0000 - object_mem_size as u64 - KERNEL_OFFSET;
80-
81-
GuestPhysAddr::new(
82-
rng.random_range(RAM_START.as_u64()..start_address_upper_bound)
83-
.align_down(0x20_0000),
84-
)
85-
}
86-
8773
#[inline(always)]
8874
pub const fn tcr_size(x: u64) -> u64 {
8975
((64 - x) << 16) | (64 - x)

src/arch/mod.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1+
use std::ops::Add;
2+
3+
use align_address::Align;
4+
use hermit_entry::UhyveIfVersion;
15
use thiserror::Error;
6+
use uhyve_interface::GuestPhysAddr;
7+
8+
use crate::consts::KERNEL_OFFSET;
29

310
#[derive(Error, Debug)]
411
#[error("Frequency detection failed")]
@@ -15,3 +22,81 @@ pub mod aarch64;
1522

1623
#[cfg(target_arch = "aarch64")]
1724
pub use self::aarch64::*;
25+
26+
/// Returns a guest & start address tuple based on the object file.
27+
///
28+
/// Generates a tuple containing a potentially random guest address and a derived
29+
/// start address for Uhyve's virtualized memory. The guest address will not be
30+
/// random under the following conditions:
31+
/// - The image is not relocatable / uses uhyve-interface v1.
32+
/// - ASLR is disabled.
33+
///
34+
/// If the image is not relocatable, the start address will be equal to that
35+
/// present in the unikernel image file's object representation.
36+
///
37+
/// - `interface_version`: Version of uhyve-interface.
38+
/// - `aslr`: `bool` describing whether ASLR is enabled (`true`) or disabled (`false`).
39+
/// - `object_mem_size`: Memory required to load the object file onto the guest's memory.
40+
/// - `object_start_addr`: Start address embedded in the unikernel image (if applicable).
41+
/// - `mem_size`: User-defined memory size that should be available to the VM.
42+
pub(crate) fn generate_guest_start_address(
43+
interface_version: UhyveIfVersion,
44+
aslr: bool,
45+
object_mem_size: usize,
46+
object_start_addr: Option<u64>,
47+
mem_size: usize,
48+
) -> (GuestPhysAddr, GuestPhysAddr) {
49+
let (guest_address_lb, guest_address_ub): (u64, u64) = {
50+
let tmp: (u64, Option<u64>) = match interface_version.0 {
51+
1 => {
52+
// Workaround for x86_64-specific PCI holes.
53+
#[cfg(target_arch = "aarch64")]
54+
const V1_MAX_ADDR: u64 = 0x0000_0010_0000_0000u64;
55+
#[cfg(target_arch = "x86_64")]
56+
const V1_MAX_ADDR: u64 = 0x0000_0000_CFF0_0000u64;
57+
(
58+
RAM_START.as_u64(),
59+
V1_MAX_ADDR.checked_sub((object_mem_size + mem_size) as u64 + KERNEL_OFFSET),
60+
)
61+
}
62+
2 => (
63+
0x0000_0001_0000_0000u64,
64+
0x0000_0010_0000_0000u64
65+
.checked_sub((object_mem_size + mem_size) as u64 + KERNEL_OFFSET),
66+
),
67+
_ => unimplemented!(),
68+
};
69+
(
70+
tmp.0,
71+
tmp.1.unwrap_or_else(|| {
72+
panic!("Memory size {mem_size:#x} is higher than maximum upper boundary.")
73+
}),
74+
)
75+
};
76+
77+
match (aslr, object_start_addr) {
78+
(true, None) => {
79+
let mut rng = rand::rng();
80+
let guest_address = GuestPhysAddr::new(
81+
rand::RngExt::random_range(&mut rng, guest_address_lb..guest_address_ub)
82+
.align_down(0x20_0000),
83+
);
84+
(guest_address, guest_address.add(KERNEL_OFFSET))
85+
}
86+
(false, None) => {
87+
let guest_address = GuestPhysAddr::new(guest_address_lb);
88+
(guest_address, guest_address.add(KERNEL_OFFSET))
89+
}
90+
(true, Some(predefined_start_address)) => {
91+
warn!("ASLR is enabled but kernel is not relocatable - disabling ASLR");
92+
(
93+
GuestPhysAddr::new(guest_address_lb),
94+
GuestPhysAddr::new(predefined_start_address),
95+
)
96+
}
97+
(false, Some(predefined_start_address)) => (
98+
GuestPhysAddr::new(guest_address_lb),
99+
GuestPhysAddr::new(predefined_start_address),
100+
),
101+
}
102+
}

src/arch/x86_64/mod.rs

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,17 @@
11
mod paging;
22
pub(crate) mod registers;
33

4-
use align_address::Align;
54
use paging::initialize_pagetables;
6-
use rand::RngExt;
75
use uhyve_interface::{GuestPhysAddr, GuestVirtAddr};
86
use x86_64::structures::paging::{
97
PageTable, PageTableIndex,
108
page_table::{FrameError, PageTableEntry},
119
};
1210

13-
use crate::{consts::KERNEL_OFFSET, mem::MmapMemory, paging::PagetableError};
11+
use crate::{mem::MmapMemory, paging::PagetableError};
1412

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

17-
/// Generates a random guest address for Uhyve's virtualized memory.
18-
/// This function gets invoked when a new UhyveVM gets created, provided that the object file is relocatable.
19-
pub(crate) fn generate_address(object_mem_size: usize) -> GuestPhysAddr {
20-
let mut rng = rand::rng();
21-
// TODO: Also allow mappings beyond the 32 Bit gap
22-
let start_address_upper_bound: u64 =
23-
0x0000_0000_CFF0_0000 - object_mem_size as u64 - KERNEL_OFFSET;
24-
25-
GuestPhysAddr::new(
26-
rng.random_range(0x0..start_address_upper_bound)
27-
.align_down(0x20_0000),
28-
)
29-
}
30-
3115
/// Converts a virtual address in the guest to a physical address in the guest
3216
pub(crate) fn virt_to_phys(
3317
addr: GuestVirtAddr,

src/linux/x86_64/kvm_cpu.rs

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -68,36 +68,46 @@ impl VirtualizationBackendInternal for KvmVm {
6868
Ok(kvcpu)
6969
}
7070

71-
fn new(
72-
peripherals: Arc<VmPeripherals>,
73-
params: &Params,
74-
_guest_addr: GuestPhysAddr,
75-
) -> HypervisorResult<Self> {
71+
fn new(peripherals: Arc<VmPeripherals>, params: &Params) -> HypervisorResult<Self> {
7672
let vm = KVM.create_vm().unwrap();
7773

78-
let sz = std::cmp::min(peripherals.mem.size(), KVM_32BIT_GAP_START);
79-
80-
let kvm_mem = kvm_userspace_memory_region {
81-
slot: 0,
82-
flags: 0, // Can be KVM_MEM_LOG_DIRTY_PAGES and KVM_MEM_READONLY
83-
memory_size: sz as u64,
84-
guest_phys_addr: peripherals.mem.guest_addr().as_u64(),
85-
userspace_addr: peripherals.mem.host_start() as u64,
86-
};
74+
// Instead of distinguishing between different interface versions, we can just
75+
// assume the desired layout based on the guest address generated during UhyveVm's
76+
// initialization. Assumes that the sizes have already been checked.
77+
if peripherals.mem.guest_addr().as_usize() < KVM_32BIT_MAX_MEM_SIZE {
78+
let sz = std::cmp::min(peripherals.mem.size(), KVM_32BIT_GAP_START);
79+
let kvm_mem = kvm_userspace_memory_region {
80+
slot: 0,
81+
flags: 0, // Can be KVM_MEM_LOG_DIRTY_PAGES and KVM_MEM_READONLY
82+
memory_size: sz as u64,
83+
guest_phys_addr: peripherals.mem.guest_addr().as_u64(),
84+
userspace_addr: peripherals.mem.host_start() as u64,
85+
};
8786

88-
unsafe { vm.set_user_memory_region(kvm_mem) }?;
87+
unsafe { vm.set_user_memory_region(kvm_mem) }?;
8988

90-
if peripherals.mem.size() > KVM_32BIT_GAP_START + KVM_32BIT_GAP_SIZE {
89+
if peripherals.mem.size() > KVM_32BIT_GAP_START + KVM_32BIT_GAP_SIZE {
90+
let kvm_mem = kvm_userspace_memory_region {
91+
slot: 1,
92+
flags: 0, // Can be KVM_MEM_LOG_DIRTY_PAGES and KVM_MEM_READONLY
93+
memory_size: (peripherals.mem.size() - KVM_32BIT_GAP_START - KVM_32BIT_GAP_SIZE)
94+
as u64,
95+
guest_phys_addr: peripherals.mem.guest_addr().as_u64()
96+
+ (KVM_32BIT_GAP_START + KVM_32BIT_GAP_SIZE) as u64,
97+
userspace_addr: (peripherals.mem.host_start() as usize
98+
+ KVM_32BIT_GAP_START
99+
+ KVM_32BIT_GAP_SIZE) as u64,
100+
};
101+
102+
unsafe { vm.set_user_memory_region(kvm_mem) }?;
103+
}
104+
} else {
91105
let kvm_mem = kvm_userspace_memory_region {
92-
slot: 1,
106+
slot: 0,
93107
flags: 0, // Can be KVM_MEM_LOG_DIRTY_PAGES and KVM_MEM_READONLY
94-
memory_size: (peripherals.mem.size() - KVM_32BIT_GAP_START - KVM_32BIT_GAP_SIZE)
95-
as u64,
96-
guest_phys_addr: peripherals.mem.guest_addr().as_u64()
97-
+ (KVM_32BIT_GAP_START + KVM_32BIT_GAP_SIZE) as u64,
98-
userspace_addr: (peripherals.mem.host_start() as usize
99-
+ KVM_32BIT_GAP_START
100-
+ KVM_32BIT_GAP_SIZE) as u64,
108+
memory_size: peripherals.mem.size() as u64,
109+
guest_phys_addr: peripherals.mem.guest_addr().as_u64(),
110+
userspace_addr: peripherals.mem.host_start() as u64,
101111
};
102112

103113
unsafe { vm.set_user_memory_region(kvm_mem) }?;

src/macos/aarch64/vcpu.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,23 +56,19 @@ impl VirtualizationBackendInternal for XhyveVm {
5656
})
5757
}
5858

59-
fn new(
60-
peripherals: Arc<VmPeripherals>,
61-
_params: &Params,
62-
guest_addr: GuestPhysAddr,
63-
) -> HypervisorResult<Self> {
59+
fn new(peripherals: Arc<VmPeripherals>, _params: &Params) -> HypervisorResult<Self> {
6460
trace!("Create VM...");
6561
create_vm()?;
6662

6763
trace!("Map guest memory...");
6864
map_mem(
6965
unsafe { peripherals.mem.as_slice_mut() },
70-
guest_addr.as_u64(),
66+
peripherals.mem.guest_addr().as_u64(),
7167
MemPerm::ExecReadWrite,
7268
)?;
7369
// protect the first page for hypercall
7470
// Apple uses on aarch64 default page size of 16K
75-
protect_mem(guest_addr.as_u64(), 0x4000, MemPerm::None)?;
71+
protect_mem(peripherals.mem.guest_addr().as_u64(), 0x4000, MemPerm::None)?;
7672

7773
trace!("Create GIC...");
7874
let gic = Gic::new(GICD_BASE_ADDRESS, GICR_BASE_ADDRESS, MSI_BASE_ADDRESS)?;

src/vm.rs

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ use thiserror::Error;
2020
use uhyve_interface::GuestPhysAddr;
2121

2222
use crate::{
23-
HypervisorError, arch,
23+
HypervisorError,
2424
consts::*,
2525
fdt::Fdt,
26-
generate_address,
26+
generate_guest_start_address,
2727
isolation::filemap::UhyveFileMap,
2828
mem::MmapMemory,
2929
os::KickSignal,
@@ -62,8 +62,6 @@ pub type DefaultBackend = crate::macos::XhyveVm;
6262
pub(crate) mod internal {
6363
use std::sync::Arc;
6464

65-
use uhyve_interface::GuestPhysAddr;
66-
6765
use crate::{
6866
HypervisorResult,
6967
vcpu::VirtualCPU,
@@ -83,11 +81,7 @@ pub(crate) mod internal {
8381
enable_stats: bool,
8482
) -> HypervisorResult<Self::VCPU>;
8583

86-
fn new(
87-
peripherals: Arc<VmPeripherals>,
88-
params: &Params,
89-
guest_addr: GuestPhysAddr,
90-
) -> HypervisorResult<Self>;
84+
fn new(peripherals: Arc<VmPeripherals>, params: &Params) -> HypervisorResult<Self>;
9185
}
9286
}
9387

@@ -152,6 +146,18 @@ impl<VirtBackend: VirtualizationBackend> UhyveVm<VirtBackend> {
152146
info!("Loading a pre Hermit v0.10.0 kernel");
153147
}
154148

149+
// Kernels with different Uhyve interface versions may have differing addresses for the
150+
// serial port. As we begun embedding the uhyve-interface version in unikernel images
151+
// much later than v1, but before v2, we assume that all images that don't have a version
152+
// embedded must be v1.
153+
//
154+
// Further, it is used for establishing an appropriate random start addrses for ASLR.
155+
let uhyve_interface_version = object
156+
.uhyve_interface_version()
157+
.unwrap_or(UhyveIfVersion(1));
158+
159+
debug!("Detected Uhyve interface version: {uhyve_interface_version}");
160+
155161
// The memory layout of uhyve looks as follows:
156162
//
157163
// 0x0000_0000 ┌───────────────────┐
@@ -180,19 +186,13 @@ impl<VirtBackend: VirtualizationBackend> UhyveVm<VirtBackend> {
180186
// │ │
181187
// └───────────────────┘
182188

183-
let (guest_address, kernel_address) = if let Some(start_addr) = object.start_addr() {
184-
if params.aslr {
185-
warn!("ASLR is enabled but kernel is not relocatable - disabling ASLR");
186-
}
187-
(arch::RAM_START, GuestPhysAddr::from(start_addr))
188-
} else {
189-
let guest_address = if params.aslr {
190-
generate_address(object.mem_size())
191-
} else {
192-
arch::RAM_START
193-
};
194-
(guest_address, (guest_address + KERNEL_OFFSET))
195-
};
189+
let (guest_address, kernel_address) = generate_guest_start_address(
190+
uhyve_interface_version,
191+
params.aslr,
192+
object.mem_size(),
193+
object.start_addr(),
194+
memory_size,
195+
);
196196

197197
debug!("Guest starts at {guest_address:#x}");
198198
debug!("Kernel gets loaded to {kernel_address:#x}");
@@ -266,8 +266,7 @@ impl<VirtBackend: VirtualizationBackend> UhyveVm<VirtBackend> {
266266
serial,
267267
});
268268

269-
let virt_backend =
270-
VirtBackend::BACKEND::new(peripherals.clone(), &kernel_info.params, guest_address)?;
269+
let virt_backend = VirtBackend::BACKEND::new(peripherals.clone(), &kernel_info.params)?;
271270

272271
let cpu_count = kernel_info.params.cpu_count.get();
273272

@@ -290,14 +289,6 @@ impl<VirtBackend: VirtualizationBackend> UhyveVm<VirtBackend> {
290289

291290
let freq = vcpus[0].get_cpu_frequency();
292291

293-
// Kernels with different Uhyve interface versions may have differing addresses for the
294-
// serial port. As we begun embedding the uhyve-interface version in unikernel images
295-
// much later than v1, but before v2, we assume that all images that don't have a version
296-
// embedded must be v1.
297-
let uhyve_interface_version = object
298-
.uhyve_interface_version()
299-
.unwrap_or(UhyveIfVersion(1));
300-
301292
let serial_port = SerialPortBase::new(match uhyve_interface_version.0 {
302293
1 => uhyve_interface::v1::HypercallAddress::Uart as _,
303294
2 => uhyve_interface::v2::HypercallAddress::SerialWriteBuffer as _,

0 commit comments

Comments
 (0)