`ProgramToLoad` only needs a `elf_inode: Arc<dyn Inode>` instead of a `Path`

This commit is contained in:
Wang Siyuan 2025-10-22 16:21:14 +00:00 committed by Tate, Hongliang Tian
parent 808d298938
commit 71632a51b5
7 changed files with 69 additions and 70 deletions

View File

@ -8,7 +8,7 @@ use ostd::{
}; };
use crate::{ use crate::{
fs::{fs_resolver::FsResolver, path::Path}, fs::{fs_resolver::FsResolver, path::Path, utils::Inode},
prelude::*, prelude::*,
process::{ process::{
posix_thread::{sigkill_other_threads, thread_table, PosixThread, ThreadLocal, ThreadName}, posix_thread::{sigkill_other_threads, thread_table, PosixThread, ThreadLocal, ThreadName},
@ -46,8 +46,8 @@ pub fn do_execve(
let fs_ref = ctx.thread_local.borrow_fs(); let fs_ref = ctx.thread_local.borrow_fs();
let fs_resolver = fs_ref.resolver().read(); let fs_resolver = fs_ref.resolver().read();
let program_to_load = let elf_inode = elf_file.inode();
ProgramToLoad::build_from_file(elf_file.clone(), &fs_resolver, argv, envp, 1)?; let program_to_load = ProgramToLoad::build_from_inode(elf_inode, &fs_resolver, argv, envp, 1)?;
// Ensure no other thread is concurrently performing exit_group or execve. // Ensure no other thread is concurrently performing exit_group or execve.
// If such an operation is in progress, return EAGAIN. // If such an operation is in progress, return EAGAIN.
@ -152,7 +152,7 @@ fn do_execve_no_return(
set_cpu_context(thread_local, user_context, &elf_load_info); set_cpu_context(thread_local, user_context, &elf_load_info);
// Apply file-capability changes. // Apply file-capability changes.
apply_caps_from_exec(process, posix_thread, elf_file)?; apply_caps_from_exec(process, posix_thread, elf_file.inode())?;
// If this was a vfork child, reset vfork-specific state. // If this was a vfork child, reset vfork-specific state.
reset_vfork_child(process); reset_vfork_child(process);
@ -230,62 +230,62 @@ fn set_cpu_context(
debug!("user stack top: 0x{:x}", elf_load_info.user_stack_top); debug!("user stack top: 0x{:x}", elf_load_info.user_stack_top);
} }
/// Sets the UID and GID in the credentials according to the ELF file. /// Sets the UID and GID in the credentials according to the ELF inode.
/// ///
/// The capabilities will be updated accordingly. /// The capabilities will be updated accordingly.
fn apply_caps_from_exec( fn apply_caps_from_exec(
process: &Process, process: &Process,
posix_thread: &PosixThread, posix_thread: &PosixThread,
elf_file: &Path, elf_inode: &Arc<dyn Inode>,
) -> Result<()> { ) -> Result<()> {
// FIXME: We need to recalculate the capabilities during execve even the executable file // FIXME: We need to recalculate the capabilities during execve even the executable inode
// does not have setuid/setgid bit. // does not have setuid/setgid bit.
let credentials = posix_thread.credentials_mut(); let credentials = posix_thread.credentials_mut();
set_uid_from_elf(process, &credentials, elf_file)?; set_uid_from_elf(process, &credentials, elf_inode)?;
set_gid_from_elf(process, &credentials, elf_file)?; set_gid_from_elf(process, &credentials, elf_inode)?;
credentials.set_keep_capabilities(false)?; credentials.set_keep_capabilities(false)?;
Ok(()) Ok(())
} }
/// Sets the UID in the credentials according to the ELF file. /// Sets the UID in the credentials according to the ELF inode.
/// ///
/// If the ELF file has the `set_uid` bit, the effective UID is set to the same value as the ELF /// If the ELF inode has the `set_uid` bit, the effective UID is set to the same value as the ELF
/// file's UID. /// inode's UID.
fn set_uid_from_elf( fn set_uid_from_elf(
current: &Process, current: &Process,
credentials: &Credentials<WriteOp>, credentials: &Credentials<WriteOp>,
elf_file: &Path, elf_inode: &Arc<dyn Inode>,
) -> Result<()> { ) -> Result<()> {
if elf_file.mode()?.has_set_uid() { if elf_inode.mode()?.has_set_uid() {
let uid = elf_file.owner()?; let uid = elf_inode.owner()?;
credentials.set_euid(uid); credentials.set_euid(uid);
current.clear_parent_death_signal(); current.clear_parent_death_signal();
} }
// No matter whether the ELF file has `set_uid` bit, SUID should be reset. // No matter whether the ELF inode has `set_uid` bit, SUID should be reset.
credentials.reset_suid(); credentials.reset_suid();
Ok(()) Ok(())
} }
/// Sets the GID in the credentials according to the ELF file. /// Sets the GID in the credentials according to the ELF inode.
/// ///
/// If the ELF file has the `set_gid` bit, the effective GID is set to the same value as the ELF /// If the ELF inode has the `set_gid` bit, the effective GID is set to the same value as the ELF
/// file's GID. /// inode's GID.
fn set_gid_from_elf( fn set_gid_from_elf(
current: &Process, current: &Process,
credentials: &Credentials<WriteOp>, credentials: &Credentials<WriteOp>,
elf_file: &Path, elf_inode: &Arc<dyn Inode>,
) -> Result<()> { ) -> Result<()> {
if elf_file.mode()?.has_set_gid() { if elf_inode.mode()?.has_set_gid() {
let gid = elf_file.group()?; let gid = elf_inode.group()?;
credentials.set_egid(gid); credentials.set_egid(gid);
current.clear_parent_death_signal(); current.clear_parent_death_signal();
} }
// No matter whether the ELF file has `set_gid` bit, SGID should be reset. // No matter whether the ELF inode has `set_gid` bit, SGID should be reset.
credentials.reset_sgid(); credentials.reset_sgid();
Ok(()) Ok(())
} }

View File

@ -39,7 +39,7 @@ pub use process::{
}; };
pub use process_filter::ProcessFilter; pub use process_filter::ProcessFilter;
pub use process_vm::ProcessVm; pub use process_vm::ProcessVm;
pub use program_loader::{check_executable_file, ProgramToLoad}; pub use program_loader::{check_executable_inode, ProgramToLoad};
pub use rlimit::ResourceType; pub use rlimit::ResourceType;
pub use stats::collect_process_creation_count; pub use stats::collect_process_creation_count;
pub use term_status::TermStatus; pub use term_status::TermStatus;

View File

@ -103,7 +103,7 @@ fn create_init_task(
let fs_path = FsPath::try_from(executable_path)?; let fs_path = FsPath::try_from(executable_path)?;
let elf_file = fs.resolver().read().lookup(&fs_path)?; let elf_file = fs.resolver().read().lookup(&fs_path)?;
let program_to_load = let program_to_load =
ProgramToLoad::build_from_file(elf_file, &fs_resolver, argv, envp, 1)?; ProgramToLoad::build_from_inode(elf_file.inode(), &fs_resolver, argv, envp, 1)?;
let vmar = process.lock_vmar(); let vmar = process.lock_vmar();
program_to_load.load_to_vmar(vmar.unwrap(), &fs_resolver)? program_to_load.load_to_vmar(vmar.unwrap(), &fs_resolver)?
}; };

View File

@ -7,7 +7,7 @@ use xmas_elf::{
}; };
use crate::{ use crate::{
fs::{path::Path, utils::PATH_MAX}, fs::utils::{Inode, PATH_MAX},
prelude::*, prelude::*,
}; };
pub struct ElfHeaders { pub struct ElfHeaders {
@ -92,8 +92,8 @@ impl ElfHeaders {
self.elf_header.pt2.type_.as_type() == header::Type::SharedObject self.elf_header.pt2.type_.as_type() == header::Type::SharedObject
} }
/// Reads the LDSO path from the ELF file. /// Reads the LDSO path from the ELF inode.
pub fn read_ldso_path(&self, elf_file: &Path) -> Result<Option<CString>> { pub fn read_ldso_path(&self, elf_inode: &Arc<dyn Inode>) -> Result<Option<CString>> {
for program_header in &self.program_headers { for program_header in &self.program_headers {
let type_ = program_header.get_type().map_err(|_| { let type_ = program_header.get_type().map_err(|_| {
Error::with_message(Errno::ENOEXEC, "parse program header type fails") Error::with_message(Errno::ENOEXEC, "parse program header type fails")
@ -109,9 +109,8 @@ impl ElfHeaders {
); );
} }
let inode = elf_file.inode();
let mut buffer = vec![0; file_size]; let mut buffer = vec![0; file_size];
inode.read_bytes_at(file_offset, &mut buffer)?; elf_inode.read_bytes_at(file_offset, &mut buffer)?;
let ldso_path = CString::from_vec_with_nul(buffer).map_err(|_| { let ldso_path = CString::from_vec_with_nul(buffer).map_err(|_| {
Error::with_message( Error::with_message(

View File

@ -17,6 +17,7 @@ use crate::{
fs::{ fs::{
fs_resolver::{FsPath, FsResolver}, fs_resolver::{FsPath, FsResolver},
path::Path, path::Path,
utils::Inode,
}, },
prelude::*, prelude::*,
process::process_vm::{AuxKey, AuxVec}, process::process_vm::{AuxKey, AuxVec},
@ -29,20 +30,20 @@ use crate::{
/// initialize process init stack. /// initialize process init stack.
pub fn load_elf_to_vmar( pub fn load_elf_to_vmar(
vmar: &Vmar, vmar: &Vmar,
elf_file: Path, elf_inode: &Arc<dyn Inode>,
fs_resolver: &FsResolver, fs_resolver: &FsResolver,
elf_headers: ElfHeaders, elf_headers: ElfHeaders,
argv: Vec<CString>, argv: Vec<CString>,
envp: Vec<CString>, envp: Vec<CString>,
) -> Result<ElfLoadInfo> { ) -> Result<ElfLoadInfo> {
let ldso = lookup_and_parse_ldso(&elf_headers, &elf_file, fs_resolver)?; let ldso = lookup_and_parse_ldso(&elf_headers, elf_inode, fs_resolver)?;
#[cfg_attr( #[cfg_attr(
not(any(target_arch = "x86_64", target_arch = "riscv64")), not(any(target_arch = "x86_64", target_arch = "riscv64")),
expect(unused_mut) expect(unused_mut)
)] )]
let (_range, entry_point, mut aux_vec) = let (_range, entry_point, mut aux_vec) =
init_and_map_vmos(vmar, ldso, &elf_headers, &elf_file)?; init_and_map_vmos(vmar, ldso, &elf_headers, elf_inode)?;
// Map the vDSO and set the entry. // Map the vDSO and set the entry.
// Since the vDSO does not require being mapped to any specific address, // Since the vDSO does not require being mapped to any specific address,
@ -69,11 +70,11 @@ pub fn load_elf_to_vmar(
fn lookup_and_parse_ldso( fn lookup_and_parse_ldso(
headers: &ElfHeaders, headers: &ElfHeaders,
elf_file: &Path, elf_inode: &Arc<dyn Inode>,
fs_resolver: &FsResolver, fs_resolver: &FsResolver,
) -> Result<Option<(Path, ElfHeaders)>> { ) -> Result<Option<(Path, ElfHeaders)>> {
let ldso_file = { let ldso_file = {
let Some(ldso_path) = headers.read_ldso_path(elf_file)? else { let Some(ldso_path) = headers.read_ldso_path(elf_inode)? else {
return Ok(None); return Ok(None);
}; };
// Our FS requires the path to be valid UTF-8. This may be too restrictive. // Our FS requires the path to be valid UTF-8. This may be too restrictive.
@ -96,7 +97,7 @@ fn lookup_and_parse_ldso(
} }
fn load_ldso(vmar: &Vmar, ldso_file: &Path, ldso_elf: &ElfHeaders) -> Result<LdsoLoadInfo> { fn load_ldso(vmar: &Vmar, ldso_file: &Path, ldso_elf: &ElfHeaders) -> Result<LdsoLoadInfo> {
let range = map_segment_vmos(ldso_elf, vmar, ldso_file)?; let range = map_segment_vmos(ldso_elf, vmar, ldso_file.inode())?;
Ok(LdsoLoadInfo { Ok(LdsoLoadInfo {
entry_point: range entry_point: range
.relocated_addr_of(ldso_elf.entry_point()) .relocated_addr_of(ldso_elf.entry_point())
@ -116,7 +117,7 @@ fn init_and_map_vmos(
vmar: &Vmar, vmar: &Vmar,
ldso: Option<(Path, ElfHeaders)>, ldso: Option<(Path, ElfHeaders)>,
parsed_elf: &ElfHeaders, parsed_elf: &ElfHeaders,
elf_file: &Path, elf_inode: &Arc<dyn Inode>,
) -> Result<(RelocatedRange, Vaddr, AuxVec)> { ) -> Result<(RelocatedRange, Vaddr, AuxVec)> {
// After we clear process vm, if any error happens, we must call exit_group instead of return to user space. // 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 { let ldso_load_info = if let Some((ldso_file, ldso_elf)) = ldso {
@ -125,7 +126,7 @@ fn init_and_map_vmos(
None None
}; };
let elf_map_range = map_segment_vmos(parsed_elf, vmar, elf_file)?; let elf_map_range = map_segment_vmos(parsed_elf, vmar, elf_inode)?;
let mut aux_vec = { let mut aux_vec = {
let ldso_base = ldso_load_info let ldso_base = ldso_load_info
@ -135,7 +136,7 @@ fn init_and_map_vmos(
}; };
// Set AT_SECURE based on setuid/setgid bits of the executable file. // Set AT_SECURE based on setuid/setgid bits of the executable file.
let mode = elf_file.inode().mode()?; let mode = elf_inode.mode()?;
let secure = if mode.has_set_uid() || mode.has_set_gid() { let secure = if mode.has_set_uid() || mode.has_set_gid() {
1 1
} else { } else {
@ -183,7 +184,11 @@ pub struct ElfLoadInfo {
/// boundaries may not be page-aligned. /// boundaries may not be page-aligned.
/// ///
/// [`Vmo`]: crate::vm::vmo::Vmo /// [`Vmo`]: crate::vm::vmo::Vmo
pub fn map_segment_vmos(elf: &ElfHeaders, vmar: &Vmar, elf_file: &Path) -> Result<RelocatedRange> { pub fn map_segment_vmos(
elf: &ElfHeaders,
vmar: &Vmar,
elf_inode: &Arc<dyn Inode>,
) -> Result<RelocatedRange> {
let elf_va_range = get_range_for_all_segments(elf)?; let elf_va_range = get_range_for_all_segments(elf)?;
let map_range = if elf.is_shared_object() { let map_range = if elf.is_shared_object() {
@ -225,7 +230,7 @@ pub fn map_segment_vmos(elf: &ElfHeaders, vmar: &Vmar, elf_file: &Path) -> Resul
.relocated_addr_of(program_header.virtual_addr as Vaddr) .relocated_addr_of(program_header.virtual_addr as Vaddr)
.expect("Address not covered by `get_range_for_all_segments`"); .expect("Address not covered by `get_range_for_all_segments`");
map_segment_vmo(program_header, elf_file, vmar, map_at)?; map_segment_vmo(program_header, elf_inode, vmar, map_at)?;
} }
} }
@ -297,7 +302,7 @@ fn get_range_for_all_segments(elf: &ElfHeaders) -> Result<Range<Vaddr>> {
/// If needed, create additional anonymous mapping to represents .bss segment. /// If needed, create additional anonymous mapping to represents .bss segment.
fn map_segment_vmo( fn map_segment_vmo(
program_header: &ProgramHeader64, program_header: &ProgramHeader64,
elf_file: &Path, elf_inode: &Arc<dyn Inode>,
vmar: &Vmar, vmar: &Vmar,
map_at: Vaddr, map_at: Vaddr,
) -> Result<()> { ) -> Result<()> {
@ -318,8 +323,7 @@ fn map_segment_vmo(
let virtual_addr = program_header.virtual_addr as usize; let virtual_addr = program_header.virtual_addr as usize;
debug_assert!(file_offset % PAGE_SIZE == virtual_addr % PAGE_SIZE); debug_assert!(file_offset % PAGE_SIZE == virtual_addr % PAGE_SIZE);
let segment_vmo = { let segment_vmo = {
let inode = elf_file.inode(); elf_inode.page_cache().ok_or(Error::with_message(
inode.page_cache().ok_or(Error::with_message(
Errno::ENOENT, Errno::ENOENT,
"executable has no page cache", "executable has no page cache",
))? ))?

View File

@ -10,8 +10,7 @@ use self::{
use crate::{ use crate::{
fs::{ fs::{
fs_resolver::{FsPath, FsResolver}, fs_resolver::{FsPath, FsResolver},
path::Path, utils::{Inode, InodeType, Permission},
utils::{InodeType, Permission},
}, },
prelude::*, prelude::*,
vm::vmar::Vmar, vm::vmar::Vmar,
@ -22,7 +21,7 @@ use crate::{
/// This struct encapsulates the ELF file to be executed along with its header data, /// This struct encapsulates the ELF file to be executed along with its header data,
/// the `argv` and the `envp` which is required for the program execution. /// the `argv` and the `envp` which is required for the program execution.
pub struct ProgramToLoad { pub struct ProgramToLoad {
elf_file: Path, elf_inode: Arc<dyn Inode>,
file_first_page: Box<[u8; PAGE_SIZE]>, file_first_page: Box<[u8; PAGE_SIZE]>,
argv: Vec<CString>, argv: Vec<CString>,
envp: Vec<CString>, envp: Vec<CString>,
@ -36,18 +35,17 @@ impl ProgramToLoad {
/// then it will trigger recursion. We will try to setup VMAR for the interpreter. /// then it will trigger recursion. We will try to setup VMAR for the interpreter.
/// I guess for most cases, setting the `recursion_limit` as 1 should be enough. /// I guess for most cases, setting the `recursion_limit` as 1 should be enough.
/// because the interpreter is usually an elf binary(e.g., /bin/bash) /// because the interpreter is usually an elf binary(e.g., /bin/bash)
pub fn build_from_file( pub fn build_from_inode(
elf_file: Path, elf_inode: &Arc<dyn Inode>,
fs_resolver: &FsResolver, fs_resolver: &FsResolver,
argv: Vec<CString>, argv: Vec<CString>,
envp: Vec<CString>, envp: Vec<CString>,
recursion_limit: usize, recursion_limit: usize,
) -> Result<Self> { ) -> Result<Self> {
let inode = elf_file.inode();
let file_first_page = { let file_first_page = {
// Read the first page of file header, which must contain the ELF header. // Read the first page of file header, which must contain the ELF header.
let mut buffer = Box::new([0u8; PAGE_SIZE]); let mut buffer = Box::new([0u8; PAGE_SIZE]);
inode.read_bytes_at(0, &mut *buffer)?; elf_inode.read_bytes_at(0, &mut *buffer)?;
buffer buffer
}; };
if let Some(mut new_argv) = parse_shebang_line(&*file_first_page)? { if let Some(mut new_argv) = parse_shebang_line(&*file_first_page)? {
@ -60,9 +58,9 @@ impl ProgramToLoad {
let fs_path = FsPath::try_from(filename.as_str())?; let fs_path = FsPath::try_from(filename.as_str())?;
fs_resolver.lookup(&fs_path)? fs_resolver.lookup(&fs_path)?
}; };
check_executable_file(&interpreter)?; check_executable_inode(interpreter.inode())?;
return Self::build_from_file( return Self::build_from_inode(
interpreter, interpreter.inode(),
fs_resolver, fs_resolver,
new_argv, new_argv,
envp, envp,
@ -71,7 +69,7 @@ impl ProgramToLoad {
} }
Ok(Self { Ok(Self {
elf_file, elf_inode: elf_inode.clone(),
file_first_page, file_first_page,
argv, argv,
envp, envp,
@ -80,14 +78,12 @@ impl ProgramToLoad {
/// Loads the executable into the specified virtual memory space. /// Loads the executable into the specified virtual memory space.
/// ///
/// Returns a tuple containing: /// Returns the information about the ELF loading process.
/// 1. The absolute path of the loaded executable.
/// 2. Information about the ELF loading process.
pub fn load_to_vmar(self, vmar: &Vmar, fs_resolver: &FsResolver) -> Result<ElfLoadInfo> { pub fn load_to_vmar(self, vmar: &Vmar, fs_resolver: &FsResolver) -> Result<ElfLoadInfo> {
let elf_headers = ElfHeaders::parse_elf(&*self.file_first_page)?; let elf_headers = ElfHeaders::parse_elf(&*self.file_first_page)?;
let elf_load_info = load_elf_to_vmar( let elf_load_info = load_elf_to_vmar(
vmar, vmar,
self.elf_file, &self.elf_inode,
fs_resolver, fs_resolver,
elf_headers, elf_headers,
self.argv, self.argv,
@ -98,21 +94,21 @@ impl ProgramToLoad {
} }
} }
pub fn check_executable_file(path: &Path) -> Result<()> { pub fn check_executable_inode(inode: &Arc<dyn Inode>) -> Result<()> {
if path.type_().is_directory() { if inode.type_().is_directory() {
return_errno_with_message!(Errno::EISDIR, "the file is a directory"); return_errno_with_message!(Errno::EISDIR, "the inode is a directory");
} }
if path.type_() == InodeType::SymLink { if inode.type_() == InodeType::SymLink {
return_errno_with_message!(Errno::ELOOP, "the file is a symbolic link"); return_errno_with_message!(Errno::ELOOP, "the inode is a symbolic link");
} }
if !path.type_().is_regular_file() { if !inode.type_().is_regular_file() {
return_errno_with_message!(Errno::EACCES, "the path is not a regular file"); return_errno_with_message!(Errno::EACCES, "the inode is not a regular file");
} }
if path.inode().check_permission(Permission::MAY_EXEC).is_err() { if inode.check_permission(Permission::MAY_EXEC).is_err() {
return_errno_with_message!(Errno::EACCES, "the path is not executable"); return_errno_with_message!(Errno::EACCES, "the inode is not executable");
} }
Ok(()) Ok(())

View File

@ -10,7 +10,7 @@ use crate::{
path::Path, path::Path,
}, },
prelude::*, prelude::*,
process::{check_executable_file, do_execve}, process::{check_executable_inode, do_execve},
}; };
pub fn sys_execve( pub fn sys_execve(
@ -75,7 +75,7 @@ fn lookup_executable_file(
} }
}; };
check_executable_file(&path)?; check_executable_inode(path.inode())?;
Ok(path) Ok(path)
} }