mirror of
https://github.com/DragonOS-Community/DragonOS.git
synced 2026-09-08 23:57:59 +08:00
feat(vfs): implement close_range syscall (#2200)
* feat(vfs): implement close_range syscall Add Linux-compatible close_range(2) handling for close, CLOEXEC, and UNSHARE operations with the expected u32 ABI and validation order. Introduce a fallible two-phase fd-table clone so allocation failures cannot mutate the shared table or trigger close side effects. Scan close ranges with bounded work, perform file finalization outside fd-table locks, preserve reserved descriptors, and retain the correct POSIX lock owner semantics. Add deterministic no-skip coverage for range validation, raw argument truncation, shared and private tables, sparse descriptors, lowered RLIMIT_NOFILE, next-fd reuse, and record-lock ownership. Validated with x86_64 kernel format/check/clippy/build, a RISC-V kernel check, host Linux tests, and DragonOS guest tests. Signed-off-by: longjin <longjin@dragonos.org> * fix(vfs): bound close_range clone population Separate the cloned fd-table layout size from the installed File population bound. Tail punch-hole clones now stop copying at the last descriptor that must be retained instead of cloning up to the minimum 1024-slot layout and then closing those files. This avoids redundant Arc clones, range scans, and observable flush_for_close callbacks while preserving the minimum table capacity, reserved-fd behavior, next_fd recomputation, and ordinary clone semantics. Add a focused clone-plan regression test for a 64-fd retained prefix in a 1024-slot layout. Validated with kernel format, check, clippy, make kernel, host close_range tests, and three independent adversarial reviews. Signed-off-by: longjin <longjin@dragonos.org> * fix(vfs): track fd table task ownership Separate files-table lifetime references from PCB attachment ownership so procfs, BPF, and in-flight syscall observers cannot be mistaken for CLONE_FILES users. Introduce a lightweight FileDescriptorTable identity with an atomic task-user count and an RAII FdTableAttachment for PCB slots. Route private replacement, CLONE_FILES sharing, exec, close_range, fork cleanup, and exit through the attachment lifecycle while keeping final table destruction outside basic and fd-table locks. Use coherent table-and-sharing snapshots for exec and close_range, preserve the fallible close_range clone transaction, and add a focused ownership regression test proving observer Arcs do not affect sharing decisions. Signed-off-by: longjin <longjin@dragonos.org> * fix(vfs): harden fd table unshare semantics Track task attachments independently from transient Arc observers so close_range and exec only unshare genuinely shared descriptor tables. Keep table lifetime ownership separate from task-sharing identity and preserve POSIX lock ownership for private tables. Reuse the fallible descriptor-table clone path across close_range, fork, and exec. Preserve the allocation-before-population transaction boundary, use conditional rescheduling during range scans, and keep old table teardown outside process and fdtable locks. Harden exec after the point of no return by preparing fallible signal state early and terminating through the normal fatal-exec path when later image installation fails. Add close_range ownership and exec isolation regression coverage. Signed-off-by: longjin <longjin@dragonos.org> * fix(exec): avoid synchronous RCU sighand reclamation Retire replaced sighand references outside task_lock through a fallible RCU callback admission path so shared-sighand exec no longer waits for a global grace period during normal operation. Reserve both pending and ready callback capacity before publication to keep grace-period advancement allocation-free. Preserve the removed Arc on allocation failure and use a no-allocation yielding grace-period fallback instead of turning post-PONR memory pressure into a kernel panic. Add RCU selftests for fallible deferred drop and no-allocation grace-period progress, plus deterministic successful and post-PONR shared-sighand exec isolation coverage. Validated with kernel build, formatting, nightly clippy, DragonOS guest exec ABI tests, and multi-agent adversarial review. Signed-off-by: longjin <longjin@dragonos.org> --------- Signed-off-by: longjin <longjin@dragonos.org>
This commit is contained in:
@@ -2,10 +2,9 @@ use super::super::Result;
|
||||
use crate::bpf::map::BpfMap;
|
||||
use crate::bpf::prog::util::VerifierLogLevel;
|
||||
use crate::bpf::prog::BpfProg;
|
||||
use crate::filesystem::vfs::file::FileDescriptorVec;
|
||||
use crate::filesystem::vfs::file::FileDescriptorTable;
|
||||
use crate::include::bindings::linux_bpf::*;
|
||||
use crate::libs::casting::DowncastArc;
|
||||
use crate::libs::rwsem::RwSem;
|
||||
use alloc::{sync::Arc, vec::Vec};
|
||||
use log::{error, info};
|
||||
use rbpf::ebpf;
|
||||
@@ -33,7 +32,7 @@ impl<'a> BpfProgVerifier<'a> {
|
||||
/// Relocate the program.
|
||||
///
|
||||
/// This function will relocate the program, and update the program's instructions.
|
||||
fn relocation(&mut self, fd_table: &Arc<RwSem<FileDescriptorVec>>) -> Result<()> {
|
||||
fn relocation(&mut self, fd_table: &Arc<FileDescriptorTable>) -> Result<()> {
|
||||
let instructions = self.prog.insns_mut();
|
||||
let mut fmt_insn = to_insn_vec(instructions);
|
||||
let mut index = 0;
|
||||
@@ -124,7 +123,7 @@ impl<'a> BpfProgVerifier<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify(mut self, fd_table: &Arc<RwSem<FileDescriptorVec>>) -> Result<BpfProg> {
|
||||
pub fn verify(mut self, fd_table: &Arc<FileDescriptorTable>) -> Result<BpfProg> {
|
||||
self.relocation(fd_table)?;
|
||||
Ok(self.prog)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use core::{
|
||||
fmt,
|
||||
ops::Deref,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
@@ -2507,6 +2508,99 @@ impl ReservedFd {
|
||||
}
|
||||
}
|
||||
|
||||
/// A Linux-style files-table identity.
|
||||
///
|
||||
/// `Arc` references may also be held temporarily by procfs, BPF verification,
|
||||
/// or an in-flight syscall. `task_users` deliberately counts only PCB fd-table
|
||||
/// slots, so observers cannot make a private table look shared through
|
||||
/// `CLONE_FILES`.
|
||||
#[derive(Debug)]
|
||||
pub struct FileDescriptorTable {
|
||||
inner: RwSem<FileDescriptorVec>,
|
||||
task_users: AtomicUsize,
|
||||
}
|
||||
|
||||
impl FileDescriptorTable {
|
||||
pub fn new(inner: FileDescriptorVec) -> Self {
|
||||
Self {
|
||||
inner: RwSem::new(inner),
|
||||
task_users: AtomicUsize::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn attach_task(&self) {
|
||||
self.task_users
|
||||
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |users| {
|
||||
users.checked_add(1)
|
||||
})
|
||||
.expect("fd-table task-user count overflow");
|
||||
}
|
||||
|
||||
pub(crate) fn detach_task(&self) {
|
||||
self.task_users
|
||||
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |users| {
|
||||
users.checked_sub(1)
|
||||
})
|
||||
.expect("fd-table task-user count underflow");
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is_shared_by_tasks(&self) -> bool {
|
||||
self.task_users.load(Ordering::Acquire) > 1
|
||||
}
|
||||
|
||||
/// Fallibly clone a files table, optionally omitting an open tail covered
|
||||
/// by a `close_range(CLOSE_RANGE_UNSHARE)` operation.
|
||||
///
|
||||
/// All allocations finish while the destination contains no files. The
|
||||
/// source is then rechecked under its read guard before an infallible
|
||||
/// populate step, so ENOMEM cannot trigger close side effects for an
|
||||
/// unpublished clone.
|
||||
pub(crate) fn try_clone(
|
||||
source: &Arc<Self>,
|
||||
punch_hole: Option<(u32, u32)>,
|
||||
) -> Result<Arc<Self>, SystemError> {
|
||||
let mut target_len = source.read().clone_plan(punch_hole).target_len;
|
||||
|
||||
loop {
|
||||
let layout = FileDescriptorVec::try_allocate_empty_clone_layout(target_len)?;
|
||||
let destination = Arc::try_new(Self::new(layout)).map_err(|_| SystemError::ENOMEM)?;
|
||||
|
||||
let source_guard = source.read();
|
||||
let current_plan = source_guard.clone_plan(punch_hole);
|
||||
let capacity = destination.read().empty_layout_capacity();
|
||||
if current_plan.target_len > capacity {
|
||||
target_len = current_plan.target_len;
|
||||
drop(source_guard);
|
||||
drop(destination);
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
let mut destination_guard = destination.write();
|
||||
destination_guard.resize_empty_clone_layout(current_plan.target_len);
|
||||
source_guard.populate_clone(&mut destination_guard, current_plan.copy_len);
|
||||
}
|
||||
drop(source_guard);
|
||||
return Ok(destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for FileDescriptorTable {
|
||||
type Target = RwSem<FileDescriptorVec>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FileDescriptorTable {
|
||||
fn drop(&mut self) {
|
||||
debug_assert_eq!(self.task_users.load(Ordering::Relaxed), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief pcb里面的文件描述符数组
|
||||
#[derive(Debug)]
|
||||
pub struct FileDescriptorVec {
|
||||
@@ -2522,6 +2616,50 @@ pub struct FileDescriptorVec {
|
||||
/// 类似于 Linux 的 fd_next_fd
|
||||
next_fd: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct FdTableClonePlan {
|
||||
target_len: usize,
|
||||
copy_len: usize,
|
||||
}
|
||||
|
||||
impl FdTableClonePlan {
|
||||
fn from_retained_highest(retained_highest: Option<usize>, source_len: usize) -> Self {
|
||||
let required = retained_highest.map_or(0, |fd| fd + 1);
|
||||
Self {
|
||||
target_len: core::cmp::max(FileDescriptorVec::INITIAL_CAPACITY, required),
|
||||
// The minimum table layout must not expand the File population
|
||||
// bound. In particular, a tail punch-hole clone should never
|
||||
// clone files in the range only to close/flush them afterwards.
|
||||
copy_len: core::cmp::min(required, source_len),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod fd_table_clone_plan_tests {
|
||||
use super::{FdTableClonePlan, FileDescriptorVec};
|
||||
|
||||
#[test]
|
||||
fn minimum_layout_does_not_expand_tail_punch_copy_bound() {
|
||||
let plan = FdTableClonePlan::from_retained_highest(Some(63), 1024);
|
||||
|
||||
assert_eq!(plan.target_len, FileDescriptorVec::INITIAL_CAPACITY);
|
||||
assert_eq!(plan.copy_len, 64);
|
||||
}
|
||||
}
|
||||
|
||||
/// One bounded step of `close_range()` scanning.
|
||||
///
|
||||
/// The caller owns the fd-table write guard while this value is produced, then
|
||||
/// drops that guard before finishing `dropped` or yielding the scheduler.
|
||||
pub(crate) struct FdRangeScan {
|
||||
pub(crate) next: usize,
|
||||
pub(crate) scanned: usize,
|
||||
pub(crate) dropped: Option<DroppedFd>,
|
||||
pub(crate) done: bool,
|
||||
}
|
||||
|
||||
impl Default for FileDescriptorVec {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@@ -2554,35 +2692,80 @@ impl FileDescriptorVec {
|
||||
};
|
||||
}
|
||||
|
||||
/// @brief 克隆一个文件描述符数组
|
||||
///
|
||||
/// 语义对齐 Linux dup_fd/new files_struct:
|
||||
/// - 复制 fd 与 cloexec 状态
|
||||
/// - 分配新的 record-lock owner(POSIX 锁 owner 绑定 files table)
|
||||
/// 因此 fork 出来的新进程不会与父进程共享 POSIX record lock owner。
|
||||
///
|
||||
/// @return FileDescriptorVec 克隆后的文件描述符数组
|
||||
pub fn clone(&self) -> FileDescriptorVec {
|
||||
let mut res = FileDescriptorVec::new();
|
||||
// 调整容量以匹配源文件描述符表
|
||||
let _ = res.resize_to_capacity(self.fds.len());
|
||||
|
||||
for i in 0..self.fds.len() {
|
||||
if let Some(file) = &self.fds[i] {
|
||||
res.fds[i] = Some(file.clone());
|
||||
res.cloexec[i] = self.cloexec[i];
|
||||
fn clone_plan(&self, punch_hole: Option<(u32, u32)>) -> FdTableClonePlan {
|
||||
let highest_open = self.fds.iter().rposition(Option::is_some);
|
||||
let retained_highest = match (highest_open, punch_hole) {
|
||||
(Some(highest), Some((first, last)))
|
||||
if highest >= first as usize && highest <= last as usize =>
|
||||
{
|
||||
self.fds[..core::cmp::min(first as usize, self.fds.len())]
|
||||
.iter()
|
||||
.rposition(Option::is_some)
|
||||
}
|
||||
}
|
||||
// reserved fd 不属于已经安装的 open file description,clone 时不复制。
|
||||
// 因此 next_fd 必须按 clone 后的真实空闲槽重新计算。
|
||||
res.next_fd = res.first_available_fd_from(0).unwrap_or(res.fds.len());
|
||||
// 新 fd table 必须拥有新的 record-lock owner(对齐 Linux 新 files_struct)。
|
||||
res.lock_owner_id = alloc_lock_owner_id();
|
||||
return res;
|
||||
(highest, _) => highest,
|
||||
};
|
||||
|
||||
FdTableClonePlan::from_retained_highest(retained_highest, self.fds.len())
|
||||
}
|
||||
|
||||
fn first_available_fd_from(&self, start: usize) -> Option<usize> {
|
||||
(start..self.fds.len()).find(|&i| self.fds[i].is_none() && !self.reserved[i])
|
||||
fn try_allocate_empty_clone_layout(target_len: usize) -> Result<Self, SystemError> {
|
||||
let mut fds = Vec::new();
|
||||
let mut cloexec = Vec::new();
|
||||
let mut reserved = Vec::new();
|
||||
|
||||
fds.try_reserve_exact(target_len)
|
||||
.map_err(|_| SystemError::ENOMEM)?;
|
||||
cloexec
|
||||
.try_reserve_exact(target_len)
|
||||
.map_err(|_| SystemError::ENOMEM)?;
|
||||
reserved
|
||||
.try_reserve_exact(target_len)
|
||||
.map_err(|_| SystemError::ENOMEM)?;
|
||||
|
||||
fds.resize(target_len, None);
|
||||
cloexec.resize(target_len, false);
|
||||
reserved.resize(target_len, false);
|
||||
|
||||
Ok(Self {
|
||||
fds,
|
||||
cloexec,
|
||||
reserved,
|
||||
lock_owner_id: alloc_lock_owner_id(),
|
||||
next_fd: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn empty_layout_capacity(&self) -> usize {
|
||||
debug_assert!(self.fds.iter().all(Option::is_none));
|
||||
debug_assert!(self.cloexec.iter().all(|flag| !flag));
|
||||
debug_assert!(self.reserved.iter().all(|reserved| !reserved));
|
||||
self.fds
|
||||
.capacity()
|
||||
.min(self.cloexec.capacity())
|
||||
.min(self.reserved.capacity())
|
||||
}
|
||||
|
||||
fn resize_empty_clone_layout(&mut self, target_len: usize) {
|
||||
debug_assert!(target_len <= self.empty_layout_capacity());
|
||||
self.fds.resize(target_len, None);
|
||||
self.cloexec.resize(target_len, false);
|
||||
self.reserved.resize(target_len, false);
|
||||
}
|
||||
|
||||
fn populate_clone(&self, destination: &mut Self, copy_len: usize) {
|
||||
debug_assert!(destination.fds.iter().all(Option::is_none));
|
||||
let copy_len = copy_len.min(self.fds.len()).min(destination.fds.len());
|
||||
let mut next_fd = None;
|
||||
for index in 0..copy_len {
|
||||
if let Some(file) = &self.fds[index] {
|
||||
destination.fds[index] = Some(file.clone());
|
||||
destination.cloexec[index] = self.cloexec[index];
|
||||
} else if next_fd.is_none() {
|
||||
next_fd = Some(index);
|
||||
}
|
||||
}
|
||||
// A reserved slot belongs to the old files table and is never copied.
|
||||
destination.next_fd = next_fd.unwrap_or(copy_len);
|
||||
}
|
||||
|
||||
/// 返回当前已占用的最高文件描述符索引(若无则为None)
|
||||
@@ -2976,6 +3159,79 @@ impl FileDescriptorVec {
|
||||
return Ok(DroppedFd::new(file, self.lock_owner_id));
|
||||
}
|
||||
|
||||
/// Return the last currently addressable fd in an inclusive range.
|
||||
pub(crate) fn close_range_end(&self, last: u32) -> Option<usize> {
|
||||
self.fds
|
||||
.len()
|
||||
.checked_sub(1)
|
||||
.map(|table_end| core::cmp::min(last as usize, table_end))
|
||||
}
|
||||
|
||||
/// Scan a bounded portion of an inclusive fd range and detach the next
|
||||
/// installed file, if any.
|
||||
///
|
||||
/// Empty and reserved slots are skipped without changing their ownership.
|
||||
/// The caller must finish the returned `DroppedFd` after releasing the
|
||||
/// fd-table guard.
|
||||
pub(crate) fn take_next_open_in_range(
|
||||
&mut self,
|
||||
cursor: usize,
|
||||
end: usize,
|
||||
scan_budget: usize,
|
||||
) -> FdRangeScan {
|
||||
debug_assert!(scan_budget > 0);
|
||||
if cursor > end || cursor >= self.fds.len() {
|
||||
return FdRangeScan {
|
||||
next: cursor,
|
||||
scanned: 0,
|
||||
dropped: None,
|
||||
done: true,
|
||||
};
|
||||
}
|
||||
|
||||
let effective_end = end.min(self.fds.len() - 1);
|
||||
let scan_end = effective_end.min(cursor.saturating_add(scan_budget - 1));
|
||||
for index in cursor..=scan_end {
|
||||
if let Some(file) = self.fds[index].take() {
|
||||
self.cloexec[index] = false;
|
||||
if index < self.next_fd {
|
||||
self.next_fd = index;
|
||||
}
|
||||
let next = index + 1;
|
||||
return FdRangeScan {
|
||||
next,
|
||||
scanned: next - cursor,
|
||||
dropped: Some(DroppedFd::new(file, self.lock_owner_id)),
|
||||
done: next > end,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let next = scan_end + 1;
|
||||
FdRangeScan {
|
||||
next,
|
||||
scanned: next - cursor,
|
||||
dropped: None,
|
||||
done: next > end || next >= self.fds.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set `FD_CLOEXEC` for every fd slot in an inclusive range.
|
||||
///
|
||||
/// Reserved slots are deliberately included so a later fd installation
|
||||
/// preserves the flag. A future allocation into an ordinary empty slot
|
||||
/// overwrites it with that allocation's requested cloexec state.
|
||||
pub(crate) fn set_cloexec_range(&mut self, first: u32, last: u32) {
|
||||
let Some(end) = self.close_range_end(last) else {
|
||||
return;
|
||||
};
|
||||
let first = first as usize;
|
||||
if first > end {
|
||||
return;
|
||||
}
|
||||
self.cloexec[first..=end].fill(true);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn iter(&self) -> FileDescriptorIterator<'_> {
|
||||
return FileDescriptorIterator::new(self);
|
||||
|
||||
@@ -12,6 +12,7 @@ mod rename_utils;
|
||||
mod sys_chdir;
|
||||
mod sys_chroot;
|
||||
mod sys_close;
|
||||
mod sys_close_range;
|
||||
mod sys_dup;
|
||||
mod sys_dup3;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
//! Linux-compatible `close_range(2)` implementation.
|
||||
|
||||
use alloc::{string::ToString, sync::Arc, vec::Vec};
|
||||
|
||||
use system_error::SystemError;
|
||||
|
||||
use crate::{
|
||||
arch::{interrupt::TrapFrame, syscall::nr::SYS_CLOSE_RANGE},
|
||||
filesystem::vfs::file::FileDescriptorTable,
|
||||
process::ProcessManager,
|
||||
sched::cond_resched,
|
||||
syscall::table::{FormattedSyscallParam, Syscall},
|
||||
};
|
||||
|
||||
bitflags! {
|
||||
struct CloseRangeFlags: u32 {
|
||||
const UNSHARE = 1 << 1;
|
||||
const CLOEXEC = 1 << 2;
|
||||
}
|
||||
}
|
||||
|
||||
/// Keep the fd-table write lock and the current task on-CPU for at most this
|
||||
/// many scanned slots before a voluntary scheduling point.
|
||||
const CLOSE_RANGE_WORK_BUDGET: usize = 256;
|
||||
|
||||
pub struct SysCloseRangeHandle;
|
||||
|
||||
impl Syscall for SysCloseRangeHandle {
|
||||
fn num_args(&self) -> usize {
|
||||
3
|
||||
}
|
||||
|
||||
fn handle(&self, args: &[usize], _frame: &mut TrapFrame) -> Result<usize, SystemError> {
|
||||
do_close_range(args[0] as u32, args[1] as u32, args[2] as u32)
|
||||
}
|
||||
|
||||
fn entry_format(&self, args: &[usize]) -> Vec<FormattedSyscallParam> {
|
||||
vec![
|
||||
FormattedSyscallParam::new("first", (args[0] as u32).to_string()),
|
||||
FormattedSyscallParam::new("last", (args[1] as u32).to_string()),
|
||||
FormattedSyscallParam::new("flags", alloc::format!("{:#x}", args[2] as u32)),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
syscall_table_macros::declare_syscall!(SYS_CLOSE_RANGE, SysCloseRangeHandle);
|
||||
|
||||
fn close_range_in_table(table: &Arc<FileDescriptorTable>, first: u32, last: u32) {
|
||||
let Some(end) = table.read().close_range_end(last) else {
|
||||
return;
|
||||
};
|
||||
let mut cursor = first as usize;
|
||||
if cursor > end {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut work = 0usize;
|
||||
loop {
|
||||
let remaining_budget = CLOSE_RANGE_WORK_BUDGET - work;
|
||||
let scan = table
|
||||
.write()
|
||||
.take_next_open_in_range(cursor, end, remaining_budget.max(1));
|
||||
cursor = scan.next;
|
||||
work += scan.scanned;
|
||||
|
||||
if let Some(dropped) = scan.dropped {
|
||||
// Linux ignores individual filp_close() errors in __range_close().
|
||||
let _ = dropped.finish_close();
|
||||
cond_resched();
|
||||
// The fixed budget only covers a continuous run of empty slots;
|
||||
// real close work gets its own reschedule check above.
|
||||
work = 0;
|
||||
}
|
||||
|
||||
if scan.done {
|
||||
break;
|
||||
}
|
||||
if work >= CLOSE_RANGE_WORK_BUDGET {
|
||||
cond_resched();
|
||||
work = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cloexec_in_table(table: &Arc<FileDescriptorTable>, first: u32, last: u32) {
|
||||
table.write().set_cloexec_range(first, last);
|
||||
}
|
||||
|
||||
fn do_close_range(first: u32, last: u32, flags: u32) -> Result<usize, SystemError> {
|
||||
let flags = CloseRangeFlags::from_bits(flags).ok_or(SystemError::EINVAL)?;
|
||||
if first > last {
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
|
||||
let current = ProcessManager::current_pcb();
|
||||
let (old_table, shared_by_tasks) = current
|
||||
.basic()
|
||||
.fd_table_snapshot()
|
||||
.expect("close_range task has no fd table");
|
||||
let must_unshare = flags.contains(CloseRangeFlags::UNSHARE) && shared_by_tasks;
|
||||
|
||||
if must_unshare {
|
||||
let punch_hole = if flags.contains(CloseRangeFlags::CLOEXEC) {
|
||||
None
|
||||
} else {
|
||||
Some((first, last))
|
||||
};
|
||||
let new_table = FileDescriptorTable::try_clone(&old_table, punch_hole)?;
|
||||
|
||||
if flags.contains(CloseRangeFlags::CLOEXEC) {
|
||||
set_cloexec_in_table(&new_table, first, last);
|
||||
} else {
|
||||
close_range_in_table(&new_table, first, last);
|
||||
}
|
||||
|
||||
let replaced = {
|
||||
let mut basic = current.basic_mut();
|
||||
basic.set_fd_table(Some(new_table))
|
||||
};
|
||||
// A final fd-table drop can flush files. Never do it under basic/fdtable locks.
|
||||
drop(replaced);
|
||||
drop(old_table);
|
||||
} else if flags.contains(CloseRangeFlags::CLOEXEC) {
|
||||
set_cloexec_in_table(&old_table, first, last);
|
||||
} else {
|
||||
close_range_in_table(&old_table, first, last);
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
@@ -119,12 +119,18 @@ pub struct InnerSigHand {
|
||||
|
||||
impl SigHand {
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
inner: RwLock::new(InnerSigHand::default()),
|
||||
Self::try_new().expect("failed to allocate signal-handler table")
|
||||
}
|
||||
|
||||
pub fn try_new() -> Result<Arc<Self>, SystemError> {
|
||||
let inner = InnerSigHand::try_default()?;
|
||||
Arc::try_new(Self {
|
||||
inner: RwLock::new(inner),
|
||||
group_exec_wait_queue: WaitQueue::default(),
|
||||
signalfd_wqh: WaitQueue::default(),
|
||||
signalfd_epitems: EPollItemList::default(),
|
||||
})
|
||||
.map_err(|_| SystemError::ENOMEM)
|
||||
}
|
||||
|
||||
fn inner(&self) -> RwLockReadGuard<'_, InnerSigHand> {
|
||||
@@ -231,7 +237,8 @@ impl SigHand {
|
||||
pub fn copy_handlers_from(&self, other: &Arc<SigHand>) {
|
||||
let other_guard = other.inner();
|
||||
let mut self_guard = self.inner_mut();
|
||||
self_guard.handlers = other_guard.handlers.clone();
|
||||
assert_eq!(self_guard.handlers.len(), other_guard.handlers.len());
|
||||
self_guard.handlers.clone_from_slice(&other_guard.handlers);
|
||||
}
|
||||
|
||||
pub fn copy_process_state_from(&self, other: &Arc<SigHand>) {
|
||||
@@ -937,8 +944,14 @@ impl SigHand {
|
||||
|
||||
impl Default for InnerSigHand {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
handlers: default_sighandlers(),
|
||||
Self::try_default().expect("failed to allocate signal actions")
|
||||
}
|
||||
}
|
||||
|
||||
impl InnerSigHand {
|
||||
fn try_default() -> Result<Self, SystemError> {
|
||||
Ok(Self {
|
||||
handlers: try_default_sighandlers()?,
|
||||
pids: core::array::from_fn(|_| None),
|
||||
shared_pending: SigPending::default(),
|
||||
curr_target: None,
|
||||
@@ -954,12 +967,19 @@ impl Default for InnerSigHand {
|
||||
oom_mm_id: None,
|
||||
oom_mm: None,
|
||||
cnt: 0,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn default_sighandlers() -> Vec<Sigaction> {
|
||||
let mut r = vec![Sigaction::default(); MAX_SIG_NUM];
|
||||
try_default_sighandlers().expect("failed to allocate signal actions")
|
||||
}
|
||||
|
||||
fn try_default_sighandlers() -> Result<Vec<Sigaction>, SystemError> {
|
||||
let mut r = Vec::new();
|
||||
r.try_reserve_exact(MAX_SIG_NUM)
|
||||
.map_err(|_| SystemError::ENOMEM)?;
|
||||
r.resize(MAX_SIG_NUM, Sigaction::default());
|
||||
let mut sig_ign = Sigaction::default();
|
||||
// 收到忽略的信号,重启系统调用
|
||||
// Linux ignores SIGURG/SIGWINCH by default; SIGCHLD is also ignored by default,
|
||||
@@ -970,7 +990,7 @@ fn default_sighandlers() -> Vec<Sigaction> {
|
||||
r[Signal::SIGURG as usize - 1] = sig_ign;
|
||||
r[Signal::SIGWINCH as usize - 1] = sig_ign;
|
||||
|
||||
r
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
impl ProcessControlBlock {
|
||||
|
||||
@@ -898,7 +898,6 @@ impl BinaryLoader for ElfLoader {
|
||||
// debug!("ehdr = {:?}", ehdr);
|
||||
|
||||
let binding = param.vm().clone();
|
||||
let mut user_vm = binding.write();
|
||||
|
||||
// todo: 增加对user stack上的内存是否具有可执行权限的处理(方法:寻找phdr里面的PT_GNU_STACK段)
|
||||
|
||||
@@ -980,6 +979,10 @@ impl BinaryLoader for ElfLoader {
|
||||
Self::parse_gnu_property()?;
|
||||
|
||||
param.begin_new_exec()?;
|
||||
// begin_new_exec() may de-thread, clone/install the files table and
|
||||
// drop old table references. None of those operations may run while an
|
||||
// address-space write guard is held.
|
||||
let mut user_vm = binding.write();
|
||||
|
||||
// todo: 补充逻辑:https://code.dragonos.org.cn/xref/linux-6.6.21/fs/binfmt_elf.c#1007
|
||||
param.setup_new_exec();
|
||||
|
||||
+67
-20
@@ -6,8 +6,12 @@ use system_error::SystemError;
|
||||
use crate::{
|
||||
arch::ipc::signal::Signal,
|
||||
driver::base::block::SeekFrom,
|
||||
filesystem::vfs::{fcntl::AtFlags, file::File, open::do_open_execat},
|
||||
ipc::sighand::GroupExecCancelResult,
|
||||
filesystem::vfs::{
|
||||
fcntl::AtFlags,
|
||||
file::{File, FileDescriptorTable},
|
||||
open::do_open_execat,
|
||||
},
|
||||
ipc::sighand::{GroupExecCancelResult, SigHand},
|
||||
libs::elf::ELF_LOADER,
|
||||
mm::{
|
||||
ucontext::{AddressSpace, UserStack},
|
||||
@@ -208,6 +212,9 @@ pub struct ExecParam {
|
||||
/// Information used to initialize the process. Filled jointly by the binary
|
||||
/// loader and the exec mechanism.
|
||||
init_info: ProcInitInfo,
|
||||
/// Whether exec has crossed the point where the old program may no longer
|
||||
/// resume. Errors after this point must terminate the current task.
|
||||
point_of_no_return: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
@@ -244,6 +251,7 @@ impl ExecParam {
|
||||
execfn,
|
||||
interp_flags,
|
||||
init_info,
|
||||
point_of_no_return: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +271,10 @@ impl ExecParam {
|
||||
&mut self.init_info
|
||||
}
|
||||
|
||||
pub fn point_of_no_return(&self) -> bool {
|
||||
self.point_of_no_return
|
||||
}
|
||||
|
||||
/// Returns the load mode.
|
||||
pub fn load_mode(&self) -> ExecLoadMode {
|
||||
if self.flags.contains(ExecParamFlags::EXEC) {
|
||||
@@ -309,7 +321,32 @@ impl ExecParam {
|
||||
pub fn begin_new_exec(&mut self) -> Result<(), ExecError> {
|
||||
let me = ProcessManager::current_pcb();
|
||||
// TODO: Implement the remaining Linux logic.
|
||||
de_thread(&me).map_err(ExecError::SystemError)?;
|
||||
de_thread(&me, &mut self.point_of_no_return).map_err(ExecError::SystemError)?;
|
||||
// A successful de_thread has committed the exec transaction even when
|
||||
// there were no sibling threads to signal.
|
||||
self.point_of_no_return = true;
|
||||
|
||||
if me.sighand().is_shared() {
|
||||
let old_sighand = me.sighand();
|
||||
let new_sighand = SigHand::try_new().map_err(ExecError::SystemError)?;
|
||||
new_sighand.copy_handlers_from(&old_sighand);
|
||||
new_sighand.copy_process_state_from(&old_sighand);
|
||||
me.replace_sighand(new_sighand);
|
||||
}
|
||||
|
||||
let (fd_table, shared) = me
|
||||
.basic()
|
||||
.fd_table_snapshot()
|
||||
.expect("exec task has no fd table");
|
||||
if shared {
|
||||
let private =
|
||||
FileDescriptorTable::try_clone(&fd_table, None).map_err(ExecError::SystemError)?;
|
||||
let replaced = me.basic_mut().set_fd_table(Some(private));
|
||||
// Dropping a final table reference can flush files. Never do that
|
||||
// while holding the PCB basic-info or an fd-table guard.
|
||||
drop(replaced);
|
||||
}
|
||||
drop(fd_table);
|
||||
|
||||
me.flags().remove(ProcessFlags::FORKNOEXEC);
|
||||
|
||||
@@ -324,7 +361,10 @@ impl ExecParam {
|
||||
}
|
||||
|
||||
/// https://code.dragonos.org.cn/xref/linux-6.6.21/fs/exec.c#1044
|
||||
fn de_thread(pcb: &Arc<ProcessControlBlock>) -> Result<(), SystemError> {
|
||||
fn de_thread(
|
||||
pcb: &Arc<ProcessControlBlock>,
|
||||
point_of_no_return: &mut bool,
|
||||
) -> Result<(), SystemError> {
|
||||
let current = ProcessManager::current_pcb();
|
||||
if !Arc::ptr_eq(¤t, pcb) {
|
||||
return Err(SystemError::EINVAL);
|
||||
@@ -401,6 +441,13 @@ fn de_thread(pcb: &Arc<ProcessControlBlock>) -> Result<(), SystemError> {
|
||||
}
|
||||
}
|
||||
|
||||
// From this point the published group-exec transaction may be observed by
|
||||
// the old leader, and the loop below can queue irreversible SIGKILLs. Any
|
||||
// later error must terminate the current task instead of resuming the old
|
||||
// program. Transaction-acquisition and invalid-leader failures above are
|
||||
// still fully cancelable and remain ordinary exec errors.
|
||||
*point_of_no_return = true;
|
||||
|
||||
for task in kill_list {
|
||||
Signal::queue_private_sigkill_to_thread(&task);
|
||||
}
|
||||
@@ -743,24 +790,24 @@ impl ProcInitInfo {
|
||||
self.push_str(ustack, &self.proc_name)?;
|
||||
|
||||
// Then push the environment variables onto the stack.
|
||||
let envps = self
|
||||
.envs
|
||||
.iter()
|
||||
.map(|s| {
|
||||
self.push_str(ustack, s).expect("push_str failed");
|
||||
ustack.sp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut envps = Vec::new();
|
||||
envps
|
||||
.try_reserve_exact(self.envs.len())
|
||||
.map_err(|_| SystemError::ENOMEM)?;
|
||||
for env in &self.envs {
|
||||
self.push_str(ustack, env)?;
|
||||
envps.push(ustack.sp());
|
||||
}
|
||||
|
||||
// Then push the arguments onto the stack.
|
||||
let argps = self
|
||||
.args
|
||||
.iter()
|
||||
.map(|s| {
|
||||
self.push_str(ustack, s).expect("push_str failed");
|
||||
ustack.sp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut argps = Vec::new();
|
||||
argps
|
||||
.try_reserve_exact(self.args.len())
|
||||
.map_err(|_| SystemError::ENOMEM)?;
|
||||
for arg in &self.args {
|
||||
self.push_str(ustack, arg)?;
|
||||
argps.push(ustack.sp());
|
||||
}
|
||||
|
||||
// Push the random number and store its pointer in auxv.
|
||||
self.push_slice(ustack, &[self.rand_num])?;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use super::trace::trace_sched_process_exec;
|
||||
use crate::arch::ipc::signal::Signal;
|
||||
use crate::arch::CurrentIrqArch;
|
||||
use crate::exception::InterruptArch;
|
||||
use crate::filesystem::vfs::fcntl::AtFlags;
|
||||
use crate::filesystem::vfs::open::{do_open_execat, do_open_execat_with_flags};
|
||||
use crate::libs::futex::futex::RobustListHead;
|
||||
use crate::libs::rwsem::RwSem;
|
||||
use crate::process::exec::{
|
||||
load_binary_file_with_context, ExecContext, ExecInterpFlags, ExecParam, ExecParamFlags,
|
||||
ExecStartInfo, LoadBinaryResult,
|
||||
@@ -17,6 +17,20 @@ use crate::arch::interrupt::TrapFrame;
|
||||
use alloc::{ffi::CString, string::String, sync::Arc, vec::Vec};
|
||||
use system_error::SystemError;
|
||||
|
||||
struct ExecFailure {
|
||||
error: SystemError,
|
||||
post_point_of_no_return: bool,
|
||||
}
|
||||
|
||||
impl From<SystemError> for ExecFailure {
|
||||
fn from(error: SystemError) -> Self {
|
||||
Self {
|
||||
error,
|
||||
post_point_of_no_return: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 执行execve系统调用
|
||||
///
|
||||
/// ## 参数
|
||||
@@ -64,7 +78,22 @@ pub fn do_execve_with_info(
|
||||
envp: Vec<CString>,
|
||||
regs: &mut TrapFrame,
|
||||
) -> Result<(), SystemError> {
|
||||
do_execve_internal(start, argv, envp, regs, ExecContext::new())
|
||||
match do_execve_internal(start, argv, envp, regs, ExecContext::new()) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(failure) => {
|
||||
if failure.post_point_of_no_return {
|
||||
// The recursive loader stack has returned, so its ExecParam,
|
||||
// File, address-space and argv/envp owners are gone before any
|
||||
// divergent exit. Recheck here rather than caching the signal
|
||||
// state before unwind, so a concurrent SIGKILL keeps priority.
|
||||
let current = ProcessManager::current_pcb();
|
||||
if !Signal::fatal_signal_pending(¤t) {
|
||||
ProcessManager::exit(Signal::SIGSEGV as usize);
|
||||
}
|
||||
}
|
||||
Err(failure.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exec_visible_name(dirfd: i32, path: &str) -> String {
|
||||
@@ -112,7 +141,7 @@ fn do_execve_internal(
|
||||
envp: Vec<CString>,
|
||||
regs: &mut TrapFrame,
|
||||
ctx: ExecContext,
|
||||
) -> Result<(), SystemError> {
|
||||
) -> Result<(), ExecFailure> {
|
||||
let address_space = AddressSpace::new(true).expect("Failed to create new address space");
|
||||
|
||||
let mut param = ExecParam::new(
|
||||
@@ -152,11 +181,10 @@ fn do_execve_internal(
|
||||
.expect("No user stack found")
|
||||
.clone_info_only()
|
||||
};
|
||||
let (user_sp, argv_ptr) = unsafe {
|
||||
param
|
||||
.init_info_mut()
|
||||
.push_at(&mut ustack_message)
|
||||
.expect("Failed to push proc_init_info to user stack")
|
||||
let stack_result = unsafe { param.init_info_mut().push_at(&mut ustack_message) };
|
||||
let (user_sp, argv_ptr) = match stack_result {
|
||||
Ok(stack) => stack,
|
||||
Err(err) => return finish_exec_error(¶m, old_vm.as_ref(), err),
|
||||
};
|
||||
address_space.write().user_stack = Some(ustack_message);
|
||||
|
||||
@@ -164,22 +192,6 @@ fn do_execve_internal(
|
||||
|
||||
commit_exec_robust_list(&pcb, old_vm.as_ref(), &address_space);
|
||||
|
||||
// unshare fd_table if it's shared (CLONE_FILES case)
|
||||
// 参考 Linux: https://elixir.bootlin.com/linux/v6.1.9/source/fs/exec.c#L1857
|
||||
// "Ensure the files table is not shared"
|
||||
{
|
||||
// 注意:不能先调用 pcb.fd_table() 再判断 strong_count,
|
||||
// 因为 fd_table() 会克隆 Arc,导致计数至少 +1,误判为“被共享”。
|
||||
let need_unshare = pcb.basic().fd_table_is_shared();
|
||||
if need_unshare {
|
||||
// fd_table 被共享,需要创建私有副本
|
||||
let fd_table = pcb.fd_table();
|
||||
let new_fd_table = fd_table.read().clone();
|
||||
let new_fd_table = Arc::new(RwSem::new(new_fd_table));
|
||||
pcb.basic_mut().set_fd_table(Some(new_fd_table));
|
||||
}
|
||||
}
|
||||
|
||||
// close-on-exec 必须属于成功 exec 的 commit 过程,不能留在 syscall wrapper 尾部。
|
||||
let dropped_fds = {
|
||||
let fd_table = pcb.fd_table();
|
||||
@@ -192,15 +204,6 @@ fn do_execve_internal(
|
||||
}
|
||||
}
|
||||
|
||||
if pcb.sighand().is_shared() {
|
||||
// Linux出于进程和线程隔离,要确保在execve时,对共享的 SigHand 进行深拷贝
|
||||
// 参考 https://code.dragonos.org.cn/xref/linux-6.6.21/fs/exec.c#1187
|
||||
let old_sighand = pcb.sighand();
|
||||
let new_sighand = crate::ipc::sighand::SigHand::new();
|
||||
new_sighand.copy_handlers_from(&old_sighand);
|
||||
new_sighand.copy_process_state_from(&old_sighand);
|
||||
pcb.replace_sighand(new_sighand);
|
||||
}
|
||||
// 重置所有信号处理器为默认行为(SIG_DFL),禁用并清空备用信号栈。
|
||||
pcb.flush_signal_handlers(false);
|
||||
*pcb.sig_altstack_mut() = crate::arch::SigStackArch::new();
|
||||
@@ -220,30 +223,21 @@ fn do_execve_internal(
|
||||
|
||||
// vfork 父进程必须在 child 完成 exec commit 后再恢复。
|
||||
// 否则父子仍可能共享 files_struct,child 的 close_on_exec() 会污染父进程。
|
||||
let vfork_done = pcb.thread.write_irqsave().vfork_done.take();
|
||||
let exec_ret = Syscall::arch_do_execve(regs, ¶m, &result, user_sp, argv_ptr);
|
||||
if exec_ret.is_ok() {
|
||||
// Thread4:先 complete vfork parent,再触发 trace。
|
||||
// 对齐 Linux:exec_mmap()/exec_mm_release() 内更早完成 vfork_done,
|
||||
// 而 trace_sched_process_exec() 在 exec 成功提交尾部才执行。若先 trace
|
||||
// 再 complete,父进程会被所有 tracepoint 回调阻塞,违反“child 完成 exec
|
||||
// commit 即恢复父进程”的语义,给 exec 热路径引入不必要的 trace 回调延迟。
|
||||
if let Some(completion) = vfork_done {
|
||||
completion.complete_all();
|
||||
}
|
||||
|
||||
// Linux keeps bprm->filename as the original exec-visible name
|
||||
// across shebang/interpreter rewrites. DragonOS's execfn has the
|
||||
// same lifetime and meaning; filename tracks the current loader.
|
||||
// All dynamic sizing/allocation remains behind the macro's static
|
||||
// branch, while these borrowed bytes and integers are O(1).
|
||||
trace_sched_process_exec(
|
||||
param.execfn().as_bytes(),
|
||||
pcb.raw_pid().data() as i32,
|
||||
old_pid,
|
||||
);
|
||||
if let Err(err) = Syscall::arch_do_execve(regs, ¶m, &result, user_sp, argv_ptr) {
|
||||
return finish_exec_error(¶m, old_vm.as_ref(), err);
|
||||
}
|
||||
exec_ret
|
||||
if let Some(completion) = pcb.thread.write_irqsave().vfork_done.take() {
|
||||
completion.complete_all();
|
||||
}
|
||||
// Linux keeps bprm->filename as the original exec-visible name
|
||||
// across shebang/interpreter rewrites. Complete vfork first so
|
||||
// trace callbacks cannot delay the parent after exec committed.
|
||||
trace_sched_process_exec(
|
||||
param.execfn().as_bytes(),
|
||||
pcb.raw_pid().data() as i32,
|
||||
old_pid,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Ok(LoadBinaryResult::NeedReexec { next, new_argv }) => {
|
||||
@@ -259,16 +253,31 @@ fn do_execve_internal(
|
||||
do_execve_internal(next, new_argv, envp, regs, new_ctx)
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
// 加载失败,恢复旧的地址空间
|
||||
if let Some(old_vm) = old_vm {
|
||||
do_execve_switch_user_vm(old_vm);
|
||||
}
|
||||
Err(e)
|
||||
}
|
||||
Err(e) => finish_exec_error(¶m, old_vm.as_ref(), e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Finish an exec error according to the point-of-no-return boundary.
|
||||
///
|
||||
/// DragonOS switches to the prospective address space before loading the
|
||||
/// binary, unlike Linux. Switch back to the old address space so fatal exit can
|
||||
/// clean the old robust list and clear_child_tid. A post-PONR error must still
|
||||
/// never resume the old userspace image.
|
||||
fn finish_exec_error(
|
||||
param: &ExecParam,
|
||||
old_vm: Option<&Arc<AddressSpace>>,
|
||||
error: SystemError,
|
||||
) -> Result<(), ExecFailure> {
|
||||
if let Some(old_vm) = old_vm {
|
||||
do_execve_switch_user_vm(old_vm.clone());
|
||||
}
|
||||
|
||||
Err(ExecFailure {
|
||||
error,
|
||||
post_point_of_no_return: param.point_of_no_return(),
|
||||
})
|
||||
}
|
||||
|
||||
/// 切换用户虚拟内存空间
|
||||
///
|
||||
/// 该函数用于在执行系统调用 `execve` 时切换用户进程的虚拟内存空间。
|
||||
|
||||
+16
-16
@@ -4,9 +4,7 @@ use core::sync::atomic::Ordering;
|
||||
use crate::arch::MMArch;
|
||||
use crate::cgroup::{cgroup_accounting_lock, cgroup_can_fork_in, cgroup_migrate_vet_dst_with_src};
|
||||
use crate::filesystem::cgroup2::{cgroup2_check_attach_permissions, cgroup2_inode_to_node};
|
||||
use crate::filesystem::vfs::file::File;
|
||||
use crate::filesystem::vfs::file::FileFlags;
|
||||
use crate::filesystem::vfs::file::ReservedFd;
|
||||
use crate::filesystem::vfs::file::{File, FileDescriptorTable, FileFlags, ReservedFd};
|
||||
use crate::filesystem::vfs::FileType;
|
||||
use crate::mm::access_ok;
|
||||
use crate::mm::MemoryManagementArch;
|
||||
@@ -18,7 +16,7 @@ use system_error::SystemError;
|
||||
use crate::{
|
||||
arch::{interrupt::TrapFrame, ipc::signal::Signal},
|
||||
ipc::signal_types::SignalFlags,
|
||||
libs::{cpumask::CpuMask, rwsem::RwSem},
|
||||
libs::cpumask::CpuMask,
|
||||
mm::VirtAddr,
|
||||
process::ProcessFlags,
|
||||
sched::{cpu_is_online, sched_cgroup_fork, sched_fork},
|
||||
@@ -383,14 +381,21 @@ impl ProcessManager {
|
||||
) -> Result<(), SystemError> {
|
||||
// 如果不共享文件描述符表,则拷贝文件描述符表
|
||||
if !clone_flags.contains(CloneFlags::CLONE_FILES) {
|
||||
let new_fd_table = current_pcb.basic().try_fd_table().unwrap().read().clone();
|
||||
let new_fd_table = Arc::new(RwSem::new(new_fd_table));
|
||||
new_pcb.basic_mut().set_fd_table(Some(new_fd_table));
|
||||
let source = current_pcb
|
||||
.basic()
|
||||
.try_fd_table()
|
||||
.expect("fork parent has no fd table");
|
||||
let new_fd_table = FileDescriptorTable::try_clone(&source, None)?;
|
||||
let replaced = new_pcb.basic_mut().set_fd_table(Some(new_fd_table));
|
||||
drop(replaced);
|
||||
} else {
|
||||
// 如果共享文件描述符表,则直接拷贝指针
|
||||
new_pcb
|
||||
.basic_mut()
|
||||
.set_fd_table(current_pcb.basic().try_fd_table().clone());
|
||||
let shared = current_pcb
|
||||
.basic()
|
||||
.share_fd_table_for_task()
|
||||
.expect("fork parent has no fd table");
|
||||
let replaced = new_pcb.basic_mut().set_fd_table_attachment(Some(shared));
|
||||
drop(replaced);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
@@ -711,12 +716,7 @@ impl ProcessManager {
|
||||
);
|
||||
|
||||
// 拷贝文件描述符表
|
||||
Self::copy_files(&clone_flags, current_pcb, pcb).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"fork: Failed to copy files from current process, current pid: [{:?}], new pid: [{:?}]. Error: {:?}",
|
||||
current_pcb.raw_pid(), pcb.raw_pid(), e
|
||||
)
|
||||
});
|
||||
Self::copy_files(&clone_flags, current_pcb, pcb)?;
|
||||
|
||||
// 拷贝信号相关数据
|
||||
Self::copy_sighand(&clone_flags, current_pcb, pcb).unwrap_or_else(|e| {
|
||||
|
||||
+105
-14
@@ -8,9 +8,9 @@ use core::sync::atomic::AtomicUsize;
|
||||
use crate::{
|
||||
arch::ipc::signal::{SigSet, Signal},
|
||||
driver::tty::tty_core::TtyCore,
|
||||
filesystem::vfs::file::FileDescriptorVec,
|
||||
filesystem::vfs::file::{FileDescriptorTable, FileDescriptorVec},
|
||||
ipc::signal_types::{SigInfo, SigPending},
|
||||
libs::{rwlock::RwLock, rwsem::RwSem},
|
||||
libs::rwlock::RwLock,
|
||||
mm::{ucontext::AddressSpace, VirtAddr},
|
||||
process::{ProcessControlBlock, ProcessManager, RawPid},
|
||||
sched::completion::Completion,
|
||||
@@ -133,7 +133,61 @@ pub struct ProcessBasicInfo {
|
||||
user_vm: Option<Arc<AddressSpace>>,
|
||||
|
||||
/// File descriptor table.
|
||||
fd_table: Option<Arc<RwSem<FileDescriptorVec>>>,
|
||||
fd_table: Option<FdTableAttachment>,
|
||||
}
|
||||
|
||||
/// A PCB slot's ownership of a files table.
|
||||
///
|
||||
/// This type intentionally does not implement `Clone`: only
|
||||
/// `share_for_task()` may create another task attachment. Ordinary table
|
||||
/// observers receive only an `Arc<FileDescriptorTable>`.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FdTableAttachment {
|
||||
table: Option<Arc<FileDescriptorTable>>,
|
||||
}
|
||||
|
||||
impl FdTableAttachment {
|
||||
fn new(table: Arc<FileDescriptorTable>) -> Self {
|
||||
table.attach_task();
|
||||
Self { table: Some(table) }
|
||||
}
|
||||
|
||||
fn share_for_task(&self) -> Self {
|
||||
Self::new(
|
||||
self.table
|
||||
.as_ref()
|
||||
.expect("retired fd-table attachment")
|
||||
.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn observer(&self) -> Arc<FileDescriptorTable> {
|
||||
self.table
|
||||
.as_ref()
|
||||
.expect("retired fd-table attachment")
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn is_shared(&self) -> bool {
|
||||
self.table
|
||||
.as_ref()
|
||||
.expect("retired fd-table attachment")
|
||||
.is_shared_by_tasks()
|
||||
}
|
||||
|
||||
fn retire(mut self) -> Arc<FileDescriptorTable> {
|
||||
let table = self.table.take().expect("retired fd-table attachment");
|
||||
table.detach_task();
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FdTableAttachment {
|
||||
fn drop(&mut self) {
|
||||
if let Some(table) = self.table.take() {
|
||||
table.detach_task();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcessBasicInfo {
|
||||
@@ -144,13 +198,13 @@ impl ProcessBasicInfo {
|
||||
cwd: String,
|
||||
user_vm: Option<Arc<AddressSpace>>,
|
||||
) -> RwLock<Self> {
|
||||
let fd_table = Arc::new(RwSem::new(FileDescriptorVec::new()));
|
||||
let fd_table = Arc::new(FileDescriptorTable::new(FileDescriptorVec::new()));
|
||||
return RwLock::new(Self {
|
||||
ppid,
|
||||
name,
|
||||
cwd,
|
||||
user_vm,
|
||||
fd_table: Some(fd_table),
|
||||
fd_table: Some(FdTableAttachment::new(fd_table)),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -193,25 +247,62 @@ impl ProcessBasicInfo {
|
||||
old
|
||||
}
|
||||
|
||||
pub fn try_fd_table(&self) -> Option<Arc<RwSem<FileDescriptorVec>>> {
|
||||
return self.fd_table.clone();
|
||||
pub fn try_fd_table(&self) -> Option<Arc<FileDescriptorTable>> {
|
||||
self.fd_table.as_ref().map(FdTableAttachment::observer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fd_table_is_shared(&self) -> bool {
|
||||
pub(crate) fn fd_table_snapshot(&self) -> Option<(Arc<FileDescriptorTable>, bool)> {
|
||||
self.fd_table
|
||||
.as_ref()
|
||||
.map(|t| Arc::strong_count(t) > 1)
|
||||
.unwrap_or(false)
|
||||
.map(|attachment| (attachment.observer(), attachment.is_shared()))
|
||||
}
|
||||
|
||||
pub(crate) fn share_fd_table_for_task(&self) -> Option<FdTableAttachment> {
|
||||
self.fd_table
|
||||
.as_ref()
|
||||
.map(FdTableAttachment::share_for_task)
|
||||
}
|
||||
|
||||
pub fn set_fd_table(
|
||||
&mut self,
|
||||
fd_table: Option<Arc<RwSem<FileDescriptorVec>>>,
|
||||
) -> Option<Arc<RwSem<FileDescriptorVec>>> {
|
||||
fd_table: Option<Arc<FileDescriptorTable>>,
|
||||
) -> Option<Arc<FileDescriptorTable>> {
|
||||
self.set_fd_table_attachment(fd_table.map(FdTableAttachment::new))
|
||||
}
|
||||
|
||||
pub(crate) fn set_fd_table_attachment(
|
||||
&mut self,
|
||||
fd_table: Option<FdTableAttachment>,
|
||||
) -> Option<Arc<FileDescriptorTable>> {
|
||||
let old = self.fd_table.take();
|
||||
self.fd_table = fd_table;
|
||||
return old;
|
||||
old.map(FdTableAttachment::retire)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod fd_table_attachment_tests {
|
||||
use alloc::sync::Arc;
|
||||
|
||||
use super::FdTableAttachment;
|
||||
use crate::filesystem::vfs::file::{FileDescriptorTable, FileDescriptorVec};
|
||||
|
||||
#[test]
|
||||
fn observer_arcs_do_not_count_as_task_sharing() {
|
||||
let table = Arc::new(FileDescriptorTable::new(FileDescriptorVec::new()));
|
||||
let owner = FdTableAttachment::new(table);
|
||||
let observer = owner.observer();
|
||||
let another_observer = observer.clone();
|
||||
|
||||
assert!(!owner.is_shared());
|
||||
let shared_owner = owner.share_for_task();
|
||||
assert!(owner.is_shared());
|
||||
drop(shared_owner);
|
||||
assert!(!owner.is_shared());
|
||||
|
||||
drop(another_observer);
|
||||
drop(observer);
|
||||
drop(owner);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,20 +32,30 @@ impl ProcessControlBlock {
|
||||
}
|
||||
|
||||
pub fn replace_sighand(&self, new: Arc<SigHand>) {
|
||||
self.with_task_lock_irqsave(|| {
|
||||
let retired = self.with_task_lock_irqsave(|| {
|
||||
new.attach_task_ref();
|
||||
// SAFETY: task_lock serializes sighand writers. If old and new
|
||||
// share an allocation, the replacement slot reference publishes
|
||||
// it continuously. Otherwise `old` keeps the removed allocation
|
||||
// alive until it is submitted to rcu_defer_drop below.
|
||||
// it continuously. Otherwise the returned `old` keeps the removed
|
||||
// allocation alive across the unlock-to-retire handoff below.
|
||||
let old = unsafe { self.sighand.swap(new.clone()) };
|
||||
if Arc::ptr_eq(&old, &new) {
|
||||
new.detach_task_ref();
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
|
||||
old.detach_task_ref();
|
||||
crate::rcu::rcu_defer_drop(old);
|
||||
Some(old)
|
||||
});
|
||||
|
||||
if let Some(old) = retired {
|
||||
// Callback allocation/queueing and either fallback may run
|
||||
// arbitrary allocator/destructor code. Keep all of them outside
|
||||
// the irq-disabled task writer critical section.
|
||||
if let Err(old) = crate::rcu::try_rcu_defer_drop_arc(old) {
|
||||
crate::rcu::synchronize_rcu_noalloc();
|
||||
drop(old);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ use crate::{
|
||||
exception::InterruptArch,
|
||||
filesystem::{
|
||||
fs::FsStruct,
|
||||
vfs::{file::FileDescriptorVec, FileType, IndexNode},
|
||||
vfs::{
|
||||
file::{FileDescriptorTable, FileDescriptorVec},
|
||||
FileType, IndexNode,
|
||||
},
|
||||
},
|
||||
ipc::{
|
||||
sighand::{NaturalParentNotifyPhase, SigHand},
|
||||
@@ -33,7 +36,6 @@ use crate::{
|
||||
lock_free_flags::LockFreeFlags,
|
||||
mutex::{Mutex, MutexGuard},
|
||||
rwlock::{RwLock, RwLockReadGuard, RwLockUpgradableGuard, RwLockWriteGuard},
|
||||
rwsem::RwSem,
|
||||
spinlock::{SpinLock, SpinLockGuard},
|
||||
wait_queue::WaitQueue,
|
||||
},
|
||||
@@ -838,7 +840,7 @@ impl ProcessControlBlock {
|
||||
|
||||
/// Returns an `Arc` pointer to the file descriptor table.
|
||||
#[inline(always)]
|
||||
pub fn fd_table(&self) -> Arc<RwSem<FileDescriptorVec>> {
|
||||
pub fn fd_table(&self) -> Arc<FileDescriptorTable> {
|
||||
return self.basic.read().try_fd_table().unwrap();
|
||||
}
|
||||
|
||||
|
||||
+119
-10
@@ -12,7 +12,7 @@ use crate::{
|
||||
libs::{cpumask::CpuMask, spinlock::SpinLock, wait_queue::WaitQueue},
|
||||
mm::percpu::PerCpu,
|
||||
process::{kthread::KernelThreadClosure, kthread::KernelThreadMechanism, ProcessManager},
|
||||
sched::SchedPolicy,
|
||||
sched::{sched_yield, SchedPolicy},
|
||||
smp::{
|
||||
core::smp_get_processor_id,
|
||||
cpu::{smp_cpu_manager, smp_cpu_manager_initialized, ProcessorId},
|
||||
@@ -588,18 +588,46 @@ fn report_quiescent_state(cpu: ProcessorId) {
|
||||
}
|
||||
}
|
||||
|
||||
fn reserve_callback_capacity(inner: &mut RcuStateInner) {
|
||||
let ready_additional = inner
|
||||
.pending_callbacks
|
||||
.len()
|
||||
.checked_add(1)
|
||||
.expect("RCU callback count overflow");
|
||||
inner.pending_callbacks.reserve(1);
|
||||
inner.ready_callbacks.reserve(ready_additional);
|
||||
}
|
||||
|
||||
fn try_reserve_callback_capacity(inner: &mut RcuStateInner) -> Result<(), ()> {
|
||||
let ready_additional = inner.pending_callbacks.len().checked_add(1).ok_or(())?;
|
||||
inner.pending_callbacks.try_reserve(1).map_err(|_| ())?;
|
||||
inner
|
||||
.ready_callbacks
|
||||
.try_reserve(ready_additional)
|
||||
.map_err(|_| ())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn enqueue_callback_locked(inner: &mut RcuStateInner, kind: CallbackKind) -> bool {
|
||||
let target_gp = inner.request_future_gp();
|
||||
let seq = inner.allocate_callback_seq();
|
||||
inner.pending_callbacks.push_back(CallbackItem {
|
||||
target_gp,
|
||||
seq,
|
||||
kind,
|
||||
});
|
||||
let ready_changed = RcuState::pump_grace_periods(inner);
|
||||
ready_changed || inner.has_ready_work()
|
||||
}
|
||||
|
||||
fn queue_callback(kind: CallbackKind) {
|
||||
let wake_worker = {
|
||||
let mut inner = RCU_STATE.inner.lock_irqsave();
|
||||
let target_gp = inner.request_future_gp();
|
||||
let seq = inner.allocate_callback_seq();
|
||||
inner.pending_callbacks.push_back(CallbackItem {
|
||||
target_gp,
|
||||
seq,
|
||||
kind,
|
||||
});
|
||||
let ready_changed = RcuState::pump_grace_periods(&mut inner);
|
||||
ready_changed || inner.has_ready_work()
|
||||
// Admission reserves the destination capacity as well, so grace-period
|
||||
// completion can move every pending callback to the ready queue without
|
||||
// allocating in IRQ or other non-fallible progress paths.
|
||||
reserve_callback_capacity(&mut inner);
|
||||
enqueue_callback_locked(&mut inner, kind)
|
||||
};
|
||||
|
||||
RCU_STATE.wake_state_waiters();
|
||||
@@ -620,6 +648,24 @@ fn queue_deferred_callback(call: Box<dyn DeferredCall>) {
|
||||
queue_callback(CallbackKind::Deferred(call));
|
||||
}
|
||||
|
||||
fn try_queue_deferred_callback(call: Box<dyn DeferredCall>) -> Result<(), Box<dyn DeferredCall>> {
|
||||
let mut call = Some(call);
|
||||
let wake_worker = {
|
||||
let mut inner = RCU_STATE.inner.lock_irqsave();
|
||||
if try_reserve_callback_capacity(&mut inner).is_err() {
|
||||
return Err(call.take().unwrap());
|
||||
}
|
||||
enqueue_callback_locked(&mut inner, CallbackKind::Deferred(call.take().unwrap()))
|
||||
};
|
||||
|
||||
RCU_STATE.wake_state_waiters();
|
||||
if wake_worker {
|
||||
RCU_STATE.wake_worker();
|
||||
RCU_STATE.maybe_process_ready_callbacks_inline();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn worker_main() -> i32 {
|
||||
loop {
|
||||
RCU_STATE.worker_wait.wait_until(|| {
|
||||
@@ -764,6 +810,35 @@ where
|
||||
});
|
||||
}
|
||||
|
||||
/// Tries to defer the drop of an `Arc` without invoking the allocator's
|
||||
/// infallible OOM path.
|
||||
///
|
||||
/// On failure, the original reference is returned and has not been published
|
||||
/// to the RCU callback queue. The caller must keep it alive through a grace
|
||||
/// period before dropping it.
|
||||
pub(crate) fn try_rcu_defer_drop_arc<T>(value: Arc<T>) -> Result<(), Arc<T>>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
if !rcu_enabled() {
|
||||
drop(value);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let queued_value = value.clone();
|
||||
let call: Box<dyn DeferredCall> = match Box::try_new(move || drop(queued_value)) {
|
||||
Ok(call) => call,
|
||||
Err(_) => return Err(value),
|
||||
};
|
||||
|
||||
if let Err(call) = try_queue_deferred_callback(call) {
|
||||
drop(call);
|
||||
return Err(value);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn synchronize_rcu() {
|
||||
if !rcu_enabled() {
|
||||
return;
|
||||
@@ -794,6 +869,40 @@ pub fn synchronize_rcu() {
|
||||
});
|
||||
}
|
||||
|
||||
/// Waits for a grace period without registering a waiter or allocating.
|
||||
///
|
||||
/// This is reserved for recovery after a fallible callback admission has
|
||||
/// already failed. Normal callers should use `synchronize_rcu()`, which sleeps
|
||||
/// efficiently on the RCU state wait queue.
|
||||
pub(crate) fn synchronize_rcu_noalloc() {
|
||||
if !rcu_enabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
if rcu_read_lock_held() {
|
||||
warn!("synchronize_rcu_noalloc() called inside rcu_read_lock() region");
|
||||
debug_assert!(!rcu_read_lock_held());
|
||||
}
|
||||
|
||||
let target_gp = {
|
||||
let mut inner = RCU_STATE.inner.lock_irqsave();
|
||||
let target_gp = inner.request_future_gp();
|
||||
RcuState::pump_grace_periods(&mut inner);
|
||||
target_gp
|
||||
};
|
||||
|
||||
RCU_STATE.wake_state_waiters();
|
||||
RCU_STATE.wake_worker();
|
||||
|
||||
loop {
|
||||
let completed = RCU_STATE.inner.lock_irqsave().completed_gp_seq;
|
||||
if completed >= target_gp {
|
||||
break;
|
||||
}
|
||||
sched_yield();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rcu_barrier() {
|
||||
if !rcu_enabled() {
|
||||
return;
|
||||
|
||||
@@ -383,6 +383,25 @@ fn run_pr1_selftest() -> Result<(), &'static str> {
|
||||
return Err("rcu_defer_drop did not run after rcu_barrier");
|
||||
}
|
||||
|
||||
let fallible_deferred_drops = Arc::new(AtomicUsize::new(0));
|
||||
try_rcu_defer_drop_arc(Arc::new(RcuSelftestDropProbe {
|
||||
id: 2,
|
||||
drops: fallible_deferred_drops.clone(),
|
||||
}))
|
||||
.map_err(|_| "try_rcu_defer_drop_arc could not reserve a callback")?;
|
||||
rcu_barrier();
|
||||
|
||||
if fallible_deferred_drops.load(Ordering::SeqCst) != 1 {
|
||||
return Err("try_rcu_defer_drop_arc did not run after rcu_barrier");
|
||||
}
|
||||
|
||||
let completed_gp_before = RCU_STATE.inner.lock_irqsave().completed_gp_seq;
|
||||
synchronize_rcu_noalloc();
|
||||
let completed_gp_after = RCU_STATE.inner.lock_irqsave().completed_gp_seq;
|
||||
if completed_gp_after <= completed_gp_before {
|
||||
return Err("synchronize_rcu_noalloc did not complete a new grace period");
|
||||
}
|
||||
|
||||
let deferred_hits = Arc::new(AtomicUsize::new(0));
|
||||
rcu_defer({
|
||||
let deferred_hits = deferred_hits.clone();
|
||||
|
||||
@@ -1509,6 +1509,20 @@ pub fn sched_yield() {
|
||||
schedule(SchedMode::SM_NONE);
|
||||
}
|
||||
|
||||
/// Reschedule at a preemptible kernel boundary only when the scheduler has
|
||||
/// already requested it. Unlike `sched_yield()`, this does not voluntarily
|
||||
/// give up the CPU or alter scheduling-class yield state when no reschedule is
|
||||
/// pending.
|
||||
#[inline]
|
||||
pub fn cond_resched() {
|
||||
if ProcessManager::current_pcb()
|
||||
.flags()
|
||||
.contains(ProcessFlags::NEED_SCHEDULE)
|
||||
{
|
||||
schedule(SchedMode::SM_PREEMPT);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::loadavg;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Focused conformance binaries that must never report a skipped gtest case.
|
||||
normal/close_range_semantics
|
||||
normal/test_pivot_root
|
||||
normal/mount_propagation
|
||||
normal/mount_object_topology
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <limits.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#ifndef SYS_close_range
|
||||
#define SYS_close_range 436
|
||||
#endif
|
||||
#ifndef CLOSE_RANGE_UNSHARE
|
||||
#define CLOSE_RANGE_UNSHARE (1U << 1)
|
||||
#endif
|
||||
#ifndef CLOSE_RANGE_CLOEXEC
|
||||
#define CLOSE_RANGE_CLOEXEC (1U << 2)
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
int CloseRange(unsigned int first, unsigned int last, unsigned int flags) {
|
||||
return static_cast<int>(syscall(SYS_close_range, first, last, flags));
|
||||
}
|
||||
|
||||
long RawCloseRange(uint64_t first, uint64_t last, uint64_t flags) {
|
||||
return syscall(SYS_close_range, first, last, flags);
|
||||
}
|
||||
|
||||
class FdGuard {
|
||||
public:
|
||||
explicit FdGuard(int fd = -1) : fd_(fd) {}
|
||||
~FdGuard() {
|
||||
if (fd_ >= 0) close(fd_);
|
||||
}
|
||||
FdGuard(const FdGuard&) = delete;
|
||||
FdGuard& operator=(const FdGuard&) = delete;
|
||||
FdGuard(FdGuard&& other) noexcept : fd_(other.release()) {}
|
||||
FdGuard& operator=(FdGuard&& other) noexcept {
|
||||
if (this != &other) {
|
||||
if (fd_ >= 0) close(fd_);
|
||||
fd_ = other.release();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
int get() const { return fd_; }
|
||||
int release() {
|
||||
int fd = fd_;
|
||||
fd_ = -1;
|
||||
return fd;
|
||||
}
|
||||
|
||||
private:
|
||||
int fd_;
|
||||
};
|
||||
|
||||
FdGuard OpenNull() { return FdGuard(open("/dev/null", O_RDWR)); }
|
||||
|
||||
bool IsOpen(int fd) { return fcntl(fd, F_GETFD) >= 0; }
|
||||
|
||||
bool HasCloexec(int fd) {
|
||||
int flags = fcntl(fd, F_GETFD);
|
||||
return flags >= 0 && (flags & FD_CLOEXEC) != 0;
|
||||
}
|
||||
|
||||
struct SharedChildArgs {
|
||||
unsigned int first;
|
||||
unsigned int last;
|
||||
unsigned int flags;
|
||||
int target;
|
||||
int preserved;
|
||||
bool expect_closed;
|
||||
bool expect_cloexec;
|
||||
};
|
||||
|
||||
int SharedChild(void* opaque) {
|
||||
auto* args = static_cast<SharedChildArgs*>(opaque);
|
||||
if (CloseRange(args->first, args->last, args->flags) != 0) return 10;
|
||||
if (args->expect_closed == IsOpen(args->target)) return 11;
|
||||
if (!args->expect_closed && args->expect_cloexec != HasCloexec(args->target)) return 12;
|
||||
if (args->preserved >= 0 && !IsOpen(args->preserved)) return 13;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RunCloneFilesChild(SharedChildArgs* args) {
|
||||
constexpr size_t kStackSize = 64 * 1024;
|
||||
std::vector<unsigned char> stack(kStackSize);
|
||||
pid_t pid = clone(SharedChild, stack.data() + stack.size(), CLONE_FILES | SIGCHLD, args);
|
||||
if (pid < 0) return -1;
|
||||
int status = 0;
|
||||
if (waitpid(pid, &status, 0) != pid) return -1;
|
||||
if (!WIFEXITED(status)) return -1;
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
struct SharedExecArgs {
|
||||
int close_on_exec_fd;
|
||||
};
|
||||
|
||||
int SharedExecChild(void* opaque) {
|
||||
auto* args = static_cast<SharedExecArgs*>(opaque);
|
||||
char fd_text[32] = {};
|
||||
snprintf(fd_text, sizeof(fd_text), "%d", args->close_on_exec_fd);
|
||||
execl("/proc/self/exe", "close_range_semantics", "--check-exec-closed", fd_text,
|
||||
nullptr);
|
||||
return 41;
|
||||
}
|
||||
|
||||
TEST(CloseRangeSemantics, ValidationBoundsHolesAndRawU32Abi) {
|
||||
std::array<FdGuard, 6> fds = {OpenNull(), OpenNull(), OpenNull(),
|
||||
OpenNull(), OpenNull(), OpenNull()};
|
||||
for (const auto& fd : fds) ASSERT_GE(fd.get(), 0);
|
||||
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, CloseRange(fds[4].get(), fds[1].get(), 0));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, CloseRange(fds[1].get(), fds[4].get(), 0x80));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
EXPECT_EQ(0, CloseRange(UINT_MAX, UINT_MAX, 0));
|
||||
|
||||
ASSERT_EQ(0, close(fds[2].release()));
|
||||
ASSERT_EQ(0, CloseRange(fds[1].get(), fds[4].get(), 0));
|
||||
EXPECT_TRUE(IsOpen(fds[0].get()));
|
||||
for (int i = 1; i <= 4; ++i) EXPECT_FALSE(IsOpen(fds[i].get()));
|
||||
EXPECT_TRUE(IsOpen(fds[5].get()));
|
||||
EXPECT_EQ(0, CloseRange(fds[1].get(), fds[4].get(), 0));
|
||||
|
||||
FdGuard raw = OpenNull();
|
||||
FdGuard sentinel = OpenNull();
|
||||
ASSERT_GE(raw.get(), 0);
|
||||
ASSERT_GE(sentinel.get(), 0);
|
||||
const uint64_t high = uint64_t{1} << 32;
|
||||
ASSERT_EQ(0, RawCloseRange(high | static_cast<uint32_t>(raw.get()),
|
||||
high | static_cast<uint32_t>(raw.get()), high));
|
||||
EXPECT_FALSE(IsOpen(raw.get()));
|
||||
EXPECT_TRUE(IsOpen(sentinel.get()));
|
||||
}
|
||||
|
||||
TEST(CloseRangeSemantics, CloseAndCloexecRanges) {
|
||||
std::array<FdGuard, 5> fds = {OpenNull(), OpenNull(), OpenNull(), OpenNull(), OpenNull()};
|
||||
for (const auto& fd : fds) ASSERT_GE(fd.get(), 0);
|
||||
|
||||
ASSERT_EQ(0, CloseRange(fds[1].get(), fds[3].get(), CLOSE_RANGE_CLOEXEC));
|
||||
EXPECT_FALSE(HasCloexec(fds[0].get()));
|
||||
for (int i = 1; i <= 3; ++i) {
|
||||
EXPECT_TRUE(IsOpen(fds[i].get()));
|
||||
EXPECT_TRUE(HasCloexec(fds[i].get()));
|
||||
}
|
||||
EXPECT_FALSE(HasCloexec(fds[4].get()));
|
||||
|
||||
int hole = fds[2].release();
|
||||
ASSERT_EQ(0, close(hole));
|
||||
ASSERT_EQ(0, CloseRange(hole, hole, CLOSE_RANGE_CLOEXEC));
|
||||
FdGuard reused = OpenNull();
|
||||
ASSERT_EQ(hole, reused.get());
|
||||
EXPECT_FALSE(HasCloexec(reused.get()));
|
||||
|
||||
ASSERT_EQ(0, CloseRange(fds[3].get(), UINT_MAX, 0));
|
||||
EXPECT_TRUE(IsOpen(fds[0].get()));
|
||||
EXPECT_TRUE(IsOpen(fds[1].get()));
|
||||
EXPECT_FALSE(IsOpen(fds[3].get()));
|
||||
EXPECT_FALSE(IsOpen(fds[4].get()));
|
||||
}
|
||||
|
||||
TEST(CloseRangeSemantics, SharedAndUnsharedTableSemantics) {
|
||||
FdGuard target = OpenNull();
|
||||
FdGuard preserved = OpenNull();
|
||||
ASSERT_GE(target.get(), 0);
|
||||
ASSERT_GE(preserved.get(), 0);
|
||||
|
||||
SharedChildArgs private_close = {
|
||||
static_cast<unsigned int>(target.get()), static_cast<unsigned int>(target.get()),
|
||||
CLOSE_RANGE_UNSHARE, target.get(), preserved.get(), true, false};
|
||||
ASSERT_EQ(0, RunCloneFilesChild(&private_close));
|
||||
EXPECT_TRUE(IsOpen(target.get()));
|
||||
EXPECT_TRUE(IsOpen(preserved.get()));
|
||||
|
||||
SharedChildArgs private_cloexec = {
|
||||
static_cast<unsigned int>(target.get()), static_cast<unsigned int>(target.get()),
|
||||
CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC, target.get(), preserved.get(), false, true};
|
||||
ASSERT_EQ(0, RunCloneFilesChild(&private_cloexec));
|
||||
EXPECT_FALSE(HasCloexec(target.get()));
|
||||
|
||||
SharedChildArgs shared_cloexec = {
|
||||
static_cast<unsigned int>(target.get()), static_cast<unsigned int>(target.get()),
|
||||
CLOSE_RANGE_CLOEXEC, target.get(), preserved.get(), false, true};
|
||||
ASSERT_EQ(0, RunCloneFilesChild(&shared_cloexec));
|
||||
EXPECT_TRUE(HasCloexec(target.get()));
|
||||
|
||||
ASSERT_EQ(0, fcntl(target.get(), F_SETFD, 0));
|
||||
SharedChildArgs shared_close = {
|
||||
static_cast<unsigned int>(target.get()), static_cast<unsigned int>(target.get()),
|
||||
0, target.get(), preserved.get(), true, false};
|
||||
ASSERT_EQ(0, RunCloneFilesChild(&shared_close));
|
||||
EXPECT_FALSE(IsOpen(target.get()));
|
||||
EXPECT_TRUE(IsOpen(preserved.get()));
|
||||
}
|
||||
|
||||
TEST(CloseRangeSemantics, SharedFilesExecUnsharesBeforeCloseOnExec) {
|
||||
FdGuard target(open("/dev/null", O_RDWR | O_CLOEXEC));
|
||||
ASSERT_GE(target.get(), 0);
|
||||
|
||||
SharedExecArgs args = {target.get()};
|
||||
constexpr size_t kStackSize = 64 * 1024;
|
||||
std::vector<unsigned char> stack(kStackSize);
|
||||
pid_t child = clone(SharedExecChild, stack.data() + stack.size(), CLONE_FILES | SIGCHLD,
|
||||
&args);
|
||||
ASSERT_GE(child, 0);
|
||||
|
||||
int status = 0;
|
||||
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||||
ASSERT_TRUE(WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
|
||||
EXPECT_TRUE(IsOpen(target.get()));
|
||||
EXPECT_TRUE(HasCloexec(target.get()));
|
||||
}
|
||||
|
||||
int SparseNextFdProcess() {
|
||||
FdGuard base = OpenNull();
|
||||
if (base.get() < 0) return 20;
|
||||
for (int fd = 3; fd < 128; ++fd) {
|
||||
if (fd != base.get() && dup2(base.get(), fd) != fd) return 21;
|
||||
}
|
||||
|
||||
SharedChildArgs args = {64, UINT_MAX, CLOSE_RANGE_UNSHARE, 64, base.get(), true, false};
|
||||
constexpr size_t kStackSize = 64 * 1024;
|
||||
std::vector<unsigned char> stack(kStackSize);
|
||||
auto child_fn = [](void* opaque) -> int {
|
||||
auto* child_args = static_cast<SharedChildArgs*>(opaque);
|
||||
if (CloseRange(child_args->first, child_args->last, child_args->flags) != 0) return 22;
|
||||
int duplicated = dup(0);
|
||||
if (duplicated != 64) return 23;
|
||||
close(duplicated);
|
||||
return 0;
|
||||
};
|
||||
pid_t pid = clone(child_fn, stack.data() + stack.size(), CLONE_FILES | SIGCHLD, &args);
|
||||
if (pid < 0) return 24;
|
||||
int status = 0;
|
||||
if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) return 25;
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
TEST(CloseRangeSemantics, SparseHighFdAndNextFd) {
|
||||
FdGuard base = OpenNull();
|
||||
ASSERT_GE(base.get(), 0);
|
||||
FdGuard high(dup2(base.get(), 1000));
|
||||
ASSERT_EQ(1000, high.get());
|
||||
|
||||
SharedChildArgs args = {3, UINT_MAX, CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC,
|
||||
high.get(), base.get(), false, true};
|
||||
ASSERT_EQ(0, RunCloneFilesChild(&args));
|
||||
EXPECT_FALSE(HasCloexec(base.get()));
|
||||
EXPECT_FALSE(HasCloexec(high.get()));
|
||||
|
||||
pid_t isolated = fork();
|
||||
ASSERT_GE(isolated, 0);
|
||||
if (isolated == 0) _exit(SparseNextFdProcess());
|
||||
int status = 0;
|
||||
ASSERT_EQ(isolated, waitpid(isolated, &status, 0));
|
||||
ASSERT_TRUE(WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
TEST(CloseRangeSemantics, CloexecIgnoresLoweredRlimitForExistingFds) {
|
||||
pid_t child = fork();
|
||||
ASSERT_GE(child, 0);
|
||||
if (child == 0) {
|
||||
std::vector<int> fds;
|
||||
for (int i = 0; i < 40; ++i) {
|
||||
int fd = open("/dev/null", O_RDWR);
|
||||
if (fd < 0) _exit(30);
|
||||
fds.push_back(fd);
|
||||
}
|
||||
rlimit limit{};
|
||||
if (getrlimit(RLIMIT_NOFILE, &limit) != 0) _exit(31);
|
||||
limit.rlim_cur = 25;
|
||||
if (setrlimit(RLIMIT_NOFILE, &limit) != 0) _exit(32);
|
||||
int plain_high = fds[fds.size() - 2];
|
||||
int unshare_high = fds.back();
|
||||
if (CloseRange(plain_high, plain_high, CLOSE_RANGE_CLOEXEC) != 0) _exit(33);
|
||||
if (!HasCloexec(plain_high)) _exit(34);
|
||||
|
||||
SharedChildArgs args = {static_cast<unsigned int>(unshare_high),
|
||||
static_cast<unsigned int>(unshare_high),
|
||||
CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE,
|
||||
unshare_high, plain_high, false, true};
|
||||
if (RunCloneFilesChild(&args) != 0) _exit(35);
|
||||
if (HasCloexec(unshare_high)) _exit(36);
|
||||
_exit(0);
|
||||
}
|
||||
int status = 0;
|
||||
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||||
ASSERT_TRUE(WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
TEST(CloseRangeSemantics, UnshareOnPrivateTablePreservesPosixLockOwner) {
|
||||
char path[] = "/tmp/close-range-lock-XXXXXX";
|
||||
FdGuard file(mkstemp(path));
|
||||
ASSERT_GE(file.get(), 0);
|
||||
ASSERT_EQ(0, unlink(path));
|
||||
|
||||
flock lock{};
|
||||
lock.l_type = F_WRLCK;
|
||||
lock.l_whence = SEEK_SET;
|
||||
lock.l_start = 0;
|
||||
lock.l_len = 0;
|
||||
ASSERT_EQ(0, fcntl(file.get(), F_SETLK, &lock));
|
||||
ASSERT_EQ(0, CloseRange(UINT_MAX, UINT_MAX, CLOSE_RANGE_UNSHARE));
|
||||
|
||||
pid_t child = fork();
|
||||
ASSERT_GE(child, 0);
|
||||
if (child == 0) {
|
||||
// The inherited fd uses a new fork files-table owner. A conflicting lock
|
||||
// still proves the parent's original owner was not released by close_range.
|
||||
flock conflict = lock;
|
||||
int rc = fcntl(file.get(), F_SETLK, &conflict);
|
||||
_exit(rc == -1 && (errno == EACCES || errno == EAGAIN) ? 0 : 40);
|
||||
}
|
||||
int status = 0;
|
||||
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||||
ASSERT_TRUE(WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc == 3 && strcmp(argv[1], "--check-exec-closed") == 0) {
|
||||
char* end = nullptr;
|
||||
long fd = strtol(argv[2], &end, 10);
|
||||
if (end == argv[2] || *end != '\0' || fd < 0 || fd > INT_MAX) return 42;
|
||||
errno = 0;
|
||||
return fcntl(static_cast<int>(fd), F_GETFD) == -1 && errno == EBADF ? 0 : 43;
|
||||
}
|
||||
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sched.h>
|
||||
#include <sys/auxv.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -16,6 +17,8 @@
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
char g_self_path[PATH_MAX] = {};
|
||||
@@ -142,6 +145,17 @@ int set_test_robust_list(TestRobustListHead* head, TestRobustNode* node) {
|
||||
return static_cast<int>(syscall(SYS_set_robust_list, head, sizeof(*head)));
|
||||
}
|
||||
|
||||
void shared_sighand_handler(int) {}
|
||||
|
||||
int shared_sighand_exec_child(void* opaque) {
|
||||
auto* path = static_cast<char*>(opaque);
|
||||
char* const argv[] = {path, nullptr};
|
||||
char child_mode[] = "DRAGONOS_EXEC_ABI_SHARED_CHILD=1";
|
||||
char* const envp[] = {child_mode, nullptr};
|
||||
execve(path, argv, envp);
|
||||
return 97;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void ensure_tmp_dir() {
|
||||
@@ -267,53 +281,138 @@ TEST(ExecAbi, SuccessfulExecToleratesReadOnlyRobustFutex) {
|
||||
EXPECT_EQ(0, unlink(path));
|
||||
}
|
||||
|
||||
TEST(ExecAbi, FailedExecPreservesRobustList) {
|
||||
TEST(ExecAbi, EarlyFailedExecPreservesRobustList) {
|
||||
ensure_tmp_dir();
|
||||
char early_path[128] = {};
|
||||
char late_path[128] = {};
|
||||
snprintf(early_path, sizeof(early_path), "/tmp/exec_abi_bad_early_%d", getpid());
|
||||
snprintf(late_path, sizeof(late_path), "/tmp/exec_abi_bad_late_%d", getpid());
|
||||
static constexpr char kNotElf[] = "not an executable";
|
||||
write_executable(early_path, kNotElf, sizeof(kNotElf));
|
||||
|
||||
pid_t child = fork();
|
||||
ASSERT_GE(child, 0);
|
||||
if (child == 0) {
|
||||
TestRobustListHead head = {};
|
||||
TestRobustNode node = {};
|
||||
if (set_test_robust_list(&head, &node) != 0) {
|
||||
_exit(92);
|
||||
}
|
||||
char* const argv[] = {early_path, nullptr};
|
||||
char* const envp[] = {nullptr};
|
||||
if (execve(early_path, argv, envp) == 0) {
|
||||
_exit(93);
|
||||
}
|
||||
TestRobustListHead* observed = nullptr;
|
||||
size_t observed_size = 0;
|
||||
if (syscall(SYS_get_robust_list, 0, &observed, &observed_size) != 0) {
|
||||
_exit(94);
|
||||
}
|
||||
if (observed != &head || observed_size != sizeof(head) ||
|
||||
(node.futex & kFutexOwnerDied) != 0) {
|
||||
_exit(95);
|
||||
}
|
||||
_exit(0);
|
||||
}
|
||||
int status = 0;
|
||||
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||||
ASSERT_TRUE(WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
|
||||
EXPECT_EQ(0, unlink(early_path));
|
||||
}
|
||||
|
||||
TEST(ExecAbi, PostPointOfNoReturnExecFailureIsFatal) {
|
||||
ensure_tmp_dir();
|
||||
char path[128] = {};
|
||||
snprintf(path, sizeof(path), "/tmp/exec_abi_bad_post_ponr_%d", getpid());
|
||||
|
||||
unsigned char malformed[sizeof(kCheckRdxElf)] = {};
|
||||
memcpy(malformed, kCheckRdxElf, sizeof(malformed));
|
||||
malformed[96] += 1; // p_filesz > p_memsz, rejected after begin_new_exec().
|
||||
write_executable(late_path, malformed, sizeof(malformed));
|
||||
write_executable(path, malformed, sizeof(malformed));
|
||||
|
||||
for (const char* path : {early_path, late_path}) {
|
||||
pid_t child = fork();
|
||||
ASSERT_GE(child, 0);
|
||||
if (child == 0) {
|
||||
TestRobustListHead head = {};
|
||||
TestRobustNode node = {};
|
||||
if (set_test_robust_list(&head, &node) != 0) {
|
||||
_exit(92);
|
||||
}
|
||||
char* const argv[] = {const_cast<char*>(path), nullptr};
|
||||
char* const envp[] = {nullptr};
|
||||
if (execve(path, argv, envp) == 0) {
|
||||
_exit(93);
|
||||
}
|
||||
TestRobustListHead* observed = nullptr;
|
||||
size_t observed_size = 0;
|
||||
if (syscall(SYS_get_robust_list, 0, &observed, &observed_size) != 0) {
|
||||
_exit(94);
|
||||
}
|
||||
if (observed != &head || observed_size != sizeof(head) ||
|
||||
(node.futex & kFutexOwnerDied) != 0) {
|
||||
_exit(95);
|
||||
}
|
||||
_exit(0);
|
||||
}
|
||||
int status = 0;
|
||||
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||||
ASSERT_TRUE(WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
pid_t child = fork();
|
||||
ASSERT_GE(child, 0);
|
||||
if (child == 0) {
|
||||
char* const argv[] = {path, nullptr};
|
||||
char* const envp[] = {nullptr};
|
||||
execve(path, argv, envp);
|
||||
_exit(96);
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, unlink(early_path));
|
||||
EXPECT_EQ(0, unlink(late_path));
|
||||
int status = 0;
|
||||
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||||
EXPECT_TRUE(WIFSIGNALED(status));
|
||||
if (WIFSIGNALED(status)) {
|
||||
EXPECT_EQ(SIGSEGV, WTERMSIG(status));
|
||||
}
|
||||
EXPECT_EQ(0, unlink(path));
|
||||
}
|
||||
|
||||
TEST(ExecAbi, PostPonrFailureDoesNotChangeSharedSiblingHandler) {
|
||||
ensure_tmp_dir();
|
||||
char path[128] = {};
|
||||
snprintf(path, sizeof(path), "/tmp/exec_abi_bad_shared_sighand_%d", getpid());
|
||||
|
||||
unsigned char malformed[sizeof(kCheckRdxElf)] = {};
|
||||
memcpy(malformed, kCheckRdxElf, sizeof(malformed));
|
||||
malformed[96] += 1;
|
||||
write_executable(path, malformed, sizeof(malformed));
|
||||
|
||||
struct sigaction saved = {};
|
||||
struct sigaction custom = {};
|
||||
ASSERT_EQ(0, sigaction(SIGSEGV, nullptr, &saved));
|
||||
custom.sa_handler = shared_sighand_handler;
|
||||
sigemptyset(&custom.sa_mask);
|
||||
ASSERT_EQ(0, sigaction(SIGSEGV, &custom, nullptr));
|
||||
|
||||
constexpr size_t kStackSize = 64 * 1024;
|
||||
std::vector<unsigned char> stack(kStackSize);
|
||||
pid_t child = clone(shared_sighand_exec_child, stack.data() + stack.size(),
|
||||
CLONE_VM | CLONE_SIGHAND | SIGCHLD, path);
|
||||
int status = 0;
|
||||
int waited = child < 0 ? -1 : waitpid(child, &status, 0);
|
||||
struct sigaction observed = {};
|
||||
int query_result = sigaction(SIGSEGV, nullptr, &observed);
|
||||
int restore_result = sigaction(SIGSEGV, &saved, nullptr);
|
||||
int unlink_result = unlink(path);
|
||||
|
||||
ASSERT_GE(child, 0);
|
||||
ASSERT_EQ(child, waited);
|
||||
ASSERT_TRUE(WIFSIGNALED(status));
|
||||
EXPECT_EQ(SIGSEGV, WTERMSIG(status));
|
||||
ASSERT_EQ(0, query_result);
|
||||
EXPECT_EQ(shared_sighand_handler, observed.sa_handler);
|
||||
EXPECT_EQ(0, restore_result);
|
||||
EXPECT_EQ(0, unlink_result);
|
||||
}
|
||||
|
||||
TEST(ExecAbi, SuccessfulExecDoesNotChangeSharedSiblingHandler) {
|
||||
ASSERT_NE('\0', g_self_path[0]) << "self executable path was not initialized";
|
||||
|
||||
struct sigaction saved = {};
|
||||
struct sigaction custom = {};
|
||||
ASSERT_EQ(0, sigaction(SIGUSR1, nullptr, &saved));
|
||||
custom.sa_handler = shared_sighand_handler;
|
||||
sigemptyset(&custom.sa_mask);
|
||||
ASSERT_EQ(0, sigaction(SIGUSR1, &custom, nullptr));
|
||||
|
||||
constexpr size_t kStackSize = 64 * 1024;
|
||||
std::vector<unsigned char> stack(kStackSize);
|
||||
pid_t child = clone(shared_sighand_exec_child, stack.data() + stack.size(),
|
||||
CLONE_VM | CLONE_SIGHAND | SIGCHLD, g_self_path);
|
||||
int status = 0;
|
||||
int waited = child < 0 ? -1 : waitpid(child, &status, 0);
|
||||
struct sigaction observed = {};
|
||||
int query_result = sigaction(SIGUSR1, nullptr, &observed);
|
||||
int restore_result = sigaction(SIGUSR1, &saved, nullptr);
|
||||
|
||||
ASSERT_GE(child, 0);
|
||||
ASSERT_EQ(child, waited);
|
||||
ASSERT_TRUE(WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
ASSERT_EQ(0, query_result);
|
||||
EXPECT_EQ(shared_sighand_handler, observed.sa_handler);
|
||||
EXPECT_EQ(0, restore_result);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -346,6 +445,9 @@ TEST(ExecAbi, AuxvUidGidFollowCredentialsAtExec) {
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (getenv("DRAGONOS_EXEC_ABI_SHARED_CHILD") != nullptr) {
|
||||
return 0;
|
||||
}
|
||||
if (getenv("DRAGONOS_EXEC_ABI_CHECK_AUXV") != nullptr) {
|
||||
return check_auxv_credentials();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Format: test case names relative to bin/ (remove the _test suffix from executables)
|
||||
demo/gtest_demo
|
||||
normal/capability
|
||||
normal/close_range_semantics
|
||||
normal/fdatasync
|
||||
normal/loop_semantics
|
||||
normal/getrandom_bit_distribution
|
||||
|
||||
Reference in New Issue
Block a user