Place the heap after bss segment
This commit is contained in:
parent
98a98dbacd
commit
ae9b0d5740
|
|
@ -15,7 +15,7 @@ use crate::{
|
|||
process::{
|
||||
ContextUnshareAdminApi, Credentials, Process,
|
||||
posix_thread::{PosixThread, ThreadLocal, ThreadName, sigkill_other_threads, thread_table},
|
||||
process_vm::{MAX_LEN_STRING_ARG, MAX_NR_STRING_ARGS, new_vmar_and_map},
|
||||
process_vm::{MAX_LEN_STRING_ARG, MAX_NR_STRING_ARGS, ProcessVm},
|
||||
program_loader::{ProgramToLoad, elf::ElfLoadInfo},
|
||||
signal::{
|
||||
HandlePendingSignal, PauseReason, SigStack,
|
||||
|
|
@ -54,7 +54,7 @@ pub fn do_execve(
|
|||
let program_to_load =
|
||||
ProgramToLoad::build_from_inode(elf_inode.clone(), &fs_resolver, argv, envp)?;
|
||||
|
||||
let new_vmar = new_vmar_and_map(elf_file.clone());
|
||||
let new_vmar = Vmar::new(ProcessVm::new(elf_file.clone()));
|
||||
let elf_load_info = program_to_load.load_to_vmar(new_vmar.as_ref(), &fs_resolver)?;
|
||||
|
||||
// Ensure no other thread is concurrently performing exit_group or execve.
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ pub use process::{
|
|||
Terminal, broadcast_signal_async, enqueue_signal_async, spawn_init_process,
|
||||
};
|
||||
pub use process_filter::ProcessFilter;
|
||||
pub use process_vm::ProcessVm;
|
||||
pub use process_vm::{INIT_STACK_SIZE, ProcessVm};
|
||||
pub use rlimit::ResourceType;
|
||||
pub use stats::collect_process_creation_count;
|
||||
pub use term_status::TermStatus;
|
||||
|
|
|
|||
|
|
@ -13,16 +13,16 @@ use crate::{
|
|||
},
|
||||
prelude::*,
|
||||
process::{
|
||||
Credentials, UserNamespace,
|
||||
Credentials, ProcessVm, UserNamespace,
|
||||
posix_thread::{PosixThreadBuilder, ThreadName, allocate_posix_tid},
|
||||
process_table,
|
||||
process_vm::new_vmar_and_map,
|
||||
program_loader::ProgramToLoad,
|
||||
rlimit::new_resource_limits_for_init,
|
||||
signal::sig_disposition::SigDispositions,
|
||||
},
|
||||
sched::Nice,
|
||||
thread::Tid,
|
||||
vm::vmar::Vmar,
|
||||
};
|
||||
|
||||
/// Creates and schedules the init process to run.
|
||||
|
|
@ -56,7 +56,7 @@ fn create_init_process(
|
|||
let elf_path = fs.resolver().read().lookup(&fs_path)?;
|
||||
|
||||
let pid = allocate_posix_tid();
|
||||
let process_vm = new_vmar_and_map(elf_path.clone());
|
||||
let vmar = Vmar::new(ProcessVm::new(elf_path.clone()));
|
||||
let resource_limits = new_resource_limits_for_init();
|
||||
let nice = Nice::default();
|
||||
let oom_score_adj = 0;
|
||||
|
|
@ -65,7 +65,7 @@ fn create_init_process(
|
|||
|
||||
let init_proc = Process::new(
|
||||
pid,
|
||||
process_vm,
|
||||
vmar,
|
||||
resource_limits,
|
||||
nice,
|
||||
oom_score_adj,
|
||||
|
|
|
|||
|
|
@ -1,139 +1,157 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
use core::ops::Range;
|
||||
|
||||
use align_ext::AlignExt;
|
||||
|
||||
use crate::{
|
||||
prelude::*,
|
||||
process::ResourceType,
|
||||
util::random::getrandom,
|
||||
vm::{perms::VmPerms, vmar::Vmar},
|
||||
};
|
||||
|
||||
/// The base address of user heap
|
||||
pub const USER_HEAP_BASE: Vaddr = 0x0000_0000_1000_0000;
|
||||
/// The max allowed size of user heap
|
||||
pub const USER_HEAP_SIZE_LIMIT: usize = 16 * 1024 * PAGE_SIZE; // 16 * 4MB
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Heap {
|
||||
/// The lowest address of the heap
|
||||
base: Vaddr,
|
||||
/// The heap size limit
|
||||
limit: usize,
|
||||
/// The current heap highest address
|
||||
current_program_break: AtomicUsize,
|
||||
inner: Mutex<Option<HeapInner>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct HeapInner {
|
||||
/// The size of the data segment, used for rlimit checking.
|
||||
data_segment_size: usize,
|
||||
/// The heap range.
|
||||
// NOTE: `heap_range.end` is decided by user input and may not be page-aligned.
|
||||
heap_range: Range<Vaddr>,
|
||||
}
|
||||
|
||||
impl Heap {
|
||||
pub const fn new() -> Self {
|
||||
Heap {
|
||||
base: USER_HEAP_BASE,
|
||||
limit: USER_HEAP_SIZE_LIMIT,
|
||||
current_program_break: AtomicUsize::new(USER_HEAP_BASE),
|
||||
pub(super) const fn new_uninitialized() -> Self {
|
||||
Self {
|
||||
inner: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes and maps the heap virtual memory.
|
||||
pub(super) fn alloc_and_map(&self, vmar: &Vmar) -> Result<()> {
|
||||
pub(super) fn map_and_init_heap(
|
||||
&self,
|
||||
vmar: &Vmar,
|
||||
data_segment_size: usize,
|
||||
heap_base: Vaddr,
|
||||
) -> Result<()> {
|
||||
let mut inner = self.inner.lock();
|
||||
|
||||
let nr_pages_padding = {
|
||||
// Some random padding pages are added as a simple measure to
|
||||
// make the heap values of a buggy user program harder
|
||||
// to be exploited by attackers.
|
||||
let mut nr_random_padding_pages: u8 = 0;
|
||||
getrandom(nr_random_padding_pages.as_bytes_mut());
|
||||
|
||||
nr_random_padding_pages as usize
|
||||
};
|
||||
|
||||
let heap_start = heap_base.align_up(PAGE_SIZE) + nr_pages_padding * PAGE_SIZE;
|
||||
|
||||
let vmar_map_options = {
|
||||
let perms = VmPerms::READ | VmPerms::WRITE;
|
||||
vmar.new_map(PAGE_SIZE, perms).unwrap().offset(self.base)
|
||||
vmar.new_map(PAGE_SIZE, perms).unwrap().offset(heap_start)
|
||||
};
|
||||
vmar_map_options.build()?;
|
||||
|
||||
// If we touch another mapped range when we are trying to expand the
|
||||
// heap, we fail.
|
||||
//
|
||||
// So a simple solution is to reserve enough space for the heap by
|
||||
// mapping without any permissions and allow it to be overwritten
|
||||
// later by `brk`. New mappings from `mmap` that overlaps this range
|
||||
// may be moved to another place.
|
||||
let vmar_reserve_options = {
|
||||
let perms = VmPerms::empty();
|
||||
vmar.new_map(USER_HEAP_SIZE_LIMIT - PAGE_SIZE, perms)
|
||||
.unwrap()
|
||||
.offset(self.base + PAGE_SIZE)
|
||||
};
|
||||
vmar_reserve_options.build()?;
|
||||
debug_assert!(inner.is_none());
|
||||
*inner = Some(HeapInner {
|
||||
data_segment_size,
|
||||
heap_range: heap_start..heap_start + PAGE_SIZE,
|
||||
});
|
||||
|
||||
self.set_uninitialized();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the current program break.
|
||||
pub fn program_break(&self) -> Vaddr {
|
||||
self.current_program_break.load(Ordering::Relaxed)
|
||||
/// Returns the current heap end.
|
||||
pub fn heap_end(&self) -> Vaddr {
|
||||
let inner = self.inner.lock();
|
||||
let inner = inner.as_ref().expect("Heap is not initialized");
|
||||
inner.heap_range.end
|
||||
}
|
||||
|
||||
/// Sets the program break to `new_heap_end`.
|
||||
/// Modifies the end address of the heap.
|
||||
///
|
||||
/// Returns the new program break on success, or the current program break on failure.
|
||||
/// Returns the new heap end on success, or the current heap end on failure.
|
||||
/// This behavior is consistent with the Linux `brk` syscall.
|
||||
pub fn set_program_break(
|
||||
//
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.16.9/source/mm/mmap.c#L115>
|
||||
pub fn modify_heap_end(
|
||||
&self,
|
||||
new_program_break: Vaddr,
|
||||
new_heap_end: Vaddr,
|
||||
ctx: &Context,
|
||||
) -> core::result::Result<Vaddr, Vaddr> {
|
||||
let user_space = ctx.user_space();
|
||||
let vmar = user_space.vmar();
|
||||
|
||||
let current_program_break = self.current_program_break.load(Ordering::Acquire);
|
||||
let mut inner = self.inner.lock();
|
||||
let inner = inner.as_mut().expect("Heap is not initialized");
|
||||
|
||||
// According to the Linux source code, when the `brk` value is more than the
|
||||
// rlimit, the `brk` syscall returns the current `brk` value.
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.16.9/source/mm/mmap.c#L152>
|
||||
if new_program_break > self.base + self.limit {
|
||||
return Err(current_program_break);
|
||||
}
|
||||
if new_program_break < current_program_break {
|
||||
// FIXME: should we allow shrink current user heap?
|
||||
return Ok(current_program_break);
|
||||
let heap_start = inner.heap_range.start;
|
||||
let current_heap_end = inner.heap_range.end;
|
||||
let new_heap_range = heap_start..new_heap_end;
|
||||
|
||||
// Check if the new heap end is valid.
|
||||
if new_heap_end < heap_start
|
||||
|| check_data_rlimit(inner.data_segment_size, &new_heap_range, ctx).is_err()
|
||||
|| new_heap_end.checked_add(PAGE_SIZE).is_none()
|
||||
{
|
||||
return Err(current_heap_end);
|
||||
}
|
||||
|
||||
let current_program_break_aligned = current_program_break.align_up(PAGE_SIZE);
|
||||
let new_program_break_aligned = new_program_break.align_up(PAGE_SIZE);
|
||||
let current_heap_end_aligned = current_heap_end.align_up(PAGE_SIZE);
|
||||
let new_heap_end_aligned = new_heap_end.align_up(PAGE_SIZE);
|
||||
|
||||
// No need to expand the heap.
|
||||
if new_program_break_aligned == current_program_break_aligned {
|
||||
self.current_program_break
|
||||
.store(new_program_break, Ordering::Release);
|
||||
return Ok(new_program_break);
|
||||
let old_size = current_heap_end_aligned - heap_start;
|
||||
let new_size = new_heap_end_aligned - heap_start;
|
||||
|
||||
// No change in the heap mapping.
|
||||
if old_size == new_size {
|
||||
inner.heap_range = new_heap_range;
|
||||
return Ok(new_heap_end);
|
||||
}
|
||||
|
||||
// Remove the reserved space.
|
||||
vmar.remove_mapping(current_program_break_aligned..new_program_break_aligned)
|
||||
.map_err(|_| current_program_break)?;
|
||||
// Because the mapped heap region may contain multiple mappings, which can be
|
||||
// done by `mmap` syscall or other ways, we need to be careful when modifying
|
||||
// the heap mapping.
|
||||
// For simplicity, we set `check_single_mapping` to `true` to ensure that the
|
||||
// heap region contains only a single mapping.
|
||||
vmar.resize_mapping(heap_start, old_size, new_size, true)
|
||||
.map_err(|_| current_heap_end)?;
|
||||
|
||||
let old_size = current_program_break_aligned - self.base;
|
||||
let new_size = new_program_break_aligned - self.base;
|
||||
// Expand the heap.
|
||||
vmar.resize_mapping(self.base, old_size, new_size, false)
|
||||
.map_err(|_| current_program_break)?;
|
||||
|
||||
self.current_program_break
|
||||
.store(new_program_break, Ordering::Release);
|
||||
Ok(new_program_break)
|
||||
}
|
||||
|
||||
pub(super) fn set_uninitialized(&self) {
|
||||
self.current_program_break
|
||||
.store(self.base + PAGE_SIZE, Ordering::Relaxed);
|
||||
inner.heap_range = new_heap_range;
|
||||
Ok(new_heap_end)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Heap {
|
||||
fn clone(&self) -> Self {
|
||||
let current_program_break = self.current_program_break.load(Ordering::Relaxed);
|
||||
Self {
|
||||
base: self.base,
|
||||
limit: self.limit,
|
||||
current_program_break: AtomicUsize::new(current_program_break),
|
||||
inner: Mutex::new(self.inner.lock().clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Heap {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
/// Checks whether the new heap range exceeds the data segment size limit.
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.16.9/source/include/linux/mm.h#L3287>
|
||||
fn check_data_rlimit(
|
||||
data_segment_size: usize,
|
||||
new_heap_range: &Range<Vaddr>,
|
||||
ctx: &Context,
|
||||
) -> Result<()> {
|
||||
let rlimit_data = ctx
|
||||
.process
|
||||
.resource_limits()
|
||||
.get_rlimit(ResourceType::RLIMIT_DATA)
|
||||
.get_cur();
|
||||
|
||||
if rlimit_data.saturating_sub(data_segment_size as u64) < new_heap_range.len() as u64 {
|
||||
return_errno_with_message!(Errno::ENOSPC, "the data segment size limit is reached");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use core::sync::atomic::{AtomicUsize, Ordering};
|
|||
use ostd::{sync::MutexGuard, task::disable_preempt};
|
||||
|
||||
pub use self::{
|
||||
heap::{Heap, USER_HEAP_SIZE_LIMIT},
|
||||
heap::Heap,
|
||||
init_stack::{
|
||||
INIT_STACK_SIZE, InitStack, InitStackReader, MAX_LEN_STRING_ARG, MAX_NR_STRING_ARGS,
|
||||
aux_vec::{AuxKey, AuxVec},
|
||||
|
|
@ -28,8 +28,6 @@ use crate::{fs::path::Path, prelude::*, vm::vmar::Vmar};
|
|||
|
||||
/*
|
||||
* The user's virtual memory space layout looks like below.
|
||||
* TODO: The layout of the userheap does not match the current implementation,
|
||||
* And currently the initial program break is a fixed value.
|
||||
*
|
||||
* (high address)
|
||||
* +---------------------+ <------+ The top of Vmar, which is the highest address usable
|
||||
|
|
@ -77,10 +75,10 @@ pub struct ProcessVm {
|
|||
|
||||
impl ProcessVm {
|
||||
/// Creates a new `ProcessVm` without mapping anything.
|
||||
fn new(executable_file: Path) -> Self {
|
||||
pub(super) fn new(executable_file: Path) -> Self {
|
||||
Self {
|
||||
init_stack: InitStack::new(),
|
||||
heap: Heap::new(),
|
||||
heap: Heap::new_uninitialized(),
|
||||
executable_file,
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
vdso_base: AtomicUsize::new(0),
|
||||
|
|
@ -124,6 +122,17 @@ impl ProcessVm {
|
|||
self.init_stack().map_and_write(vmar, argv, envp, aux_vec)
|
||||
}
|
||||
|
||||
/// Maps and initializes the heap virtual memory.
|
||||
pub(super) fn map_and_init_heap(
|
||||
&self,
|
||||
vmar: &Vmar,
|
||||
data_segment_size: usize,
|
||||
heap_base: Vaddr,
|
||||
) -> Result<()> {
|
||||
self.heap()
|
||||
.map_and_init_heap(vmar, data_segment_size, heap_base)
|
||||
}
|
||||
|
||||
/// Returns the base address for vDSO segment.
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub(super) fn vdso_base(&self) -> Vaddr {
|
||||
|
|
@ -200,19 +209,6 @@ impl<'a> ProcessVmarGuard<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new VMAR and map the heap.
|
||||
///
|
||||
/// This method should only be used to create a VMAR for the init process.
|
||||
pub(super) fn new_vmar_and_map(executable_file: Path) -> Arc<Vmar> {
|
||||
let new_vmar = Vmar::new(ProcessVm::new(executable_file));
|
||||
new_vmar
|
||||
.process_vm()
|
||||
.heap()
|
||||
.alloc_and_map(new_vmar.as_ref())
|
||||
.unwrap();
|
||||
new_vmar
|
||||
}
|
||||
|
||||
/// Activates the [`Vmar`] in the current process's context.
|
||||
pub(super) fn activate_vmar(ctx: &Context, new_vmar: Arc<Vmar>) {
|
||||
let mut vmar_guard = ctx.process.lock_vmar();
|
||||
|
|
|
|||
|
|
@ -168,6 +168,14 @@ impl ElfHeaders {
|
|||
.reduce(|r1, r2| r1.start.min(r2.start)..r1.end.max(r2.end))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Finds the last loadable segment and returns its virtual address bounds.
|
||||
pub(super) fn find_last_vaddr_bound(&self) -> Option<Range<Vaddr>> {
|
||||
self.loadable_phdrs
|
||||
.iter()
|
||||
.max_by_key(|phdr| phdr.virt_range().end)
|
||||
.map(|phdr| phdr.virt_range().clone())
|
||||
}
|
||||
}
|
||||
|
||||
struct ElfHeader {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ pub fn load_elf_to_vmar(
|
|||
not(any(target_arch = "x86_64", target_arch = "riscv64")),
|
||||
expect(unused_mut)
|
||||
)]
|
||||
let (_range, entry_point, mut aux_vec) =
|
||||
let (elf_mapped_info, entry_point, mut aux_vec) =
|
||||
map_vmos_and_build_aux_vec(vmar, ldso, &elf_headers, elf_inode)?;
|
||||
|
||||
// Map the vDSO and set the entry.
|
||||
|
|
@ -81,6 +81,11 @@ pub fn load_elf_to_vmar(
|
|||
|
||||
vmar.process_vm()
|
||||
.map_and_write_init_stack(vmar, argv, envp, aux_vec)?;
|
||||
vmar.process_vm().map_and_init_heap(
|
||||
vmar,
|
||||
elf_mapped_info.data_segment_size,
|
||||
elf_mapped_info.heap_base,
|
||||
)?;
|
||||
|
||||
let user_stack_top = vmar.process_vm().init_stack().user_stack_top();
|
||||
Ok(ElfLoadInfo {
|
||||
|
|
@ -132,26 +137,26 @@ fn lookup_and_parse_ldso(
|
|||
|
||||
/// Maps the VMOs to the corresponding virtual memory addresses and builds the auxiliary vector.
|
||||
///
|
||||
/// Returns the mapped range, the entry point, and the auxiliary vector.
|
||||
/// Returns the mapped information, the entry point, and the auxiliary vector.
|
||||
fn map_vmos_and_build_aux_vec(
|
||||
vmar: &Vmar,
|
||||
ldso: Option<(Path, ElfHeaders)>,
|
||||
parsed_elf: &ElfHeaders,
|
||||
elf_inode: &Arc<dyn Inode>,
|
||||
) -> Result<(RelocatedRange, Vaddr, AuxVec)> {
|
||||
) -> Result<(ElfMappedInfo, Vaddr, AuxVec)> {
|
||||
let ldso_load_info = if let Some((ldso_file, ldso_elf)) = ldso {
|
||||
Some(load_ldso(vmar, &ldso_file, &ldso_elf)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let elf_map_range = map_segment_vmos(parsed_elf, vmar, elf_inode, ldso_load_info.is_some())?;
|
||||
let elf_mapped_info = map_segment_vmos(parsed_elf, vmar, elf_inode, ldso_load_info.is_some())?;
|
||||
|
||||
let mut aux_vec = {
|
||||
let ldso_base = ldso_load_info
|
||||
.as_ref()
|
||||
.map(|load_info| load_info.range.relocated_start());
|
||||
init_aux_vec(parsed_elf, &elf_map_range, ldso_base)?
|
||||
init_aux_vec(parsed_elf, &elf_mapped_info.full_range, ldso_base)?
|
||||
};
|
||||
|
||||
// Set AT_SECURE based on setuid/setgid bits of the executable file.
|
||||
|
|
@ -166,7 +171,8 @@ fn map_vmos_and_build_aux_vec(
|
|||
let entry_point = if let Some(ldso_load_info) = ldso_load_info {
|
||||
ldso_load_info.entry_point
|
||||
} else {
|
||||
elf_map_range
|
||||
elf_mapped_info
|
||||
.full_range
|
||||
.relocated_addr_of(parsed_elf.entry_point())
|
||||
.ok_or_else(|| {
|
||||
Error::with_message(
|
||||
|
|
@ -176,7 +182,7 @@ fn map_vmos_and_build_aux_vec(
|
|||
})?
|
||||
};
|
||||
|
||||
Ok((elf_map_range, entry_point, aux_vec))
|
||||
Ok((elf_mapped_info, entry_point, aux_vec))
|
||||
}
|
||||
|
||||
struct LdsoLoadInfo {
|
||||
|
|
@ -189,7 +195,8 @@ struct LdsoLoadInfo {
|
|||
}
|
||||
|
||||
fn load_ldso(vmar: &Vmar, ldso_file: &Path, ldso_elf: &ElfHeaders) -> Result<LdsoLoadInfo> {
|
||||
let range = map_segment_vmos(ldso_elf, vmar, ldso_file.inode(), false)?;
|
||||
let elf_mapped_info = map_segment_vmos(ldso_elf, vmar, ldso_file.inode(), false)?;
|
||||
let range = elf_mapped_info.full_range;
|
||||
let entry_point = range
|
||||
.relocated_addr_of(ldso_elf.entry_point())
|
||||
.ok_or_else(|| {
|
||||
|
|
@ -201,11 +208,22 @@ fn load_ldso(vmar: &Vmar, ldso_file: &Path, ldso_elf: &ElfHeaders) -> Result<Lds
|
|||
Ok(LdsoLoadInfo { entry_point, range })
|
||||
}
|
||||
|
||||
/// The information of mapped ELF segments.
|
||||
struct ElfMappedInfo {
|
||||
/// The range covering all the mapped segments.
|
||||
full_range: RelocatedRange,
|
||||
/// The size of the data segment.
|
||||
data_segment_size: usize,
|
||||
/// The base address for the heap start.
|
||||
heap_base: Vaddr,
|
||||
}
|
||||
|
||||
/// Initializes a [`Vmo`] for each segment and then map to the [`Vmar`].
|
||||
///
|
||||
/// This function will return the mapped range that covers all segments. The
|
||||
/// range will be tight, i.e., will not include any padding bytes. So the
|
||||
/// boundaries may not be page-aligned.
|
||||
/// This function will return the mapped information, which contains the
|
||||
/// mapped range that covers all segments. The range will be tight,
|
||||
/// i.e., will not include any padding bytes. So the boundaries may not
|
||||
/// be page-aligned.
|
||||
///
|
||||
/// [`Vmo`]: crate::vm::vmo::Vmo
|
||||
fn map_segment_vmos(
|
||||
|
|
@ -213,9 +231,12 @@ fn map_segment_vmos(
|
|||
vmar: &Vmar,
|
||||
elf_inode: &Arc<dyn Inode>,
|
||||
has_interpreter: bool,
|
||||
) -> Result<RelocatedRange> {
|
||||
) -> Result<ElfMappedInfo> {
|
||||
let elf_va_range = elf.calc_total_vaddr_bounds();
|
||||
|
||||
// The base address for the heap start. If it's `None`, we will use the end of ELF segments.
|
||||
let mut heap_base = None;
|
||||
|
||||
let map_range = if elf.is_shared_object() {
|
||||
// Relocatable object.
|
||||
|
||||
|
|
@ -261,6 +282,16 @@ fn map_segment_vmos(
|
|||
.offset(offset)
|
||||
} else {
|
||||
// Static PIE program: pick an aligned address from the mmap region.
|
||||
|
||||
// When executing static PIE programs, place the heap area away from the
|
||||
// general mmap region and into the unused `PIE_BASE_ADDR` space.
|
||||
// This helps avoid early collisions, since the heap grows upward while
|
||||
// the stack grows downward, and other mappings (such as the vDSO) may
|
||||
// also be placed in the mmap region.
|
||||
//
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.16.9/source/fs/binfmt_elf.c#L1293>
|
||||
heap_base = Some(PIE_BASE_ADDR);
|
||||
|
||||
vmar.new_map(map_size, VmPerms::empty())?.align(align)
|
||||
};
|
||||
let aligned_range = vmar_map_options.build().map(|addr| addr..addr + map_size)?;
|
||||
|
|
@ -312,7 +343,16 @@ fn map_segment_vmos(
|
|||
map_segment_vmo(loadable_phdr, &elf_vmo, vmar, map_at)?;
|
||||
}
|
||||
|
||||
Ok(relocated_range)
|
||||
// Calculate the data segment size.
|
||||
// According to Linux behavior, the data segment only includes the last loadable segment.
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.16.9/source/fs/binfmt_elf.c#L1200-L1227>
|
||||
let data_segment_size = elf.find_last_vaddr_bound().map_or(0, |range| range.len());
|
||||
|
||||
Ok(ElfMappedInfo {
|
||||
full_range: relocated_range,
|
||||
data_segment_size,
|
||||
heap_base: heap_base.unwrap_or(map_range.end),
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates and maps the segment VMO to the VMAR.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use core::{
|
|||
sync::atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
|
||||
use super::process_vm::{INIT_STACK_SIZE, USER_HEAP_SIZE_LIMIT};
|
||||
use super::process_vm::INIT_STACK_SIZE;
|
||||
use crate::{
|
||||
prelude::*,
|
||||
process::{UserNamespace, credentials::capabilities::CapSet},
|
||||
|
|
@ -48,8 +48,7 @@ impl Default for ResourceLimits {
|
|||
// Sets the resource limits with predefined values
|
||||
rlimits[ResourceType::RLIMIT_CPU as usize] = RLimit64::new(RLIM_INFINITY, RLIM_INFINITY);
|
||||
rlimits[ResourceType::RLIMIT_FSIZE as usize] = RLimit64::new(RLIM_INFINITY, RLIM_INFINITY);
|
||||
rlimits[ResourceType::RLIMIT_DATA as usize] =
|
||||
RLimit64::new(USER_HEAP_SIZE_LIMIT as u64, RLIM_INFINITY);
|
||||
rlimits[ResourceType::RLIMIT_DATA as usize] = RLimit64::new(RLIM_INFINITY, RLIM_INFINITY);
|
||||
rlimits[ResourceType::RLIMIT_STACK as usize] =
|
||||
RLimit64::new(INIT_STACK_SIZE as u64, RLIM_INFINITY);
|
||||
rlimits[ResourceType::RLIMIT_CORE as usize] = RLimit64::new(0, RLIM_INFINITY);
|
||||
|
|
|
|||
|
|
@ -10,15 +10,16 @@ pub fn sys_brk(heap_end: u64, ctx: &Context) -> Result<SyscallReturn> {
|
|||
Some(heap_end as usize)
|
||||
};
|
||||
debug!("new heap end = {:x?}", heap_end);
|
||||
|
||||
let user_space = ctx.user_space();
|
||||
let user_heap = user_space.vmar().process_vm().heap();
|
||||
|
||||
let syscall_ret = match new_heap_end {
|
||||
let current_heap_end = match new_heap_end {
|
||||
Some(addr) => user_heap
|
||||
.set_program_break(addr, ctx)
|
||||
.unwrap_or_else(|current_break| current_break),
|
||||
None => user_heap.program_break(),
|
||||
.modify_heap_end(addr, ctx)
|
||||
.unwrap_or_else(|cur_heap_end| cur_heap_end),
|
||||
None => user_heap.heap_end(),
|
||||
};
|
||||
|
||||
Ok(SyscallReturn::Return(syscall_ret as _))
|
||||
Ok(SyscallReturn::Return(current_heap_end as _))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ use super::{
|
|||
};
|
||||
use crate::{
|
||||
prelude::*,
|
||||
process::{Process, ProcessVm, ResourceType},
|
||||
process::{INIT_STACK_SIZE, Process, ProcessVm, ResourceType},
|
||||
vm::vmar::is_userspace_vaddr_range,
|
||||
};
|
||||
|
||||
|
|
@ -304,47 +304,59 @@ impl VmarInner {
|
|||
Ok(offset..(offset + size))
|
||||
}
|
||||
|
||||
/// Allocates a free region for mapping.
|
||||
/// Allocates a free region for mapping, searching from high address to low address.
|
||||
///
|
||||
/// If no such region is found, return an error.
|
||||
fn alloc_free_region(&mut self, size: usize, align: usize) -> Result<Range<Vaddr>> {
|
||||
// Fast path that there's still room to the end.
|
||||
let highest_occupied = self
|
||||
.vm_mappings
|
||||
.iter()
|
||||
.next_back()
|
||||
.map_or(VMAR_LOWEST_ADDR, |vm_mapping| vm_mapping.range().end);
|
||||
// FIXME: The up-align may overflow.
|
||||
let last_occupied_aligned = highest_occupied.align_up(align);
|
||||
if let Some(last) = last_occupied_aligned.checked_add(size)
|
||||
&& last <= VMAR_CAP_ADDR
|
||||
{
|
||||
return Ok(last_occupied_aligned..last);
|
||||
// This value represents the highest possible address for a new mapping.
|
||||
// For simplicity, we use a fixed value `2048` here. The value contains the following considerations:
|
||||
// - The stack fixed padding size.
|
||||
// - The stack random padding size.
|
||||
// - The future growth of the stack.
|
||||
// FIXME: This value should consider the process's actual stack configuration, which may
|
||||
// exist in `ResourceLimits`.
|
||||
let high_limit = VMAR_CAP_ADDR - INIT_STACK_SIZE - PAGE_SIZE * 2048;
|
||||
let low_limit = VMAR_LOWEST_ADDR;
|
||||
|
||||
fn try_alloc_in_hole(
|
||||
hole_start: Vaddr,
|
||||
hole_end: Vaddr,
|
||||
size: usize,
|
||||
align: usize,
|
||||
) -> Option<Range<Vaddr>> {
|
||||
let start = hole_end.checked_sub(size)?.align_down(align);
|
||||
if start >= hole_start {
|
||||
Some(start..start + size)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Slow path that we need to search for a free region.
|
||||
// Here, we use a simple brute-force FIRST-FIT algorithm.
|
||||
// Allocate as low as possible to reduce fragmentation.
|
||||
let mut last_end: Vaddr = VMAR_LOWEST_ADDR;
|
||||
for vm_mapping in self.vm_mappings.iter() {
|
||||
let range = vm_mapping.range();
|
||||
let mut prev_vm_mapping_start = high_limit;
|
||||
for vm_mapping in self.vm_mappings.iter().rev() {
|
||||
let hole_start = vm_mapping.range().end.max(low_limit);
|
||||
let hole_end = prev_vm_mapping_start.min(high_limit);
|
||||
|
||||
debug_assert!(range.start >= last_end);
|
||||
debug_assert!(range.end <= highest_occupied);
|
||||
|
||||
let last_aligned = last_end.align_up(align);
|
||||
let needed_end = last_aligned
|
||||
.checked_add(size)
|
||||
.ok_or(Error::new(Errno::ENOMEM))?;
|
||||
|
||||
if needed_end <= range.start {
|
||||
return Ok(last_aligned..needed_end);
|
||||
if let Some(region) = try_alloc_in_hole(hole_start, hole_end, size, align) {
|
||||
return Ok(region);
|
||||
}
|
||||
|
||||
last_end = range.end;
|
||||
prev_vm_mapping_start = vm_mapping.range().start;
|
||||
if prev_vm_mapping_start <= low_limit {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return_errno_with_message!(Errno::ENOMEM, "Cannot find free region for mapping");
|
||||
// Check the hole between `low_limit` and the lowest mapping.
|
||||
if prev_vm_mapping_start > low_limit {
|
||||
let hole_start = low_limit;
|
||||
let hole_end = prev_vm_mapping_start.min(high_limit);
|
||||
if let Some(region) = try_alloc_in_hole(hole_start, hole_end, size, align) {
|
||||
return Ok(region);
|
||||
}
|
||||
}
|
||||
|
||||
return_errno_with_message!(Errno::ENOMEM, "no free region for mapping can be found");
|
||||
}
|
||||
|
||||
/// Splits and unmaps the found mapping if the new size is smaller.
|
||||
|
|
|
|||
Loading…
Reference in New Issue