From 2b8ccbf3d25170c71e6ebfaaaa011c0eae5b748d Mon Sep 17 00:00:00 2001 From: Ruihan Li Date: Fri, 12 Dec 2025 23:07:27 +0800 Subject: [PATCH] Clean up `load_elf.rs` and do overflow checks --- .../process/program_loader/elf/elf_file.rs | 253 +++++++++++--- .../process/program_loader/elf/load_elf.rs | 316 +++++++----------- kernel/src/process/program_loader/elf/mod.rs | 1 + .../process/program_loader/elf/relocate.rs | 40 +++ kernel/src/vm/vmar/mod.rs | 2 +- kernel/src/vm/vmar/vmar_impls/map.rs | 1 - test/src/apps/execve/execve_err.c | 44 ++- 7 files changed, 405 insertions(+), 252 deletions(-) create mode 100644 kernel/src/process/program_loader/elf/relocate.rs diff --git a/kernel/src/process/program_loader/elf/elf_file.rs b/kernel/src/process/program_loader/elf/elf_file.rs index 7f41fe48d..f4c8ebc24 100644 --- a/kernel/src/process/program_loader/elf/elf_file.rs +++ b/kernel/src/process/program_loader/elf/elf_file.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 +use core::ops::Range; + use xmas_elf::{ header::{self, Header, HeaderPt1, HeaderPt2, HeaderPt2_, Machine_, Type_}, program::{self, ProgramHeader64}, @@ -8,15 +10,26 @@ use xmas_elf::{ use crate::{ fs::utils::{Inode, PATH_MAX}, prelude::*, + vm::{perms::VmPerms, vmar::VMAR_CAP_ADDR}, }; /// A wrapper for the [`xmas_elf`] ELF parser. pub struct ElfHeaders { elf_header: ElfHeader, - program_headers: Vec, + loadable_phdrs: Vec, + max_load_align: usize, + interp_phdr: Option, } impl ElfHeaders { + /// The minimized length of a valid ELF header. + /// + /// [`Self::parse`] will fail with [`ENOEXEC`] if the slice is shorter than this constant. This + /// can also be checked manually if a different error code is expected. + /// + /// [`ENOEXEC`]: Errno::ENOEXEC + pub(super) const LEN: usize = size_of::() + size_of::(); + pub fn parse(input: &[u8]) -> Result { // Parse the ELF header. let header = xmas_elf::header::parse_header(input) @@ -47,7 +60,9 @@ impl ElfHeaders { } // Parse the ELF program headers. - let mut program_headers = Vec::with_capacity(ph_count as usize); + let mut loadable_phdrs = Vec::with_capacity(ph_count as usize); + let mut max_load_align = PAGE_SIZE; + let mut interp_phdr = None; for index in 0..ph_count { let program_header = xmas_elf::program::parse_program_header(input, header, index) .map_err(|_| { @@ -62,25 +77,44 @@ impl ElfHeaders { ) } }; - program_headers.push(ph64); + match ph64.get_type() { + Ok(program::Type::Load) => { + loadable_phdrs.push(LoadablePhdr::parse(&ph64)?); + // Like Linux, we ignore any invalid alignment requirements that are not a + // power of two. + if ph64.align.is_power_of_two() { + max_load_align = max_load_align.max(ph64.align as usize); + } + } + Ok(program::Type::Interp) if interp_phdr.is_none() => { + // Like Linux, we only handle the first interpreter program header. + interp_phdr = Some(InterpPhdr::parse(&ph64)?); + } + _ => (), + } + } + if loadable_phdrs.is_empty() { + return_errno_with_message!(Errno::ENOEXEC, "there are no loadable ELF program headers"); } Ok(Self { elf_header, - program_headers, + loadable_phdrs, + max_load_align, + interp_phdr, }) } + /// Returns whether the ELF is a shared object. + pub(super) fn is_shared_object(&self) -> bool { + self.elf_header.pt2.type_.as_type() == header::Type::SharedObject + } + /// Returns the address of the entry point. pub(super) fn entry_point(&self) -> Vaddr { self.elf_header.pt2.entry_point as Vaddr } - /// Returns a reference to the program headers. - pub(super) fn program_headers(&self) -> &[ProgramHeader64] { - self.program_headers.as_slice() - } - /// Returns the number of the program headers. pub(super) fn ph_count(&self) -> u16 { self.elf_header.pt2.ph_count @@ -91,14 +125,32 @@ impl ElfHeaders { self.elf_header.pt2.ph_entry_size } + /// Returns a reference to the loadable program headers. + /// + /// It is guaranteed that there is at least one loadable program header. + pub(super) fn loadable_phdrs(&self) -> &[LoadablePhdr] { + self.loadable_phdrs.as_slice() + } + + /// Returns the maximum alignment of the loadable program headers. + /// + /// It is guaranteed that the alignment is a power of two and is at least [`PAGE_SIZE`]. + pub(super) fn max_load_align(&self) -> usize { + self.max_load_align + } + + /// Returns a reference to the interpreter program header. + pub(super) fn interp_phdr(&self) -> Option<&InterpPhdr> { + self.interp_phdr.as_ref() + } + /// Finds the virtual address of the program headers. pub(super) fn find_vaddr_of_phdrs(&self) -> Result { - let ph_offset = self.elf_header.pt2.ph_offset; - for program_header in &self.program_headers { - if let Some(offset_in_ph) = ph_offset.checked_sub(program_header.offset) - && offset_in_ph <= program_header.file_size - { - return Ok((offset_in_ph + program_header.virtual_addr) as Vaddr); + let ph_offset = self.elf_header.pt2.ph_offset as usize; + for loadable_phdrs in self.loadable_phdrs.iter() { + if loadable_phdrs.file_range().contains(&ph_offset) { + return Ok(loadable_phdrs.virt_range().start + + (ph_offset - loadable_phdrs.file_range().start)); } } return_errno_with_message!( @@ -107,37 +159,14 @@ impl ElfHeaders { ); } - /// Returns whether the ELF is a shared object. - pub(super) fn is_shared_object(&self) -> bool { - self.elf_header.pt2.type_.as_type() == header::Type::SharedObject - } - - /// Reads the LDSO path from the ELF inode. - pub(super) fn read_ldso_path(&self, elf_inode: &Arc) -> Result> { - for program_header in &self.program_headers { - if let Ok(program::Type::Interp) = program_header.get_type() { - let file_size = program_header.file_size as usize; - let file_offset = program_header.offset as usize; - - if file_size > PATH_MAX { - return_errno_with_message!(Errno::ENOEXEC, "the interpreter path is too long"); - } - - let mut buffer = vec![0; file_size]; - elf_inode.read_bytes_at(file_offset, &mut buffer)?; - - let ldso_path = CString::from_vec_with_nul(buffer).map_err(|_| { - Error::with_message( - Errno::ENOEXEC, - "the interpreter path is not a valid C string", - ) - })?; - - return Ok(Some(ldso_path)); - } - } - - Ok(None) + /// Calculates the virtual address bounds of all segments as a range. + pub(super) fn calc_total_vaddr_bounds(&self) -> Range { + self.loadable_phdrs + .iter() + .map(LoadablePhdr::virt_range) + .cloned() + .reduce(|r1, r2| r1.start.min(r2.start)..r1.end.max(r2.end)) + .unwrap() } } @@ -244,3 +273,135 @@ fn check_elf_header(elf_header: &ElfHeader) -> Result<()> { Ok(()) } + +/// A ELF program header of the type [`program::Type::Load`]. +pub(super) struct LoadablePhdr { + virt_range: Range, + file_range: Range, + vm_perms: VmPerms, +} + +impl LoadablePhdr { + pub(self) fn parse(phdr: &ProgramHeader64) -> Result { + debug_assert_eq!(phdr.get_type(), Ok(program::Type::Load)); + + let virt_start = phdr.virtual_addr; + let virt_end = if let Some(virt_end) = virt_start.checked_add(phdr.mem_size) + && virt_end <= VMAR_CAP_ADDR as u64 + { + virt_end + } else { + return_errno_with_message!(Errno::ENOMEM, "the mapping address is too large"); + }; + + let file_start = phdr.offset; + let Some(file_end) = file_start.checked_add(phdr.file_size) else { + return_errno_with_message!(Errno::EINVAL, "the mapping offset overflows"); + }; + if file_end >= isize::MAX as u64 { + return_errno_with_message!(Errno::EOVERFLOW, "the mapping offset overflows"); + } + + if phdr.mem_size == 0 { + return_errno_with_message!(Errno::EINVAL, "the mapping length is zero"); + } + if phdr.mem_size < phdr.file_size { + return_errno_with_message!( + Errno::EINVAL, + "the mapping length is smaller than the file length" + ); + } + if virt_start % (PAGE_SIZE as u64) != file_start % (PAGE_SIZE as u64) { + return_errno_with_message!(Errno::EINVAL, "the mapping address is not aligned"); + } + + Ok(Self { + virt_range: (virt_start as Vaddr)..(virt_end as Vaddr), + file_range: (file_start as usize)..(file_end as usize), + vm_perms: parse_segment_perm(phdr.flags), + }) + } + + /// Returns the virtual address range. + /// + /// The range is guaranteed to be below [`VMAR_CAP_ADDR`] and non-empty. + pub(super) fn virt_range(&self) -> &Range { + &self.virt_range + } + + /// Returns the file offset range. + /// + /// The range is guaranteed to be below [`i64::MAX`]. It will also be shorter than the virtual + /// address range ([`Self::virt_range`]) and have the same offset within a page as that range. + pub(super) fn file_range(&self) -> &Range { + &self.file_range + } + + /// Returns the permission to map the virtual memory. + pub(super) fn vm_perms(&self) -> VmPerms { + self.vm_perms + } +} + +fn parse_segment_perm(flags: xmas_elf::program::Flags) -> VmPerms { + let mut vm_perm = VmPerms::empty(); + if flags.is_read() { + vm_perm |= VmPerms::READ; + } + if flags.is_write() { + vm_perm |= VmPerms::WRITE; + } + if flags.is_execute() { + vm_perm |= VmPerms::EXEC; + } + vm_perm +} + +/// A ELF program header of the type [`program::Type::Interp`]. +pub(super) struct InterpPhdr { + file_offset: usize, + file_size: u16, +} + +impl InterpPhdr { + pub(self) fn parse(phdr: &ProgramHeader64) -> Result { + debug_assert_eq!(phdr.get_type(), Ok(program::Type::Interp)); + + if phdr + .offset + .checked_add(phdr.file_size) + .is_none_or(|file_end| file_end > isize::MAX as u64) + { + return_errno_with_message!(Errno::EINVAL, "the interpreter offset overflows"); + } + + if phdr.file_size >= PATH_MAX as u64 { + return_errno_with_message!(Errno::ENOEXEC, "the interpreter path is too long"); + } + + const { assert!(PATH_MAX as u64 <= u16::MAX as u64) }; + + Ok(Self { + file_offset: phdr.offset as usize, + file_size: phdr.file_size as u16, + }) + } + + /// Reads the LDSO path from the ELF inode. + pub(super) fn read_ldso_path(&self, elf_inode: &Arc) -> Result { + // Note that `self.file_size` is at most `PATH_SIZE`. + let file_size = self.file_size as usize; + let mut buffer = vec![0; file_size]; + if elf_inode.read_bytes_at(self.file_offset, &mut buffer)? != file_size { + return_errno_with_message!(Errno::EIO, "the interpreter path cannot be fully read"); + } + + let ldso_path = CString::from_vec_with_nul(buffer).map_err(|_| { + Error::with_message( + Errno::ENOEXEC, + "the interpreter path is not a valid C string", + ) + })?; + Ok(ldso_path) + } +} diff --git a/kernel/src/process/program_loader/elf/load_elf.rs b/kernel/src/process/program_loader/elf/load_elf.rs index e8e5afad2..11e8423fa 100644 --- a/kernel/src/process/program_loader/elf/load_elf.rs +++ b/kernel/src/process/program_loader/elf/load_elf.rs @@ -1,14 +1,13 @@ // SPDX-License-Identifier: MPL-2.0 -//! This module is used to parse elf file content to get elf_load_info. -//! When create a process from elf file, we will use the elf_load_info to construct the VmSpace - -use core::ops::Range; +//! ELF file parser. use align_ext::AlignExt; -use xmas_elf::program::{self, ProgramHeader64}; -use super::elf_file::ElfHeaders; +use super::{ + elf_file::{ElfHeaders, LoadablePhdr}, + relocate::RelocatedRange, +}; use crate::{ fs::{ fs_resolver::{FsPath, FsResolver}, @@ -16,14 +15,28 @@ use crate::{ utils::Inode, }, prelude::*, - process::process_vm::{AuxKey, AuxVec}, - vm::{perms::VmPerms, vmar::Vmar}, + process::{ + check_executable_inode, + process_vm::{AuxKey, AuxVec}, + }, + vm::{ + perms::VmPerms, + vmar::{VMAR_LOWEST_ADDR, Vmar}, + vmo::Vmo, + }, }; -/// Loads elf to the process VMAR. +pub struct ElfLoadInfo { + /// The relocated entry point. + pub entry_point: Vaddr, + /// The top address of the user stack. + pub user_stack_top: Vaddr, +} + +/// Loads an ELF file to the process VMAR. /// -/// This function will map elf segments and -/// initialize process init stack. +/// This function will map ELF segments and +/// initialize the init stack and heap. pub fn load_elf_to_vmar( vmar: &Vmar, elf_inode: &Arc, @@ -39,7 +52,7 @@ pub fn load_elf_to_vmar( expect(unused_mut) )] let (_range, entry_point, mut aux_vec) = - init_and_map_vmos(vmar, ldso, &elf_headers, elf_inode)?; + map_vmos_and_build_aux_vec(vmar, ldso, &elf_headers, elf_inode)?; // Map the vDSO and set the entry. // Since the vDSO does not require being mapped to any specific address, @@ -60,7 +73,6 @@ pub fn load_elf_to_vmar( Ok(ElfLoadInfo { entry_point, user_stack_top, - _private: (), }) } @@ -70,52 +82,50 @@ fn lookup_and_parse_ldso( fs_resolver: &FsResolver, ) -> Result> { let ldso_file = { - let Some(ldso_path) = headers.read_ldso_path(elf_inode)? else { + let ldso_path = if let Some(interp_phdr) = headers.interp_phdr() { + interp_phdr.read_ldso_path(elf_inode)? + } else { return Ok(None); }; + // Our FS requires the path to be valid UTF-8. This may be too restrictive. let ldso_path = ldso_path.into_string().map_err(|_| { Error::with_message( Errno::ENOEXEC, - "The interpreter path specified in ELF is not a valid UTF-8 string", + "the interpreter path is not a valid UTF-8 string", ) })?; + let fs_path = FsPath::try_from(ldso_path.as_str())?; fs_resolver.lookup(&fs_path)? }; + let ldso_elf = { - let mut buf = Box::new([0u8; PAGE_SIZE]); let inode = ldso_file.inode(); - inode.read_bytes_at(0, &mut *buf)?; - ElfHeaders::parse(&*buf)? + check_executable_inode(inode)?; + + let mut buf = Box::new([0u8; PAGE_SIZE]); + let len = inode.read_bytes_at(0, &mut *buf)?; + if len < ElfHeaders::LEN { + return_errno_with_message!(Errno::EIO, "the interpreter format is invalid"); + } + + ElfHeaders::parse(&buf[..len]) + .map_err(|_| Error::with_message(Errno::ELIBBAD, "the interpreter format is invalid"))? }; + Ok(Some((ldso_file, ldso_elf))) } -fn load_ldso(vmar: &Vmar, ldso_file: &Path, ldso_elf: &ElfHeaders) -> Result { - let range = map_segment_vmos(ldso_elf, vmar, ldso_file.inode())?; - Ok(LdsoLoadInfo { - entry_point: range - .relocated_addr_of(ldso_elf.entry_point()) - .ok_or(Error::with_message( - Errno::ENOEXEC, - "The entry point is not in the mapped range", - ))?, - range, - _private: (), - }) -} - -/// Initializes the VM space and maps the VMO to the corresponding virtual memory address. +/// 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. -fn init_and_map_vmos( +/// Returns the mapped range, 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, ) -> Result<(RelocatedRange, Vaddr, AuxVec)> { - // After we clear process vm, if any error happens, we must call exit_group instead of return to user space. let ldso_load_info = if let Some((ldso_file, ldso_elf)) = ldso { Some(load_ldso(vmar, &ldso_file, &ldso_elf)?) } else { @@ -127,7 +137,7 @@ fn init_and_map_vmos( let mut aux_vec = { let ldso_base = ldso_load_info .as_ref() - .map(|load_info| load_info.range.relocated_start); + .map(|load_info| load_info.range.relocated_start()); init_aux_vec(parsed_elf, &elf_map_range, ldso_base)? }; @@ -141,36 +151,41 @@ fn init_and_map_vmos( aux_vec.set(AuxKey::AT_SECURE, secure)?; let entry_point = if let Some(ldso_load_info) = ldso_load_info { - // Normal shared object ldso_load_info.entry_point } else { elf_map_range .relocated_addr_of(parsed_elf.entry_point()) - .ok_or(Error::with_message( - Errno::ENOEXEC, - "The entry point is not in the mapped range", - ))? + .ok_or_else(|| { + Error::with_message( + Errno::ENOEXEC, + "the entry point is not located in any segments", + ) + })? }; Ok((elf_map_range, entry_point, aux_vec)) } -pub struct LdsoLoadInfo { - /// Relocated entry point. - pub entry_point: Vaddr, +struct LdsoLoadInfo { + /// The relocated entry point. + entry_point: Vaddr, /// The range covering all the mapped segments. /// - /// May not be page-aligned. - pub range: RelocatedRange, - _private: (), + /// Note that the range may not be page-aligned. + range: RelocatedRange, } -pub struct ElfLoadInfo { - /// Relocated entry point. - pub entry_point: Vaddr, - /// Address of the user stack top. - pub user_stack_top: Vaddr, - _private: (), +fn load_ldso(vmar: &Vmar, ldso_file: &Path, ldso_elf: &ElfHeaders) -> Result { + let range = map_segment_vmos(ldso_elf, vmar, ldso_file.inode())?; + let entry_point = range + .relocated_addr_of(ldso_elf.entry_point()) + .ok_or_else(|| { + Error::with_message( + Errno::ENOEXEC, + "the entry point is not located in any segments", + ) + })?; + Ok(LdsoLoadInfo { entry_point, range }) } /// Initializes a [`Vmo`] for each segment and then map to the [`Vmar`]. @@ -180,12 +195,12 @@ pub struct ElfLoadInfo { /// boundaries may not be page-aligned. /// /// [`Vmo`]: crate::vm::vmo::Vmo -pub fn map_segment_vmos( +fn map_segment_vmos( elf: &ElfHeaders, vmar: &Vmar, elf_inode: &Arc, ) -> Result { - let elf_va_range = get_range_for_all_segments(elf)?; + let elf_va_range = elf.calc_total_vaddr_bounds(); let map_range = if elf.is_shared_object() { // Relocatable object. @@ -200,6 +215,7 @@ pub fn map_segment_vmos( let vmar_map_options = vmar .new_map(map_size, VmPerms::empty())? + .align(elf.max_load_align()) .handle_page_faults_around(); let aligned_range = vmar_map_options.build().map(|addr| addr..addr + map_size)?; @@ -209,141 +225,66 @@ pub fn map_segment_vmos( aligned_range.start + start_in_page_offset..aligned_range.end - end_in_page_offset } else { // Not relocatable object. Map as-is. + + if elf_va_range.start < VMAR_LOWEST_ADDR { + return_errno_with_message!(Errno::EPERM, "the mapping address is too small"); + } + elf_va_range.clone() }; - let relocated_range = - RelocatedRange::new(elf_va_range, map_range.start).expect("Mapped range overflows"); + let relocated_range = RelocatedRange::new(elf_va_range, map_range.start) + .expect("`map_range` should not overflow"); - for program_header in elf.program_headers() { - let type_ = program_header.get_type().map_err(|_| { - Error::with_message(Errno::ENOEXEC, "Failed to parse the program header") - })?; - if type_ == program::Type::Load { - check_segment_align(program_header)?; - - let map_at = relocated_range - .relocated_addr_of(program_header.virtual_addr as Vaddr) - .expect("Address not covered by `get_range_for_all_segments`"); - - map_segment_vmo(program_header, elf_inode, vmar, map_at)?; - } + let Some(elf_vmo) = elf_inode.page_cache() else { + return_errno_with_message!(Errno::ENOEXEC, "the executable has no page cache"); + }; + for loadable_phdr in elf.loadable_phdrs() { + let map_at = relocated_range + .relocated_addr_of(loadable_phdr.virt_range().start) + .expect("`calc_total_vaddr_bounds()` should cover all segments"); + map_segment_vmo(loadable_phdr, &elf_vmo, vmar, map_at)?; } Ok(relocated_range) } -/// A virtual range and its relocated address. -pub struct RelocatedRange { - original_range: Range, - relocated_start: Vaddr, -} - -impl RelocatedRange { - /// Creates a new `RelocatedRange`. - /// - /// If the relocated address overflows, it will return `None`. - pub fn new(original_range: Range, relocated_start: Vaddr) -> Option { - relocated_start.checked_add(original_range.len())?; - Some(Self { - original_range, - relocated_start, - }) - } - - /// Gets the relocated address of an address in the original range. - /// - /// If the provided address is not in the original range, it will return `None`. - pub fn relocated_addr_of(&self, addr: Vaddr) -> Option { - if self.original_range.contains(&addr) { - Some(addr - self.original_range.start + self.relocated_start) - } else { - None - } - } -} - -/// Returns the range that covers all segments in the ELF file. +/// Creates and maps the segment VMO to the VMAR. /// -/// The range must be tight, i.e., will not include any padding bytes. So the -/// boundaries may not be page-aligned. -fn get_range_for_all_segments(elf: &ElfHeaders) -> Result> { - let loadable_ranges_iter = elf.program_headers().iter().filter_map(|ph| { - if let Ok(program::Type::Load) = ph.get_type() { - Some((ph.virtual_addr as Vaddr)..((ph.virtual_addr + ph.mem_size) as Vaddr)) - } else { - None - } - }); - - let min_addr = - loadable_ranges_iter - .clone() - .map(|r| r.start) - .min() - .ok_or(Error::with_message( - Errno::ENOEXEC, - "Executable file does not has loadable sections", - ))?; - - let max_addr = loadable_ranges_iter - .map(|r| r.end) - .max() - .expect("The range set contains minimum but no maximum"); - - Ok(min_addr..max_addr) -} - -/// Creates and map the corresponding segment VMO to `vmar`. -/// If needed, create additional anonymous mapping to represents .bss segment. +/// Additional anonymous mappings will be created to represent trailing bytes, if any. For example, +/// this applies to the `.bss` segment. fn map_segment_vmo( - program_header: &ProgramHeader64, - elf_inode: &Arc, + loadable_phdr: &LoadablePhdr, + elf_vmo: &Arc, vmar: &Vmar, map_at: Vaddr, ) -> Result<()> { + let virt_range = loadable_phdr.virt_range(); + let file_range = loadable_phdr.file_range(); trace!( - "mem range = 0x{:x} - 0x{:x}, mem_size = 0x{:x}", - program_header.virtual_addr, - program_header.virtual_addr + program_header.mem_size, - program_header.mem_size + "ELF segment: virt_range = {:#x?}, file_range = {:#x?}", + virt_range, file_range, ); - trace!( - "file range = 0x{:x} - 0x{:x}, file_size = 0x{:x}", - program_header.offset, - program_header.offset + program_header.file_size, - program_header.file_size - ); - - let file_offset = program_header.offset as usize; - let virtual_addr = program_header.virtual_addr as usize; - debug_assert!(file_offset % PAGE_SIZE == virtual_addr % PAGE_SIZE); - let segment_vmo = { - elf_inode.page_cache().ok_or(Error::with_message( - Errno::ENOENT, - "executable has no page cache", - ))? - }; let total_map_size = { - let vmap_start = virtual_addr.align_down(PAGE_SIZE); - let vmap_end = (virtual_addr + program_header.mem_size as usize).align_up(PAGE_SIZE); + let vmap_start = virt_range.start.align_down(PAGE_SIZE); + let vmap_end = virt_range.end.align_up(PAGE_SIZE); vmap_end - vmap_start }; let (segment_offset, segment_size) = { - let start = file_offset.align_down(PAGE_SIZE); - let end = (file_offset + program_header.file_size as usize).align_up(PAGE_SIZE); - debug_assert!(total_map_size >= (program_header.file_size as usize).align_up(PAGE_SIZE)); + let start = file_range.start.align_down(PAGE_SIZE); + let end = file_range.end.align_up(PAGE_SIZE); (start, end - start) }; - let perms = parse_segment_perm(program_header.flags); + let perms = loadable_phdr.vm_perms(); let offset = map_at.align_down(PAGE_SIZE); + if segment_size != 0 { let mut vm_map_options = vmar .new_map(segment_size, perms)? - .vmo(segment_vmo.clone()) + .vmo(elf_vmo.clone()) .vmo_offset(segment_offset) .can_overwrite(true); vm_map_options = vm_map_options.offset(offset).handle_page_faults_around(); @@ -354,57 +295,26 @@ fn map_segment_vmo( // private so the writes will trigger copy-on-write. Ignore errors if // the permissions do not allow writing. // Reference: - if program_header.file_size < program_header.mem_size { - let tail_start_vaddr = - map_addr + virtual_addr % PAGE_SIZE + program_header.file_size as usize; - if tail_start_vaddr < map_addr + segment_size { - let zero_size = PAGE_SIZE - tail_start_vaddr % PAGE_SIZE; - let res = vmar.fill_zeros_remote(tail_start_vaddr, zero_size); - if let Err((e, _)) = res - && perms.contains(VmPerms::WRITE) - { - return Err(e); - } - }; + let vaddr_to_zero = map_addr + (file_range.end - segment_offset); + let size_to_zero = map_addr + segment_size - vaddr_to_zero; + if size_to_zero != 0 { + let res = vmar.fill_zeros_remote(vaddr_to_zero, size_to_zero); + if let Err((err, _)) = res + && perms.contains(VmPerms::WRITE) + { + return Err(err); + } } } - let anonymous_map_size: usize = total_map_size.saturating_sub(segment_size); + let anonymous_map_size = total_map_size - segment_size; if anonymous_map_size > 0 { let mut anonymous_map_options = vmar.new_map(anonymous_map_size, perms)?.can_overwrite(true); anonymous_map_options = anonymous_map_options.offset(offset + segment_size); anonymous_map_options.build()?; } - Ok(()) -} -fn parse_segment_perm(flags: xmas_elf::program::Flags) -> VmPerms { - let mut vm_perm = VmPerms::empty(); - if flags.is_read() { - vm_perm |= VmPerms::READ; - } - if flags.is_write() { - vm_perm |= VmPerms::WRITE; - } - if flags.is_execute() { - vm_perm |= VmPerms::EXEC; - } - vm_perm -} - -fn check_segment_align(program_header: &ProgramHeader64) -> Result<()> { - let align = program_header.align; - if align == 0 || align == 1 { - // no align requirement - return Ok(()); - } - if !align.is_power_of_two() { - return_errno_with_message!(Errno::ENOEXEC, "segment align is invalid."); - } - if program_header.offset % align != program_header.virtual_addr % align { - return_errno_with_message!(Errno::ENOEXEC, "segment align is not satisfied."); - } Ok(()) } diff --git a/kernel/src/process/program_loader/elf/mod.rs b/kernel/src/process/program_loader/elf/mod.rs index 70323834b..9588ecaea 100644 --- a/kernel/src/process/program_loader/elf/mod.rs +++ b/kernel/src/process/program_loader/elf/mod.rs @@ -2,6 +2,7 @@ mod elf_file; mod load_elf; +mod relocate; pub use elf_file::ElfHeaders; pub use load_elf::{ElfLoadInfo, load_elf_to_vmar}; diff --git a/kernel/src/process/program_loader/elf/relocate.rs b/kernel/src/process/program_loader/elf/relocate.rs new file mode 100644 index 000000000..55f342113 --- /dev/null +++ b/kernel/src/process/program_loader/elf/relocate.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::ops::Range; + +use ostd::mm::Vaddr; + +/// A virtual range and its relocated address. +pub(super) struct RelocatedRange { + original_range: Range, + relocated_start: Vaddr, +} + +impl RelocatedRange { + /// Creates a new `RelocatedRange`. + /// + /// If the relocated address overflows, it will return `None`. + pub(super) fn new(original_range: Range, relocated_start: Vaddr) -> Option { + relocated_start.checked_add(original_range.len())?; + Some(Self { + original_range, + relocated_start, + }) + } + + /// Gets the relocated address of an address in the original range. + /// + /// If the provided address is not in the original range, it will return `None`. + pub(super) fn relocated_addr_of(&self, addr: Vaddr) -> Option { + if self.original_range.contains(&addr) { + Some(addr - self.original_range.start + self.relocated_start) + } else { + None + } + } + + /// Returns the relocated start address. + pub(super) fn relocated_start(&self) -> Vaddr { + self.relocated_start + } +} diff --git a/kernel/src/vm/vmar/mod.rs b/kernel/src/vm/vmar/mod.rs index d6975b8c4..c2aa83da6 100644 --- a/kernel/src/vm/vmar/mod.rs +++ b/kernel/src/vm/vmar/mod.rs @@ -14,7 +14,7 @@ use ostd::mm::Vaddr; pub use vmar_impls::{RssType, Vmar}; pub const VMAR_LOWEST_ADDR: Vaddr = 0x001_0000; // 64 KiB is the Linux configurable default -const VMAR_CAP_ADDR: Vaddr = ostd::mm::MAX_USERSPACE_VADDR; +pub const VMAR_CAP_ADDR: Vaddr = ostd::mm::MAX_USERSPACE_VADDR; /// Returns whether the input `vaddr` is a legal user space virtual address. pub fn is_userspace_vaddr(vaddr: Vaddr) -> bool { diff --git a/kernel/src/vm/vmar/vmar_impls/map.rs b/kernel/src/vm/vmar/vmar_impls/map.rs index 6aea70358..982aac930 100644 --- a/kernel/src/vm/vmar/vmar_impls/map.rs +++ b/kernel/src/vm/vmar/vmar_impls/map.rs @@ -141,7 +141,6 @@ impl<'a> VmarMapOptions<'a> { /// /// The provided alignment must be a power of two and a multiple of the /// page size. - #[expect(dead_code)] pub fn align(mut self, align: usize) -> Self { self.align = align; self diff --git a/test/src/apps/execve/execve_err.c b/test/src/apps/execve/execve_err.c index 63fa64aa0..3a7947487 100644 --- a/test/src/apps/execve/execve_err.c +++ b/test/src/apps/execve/execve_err.c @@ -10,7 +10,7 @@ struct custom_elf { Elf64_Ehdr ehdr; - Elf64_Phdr phdr[1]; + Elf64_Phdr phdr[3]; // See `push_interp` and `pop_interp` below. char buf[128]; }; @@ -145,6 +145,48 @@ FN_TEST(bad_phentsize) } END_TEST() +FN_TEST(bad_phnum) +{ + long old; + + old = elf.ehdr.e_phnum; + elf.ehdr.e_phnum = 0; + TEST_ERRNO(do_execve(), ENOEXEC); + elf.ehdr.e_phnum = old; +} +END_TEST() + +static unsigned int push_interp(const char *interpreter_path) +{ + unsigned int i; + + i = CHECK_WITH(elf.ehdr.e_phnum++, + _ret < sizeof(elf.phdr) / sizeof(elf.phdr[0])); + elf.phdr[i].p_type = PT_INTERP; + elf.phdr[i].p_offset = offsetof(struct custom_elf, buf); + elf.phdr[i].p_filesz = strlen(interpreter_path) + 1; + + strncpy(elf.buf, interpreter_path, sizeof(elf.buf) - 1); + + return i; +} + +static void pop_interp(void) +{ + CHECK_WITH(--elf.ehdr.e_phnum, _ret >= 1); +} + +FN_TEST(interp_too_long) +{ + unsigned int i; + + i = push_interp("/dev/zero"); + elf.phdr[i].p_filesz = 0x1000000; + TEST_ERRNO(do_execve(), ENOEXEC); + pop_interp(); +} +END_TEST() + FN_SETUP(cleanup) { CHECK(unlink(EXE_PATH));