`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::{
fs::{fs_resolver::FsResolver, path::Path},
fs::{fs_resolver::FsResolver, path::Path, utils::Inode},
prelude::*,
process::{
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_resolver = fs_ref.resolver().read();
let program_to_load =
ProgramToLoad::build_from_file(elf_file.clone(), &fs_resolver, argv, envp, 1)?;
let elf_inode = elf_file.inode();
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.
// 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);
// 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.
reset_vfork_child(process);
@ -230,62 +230,62 @@ fn set_cpu_context(
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.
fn apply_caps_from_exec(
process: &Process,
posix_thread: &PosixThread,
elf_file: &Path,
elf_inode: &Arc<dyn Inode>,
) -> 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.
let credentials = posix_thread.credentials_mut();
set_uid_from_elf(process, &credentials, elf_file)?;
set_gid_from_elf(process, &credentials, elf_file)?;
set_uid_from_elf(process, &credentials, elf_inode)?;
set_gid_from_elf(process, &credentials, elf_inode)?;
credentials.set_keep_capabilities(false)?;
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
/// file's UID.
/// If the ELF inode has the `set_uid` bit, the effective UID is set to the same value as the ELF
/// inode's UID.
fn set_uid_from_elf(
current: &Process,
credentials: &Credentials<WriteOp>,
elf_file: &Path,
elf_inode: &Arc<dyn Inode>,
) -> Result<()> {
if elf_file.mode()?.has_set_uid() {
let uid = elf_file.owner()?;
if elf_inode.mode()?.has_set_uid() {
let uid = elf_inode.owner()?;
credentials.set_euid(uid);
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();
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
/// file's GID.
/// If the ELF inode has the `set_gid` bit, the effective GID is set to the same value as the ELF
/// inode's GID.
fn set_gid_from_elf(
current: &Process,
credentials: &Credentials<WriteOp>,
elf_file: &Path,
elf_inode: &Arc<dyn Inode>,
) -> Result<()> {
if elf_file.mode()?.has_set_gid() {
let gid = elf_file.group()?;
if elf_inode.mode()?.has_set_gid() {
let gid = elf_inode.group()?;
credentials.set_egid(gid);
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();
Ok(())
}

View File

@ -39,7 +39,7 @@ pub use process::{
};
pub use process_filter::ProcessFilter;
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 stats::collect_process_creation_count;
pub use term_status::TermStatus;

View File

@ -103,7 +103,7 @@ fn create_init_task(
let fs_path = FsPath::try_from(executable_path)?;
let elf_file = fs.resolver().read().lookup(&fs_path)?;
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();
program_to_load.load_to_vmar(vmar.unwrap(), &fs_resolver)?
};

View File

@ -7,7 +7,7 @@ use xmas_elf::{
};
use crate::{
fs::{path::Path, utils::PATH_MAX},
fs::utils::{Inode, PATH_MAX},
prelude::*,
};
pub struct ElfHeaders {
@ -92,8 +92,8 @@ impl ElfHeaders {
self.elf_header.pt2.type_.as_type() == header::Type::SharedObject
}
/// Reads the LDSO path from the ELF file.
pub fn read_ldso_path(&self, elf_file: &Path) -> Result<Option<CString>> {
/// Reads the LDSO path from the ELF inode.
pub fn read_ldso_path(&self, elf_inode: &Arc<dyn Inode>) -> Result<Option<CString>> {
for program_header in &self.program_headers {
let type_ = program_header.get_type().map_err(|_| {
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];
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(|_| {
Error::with_message(

View File

@ -17,6 +17,7 @@ use crate::{
fs::{
fs_resolver::{FsPath, FsResolver},
path::Path,
utils::Inode,
},
prelude::*,
process::process_vm::{AuxKey, AuxVec},
@ -29,20 +30,20 @@ use crate::{
/// initialize process init stack.
pub fn load_elf_to_vmar(
vmar: &Vmar,
elf_file: Path,
elf_inode: &Arc<dyn Inode>,
fs_resolver: &FsResolver,
elf_headers: ElfHeaders,
argv: Vec<CString>,
envp: Vec<CString>,
) -> 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(
not(any(target_arch = "x86_64", target_arch = "riscv64")),
expect(unused_mut)
)]
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.
// 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(
headers: &ElfHeaders,
elf_file: &Path,
elf_inode: &Arc<dyn Inode>,
fs_resolver: &FsResolver,
) -> Result<Option<(Path, ElfHeaders)>> {
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);
};
// 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> {
let range = map_segment_vmos(ldso_elf, vmar, ldso_file)?;
let range = map_segment_vmos(ldso_elf, vmar, ldso_file.inode())?;
Ok(LdsoLoadInfo {
entry_point: range
.relocated_addr_of(ldso_elf.entry_point())
@ -116,7 +117,7 @@ fn init_and_map_vmos(
vmar: &Vmar,
ldso: Option<(Path, ElfHeaders)>,
parsed_elf: &ElfHeaders,
elf_file: &Path,
elf_inode: &Arc<dyn Inode>,
) -> 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 {
@ -125,7 +126,7 @@ fn init_and_map_vmos(
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 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.
let mode = elf_file.inode().mode()?;
let mode = elf_inode.mode()?;
let secure = if mode.has_set_uid() || mode.has_set_gid() {
1
} else {
@ -183,7 +184,11 @@ pub struct ElfLoadInfo {
/// boundaries may not be page-aligned.
///
/// [`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 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)
.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.
fn map_segment_vmo(
program_header: &ProgramHeader64,
elf_file: &Path,
elf_inode: &Arc<dyn Inode>,
vmar: &Vmar,
map_at: Vaddr,
) -> Result<()> {
@ -318,8 +323,7 @@ fn map_segment_vmo(
let virtual_addr = program_header.virtual_addr as usize;
debug_assert!(file_offset % PAGE_SIZE == virtual_addr % PAGE_SIZE);
let segment_vmo = {
let inode = elf_file.inode();
inode.page_cache().ok_or(Error::with_message(
elf_inode.page_cache().ok_or(Error::with_message(
Errno::ENOENT,
"executable has no page cache",
))?

View File

@ -10,8 +10,7 @@ use self::{
use crate::{
fs::{
fs_resolver::{FsPath, FsResolver},
path::Path,
utils::{InodeType, Permission},
utils::{Inode, InodeType, Permission},
},
prelude::*,
vm::vmar::Vmar,
@ -22,7 +21,7 @@ use crate::{
/// 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.
pub struct ProgramToLoad {
elf_file: Path,
elf_inode: Arc<dyn Inode>,
file_first_page: Box<[u8; PAGE_SIZE]>,
argv: 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.
/// 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)
pub fn build_from_file(
elf_file: Path,
pub fn build_from_inode(
elf_inode: &Arc<dyn Inode>,
fs_resolver: &FsResolver,
argv: Vec<CString>,
envp: Vec<CString>,
recursion_limit: usize,
) -> Result<Self> {
let inode = elf_file.inode();
let file_first_page = {
// Read the first page of file header, which must contain the ELF header.
let mut buffer = Box::new([0u8; PAGE_SIZE]);
inode.read_bytes_at(0, &mut *buffer)?;
elf_inode.read_bytes_at(0, &mut *buffer)?;
buffer
};
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())?;
fs_resolver.lookup(&fs_path)?
};
check_executable_file(&interpreter)?;
return Self::build_from_file(
interpreter,
check_executable_inode(interpreter.inode())?;
return Self::build_from_inode(
interpreter.inode(),
fs_resolver,
new_argv,
envp,
@ -71,7 +69,7 @@ impl ProgramToLoad {
}
Ok(Self {
elf_file,
elf_inode: elf_inode.clone(),
file_first_page,
argv,
envp,
@ -80,14 +78,12 @@ impl ProgramToLoad {
/// Loads the executable into the specified virtual memory space.
///
/// Returns a tuple containing:
/// 1. The absolute path of the loaded executable.
/// 2. Information about the ELF loading process.
/// Returns the information about the ELF loading process.
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_load_info = load_elf_to_vmar(
vmar,
self.elf_file,
&self.elf_inode,
fs_resolver,
elf_headers,
self.argv,
@ -98,21 +94,21 @@ impl ProgramToLoad {
}
}
pub fn check_executable_file(path: &Path) -> Result<()> {
if path.type_().is_directory() {
return_errno_with_message!(Errno::EISDIR, "the file is a directory");
pub fn check_executable_inode(inode: &Arc<dyn Inode>) -> Result<()> {
if inode.type_().is_directory() {
return_errno_with_message!(Errno::EISDIR, "the inode is a directory");
}
if path.type_() == InodeType::SymLink {
return_errno_with_message!(Errno::ELOOP, "the file is a symbolic link");
if inode.type_() == InodeType::SymLink {
return_errno_with_message!(Errno::ELOOP, "the inode is a symbolic link");
}
if !path.type_().is_regular_file() {
return_errno_with_message!(Errno::EACCES, "the path is not a regular file");
if !inode.type_().is_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() {
return_errno_with_message!(Errno::EACCES, "the path is not executable");
if inode.check_permission(Permission::MAY_EXEC).is_err() {
return_errno_with_message!(Errno::EACCES, "the inode is not executable");
}
Ok(())

View File

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