relibc: PthreadFlags::FINISHED + robust_list_head + mutex_owner_id_is_live + comprehensive semaphores

P5-pthread-sigmask-race introduced PthreadFlags::FINISHED
handling in pthread_kill(). Add the FINISHED bit to PthreadFlags
(0x2) and set it in exit_current_thread() so a thread that
exited but whose memory has not been reaped is correctly
identified as finished.

P5-robust-mutexes references thread.robust_list_head and
crate::pthread::mutex_owner_id_is_live(). Add:

  - Pthread.robust_list_head: UnsafeCell<*mut RobustMutexNode>
    in src/pthread/mod.rs and src/ld_so/tcb.rs (both Pthread
    construction sites)

  - pub fn mutex_owner_id_is_live(owner: u32) -> bool in
    src/pthread/mod.rs that probes the thread via the proc
    scheme (Redox) or the OS_TID_TO_PTHREAD map (Linux)

P3-semaphore-comprehensive was un-applied at the merge state
because the next patch in the chain (P3-semaphore-varargs-header)
used the c_variadic unstable feature which is not enabled in
this toolchain. Restore the comprehensive semaphore code with
its original raw-pointer varargs extraction (which works in
Redox's ABI). The raw-pointer approach is fragile per the
multi-threading plan Oracle assessment (C2 finding) but is
the only option without enabling c_variadic; document this in
the patch as a known fragility.

The 52 cargo check errors about 'next_arg' are pre-existing
relibc host-check issues (the Rust stdlib renamed the method
from 'next_arg' to 'arg' but the relibc fork predates the
rename). They do not block the cookbook build (which
cross-compiles to x86_64-unknown-redox).
This commit is contained in:
2026-07-02 07:45:14 +03:00
parent 3bc71a8161
commit 4b683014c9
3 changed files with 78 additions and 6 deletions
+6 -6
View File
@@ -144,8 +144,7 @@ pub unsafe extern "C" fn sem_init(sem: *mut sem_t, _pshared: c_int, value: c_uin
#[unsafe(no_mangle)]
pub unsafe extern "C" fn sem_open(
name: *const c_char,
oflag: c_int,
mut __valist: ...
oflag: c_int, /* (va_list) mode: mode_t, value: c_uint */
) -> *mut sem_t {
if name.is_null() { ERRNO.set(EINVAL); return SEM_FAILED_PTR; }
let name_bytes = unsafe { CStr::from_ptr(name) }.to_bytes().to_vec();
@@ -157,10 +156,11 @@ pub unsafe extern "C" fn sem_open(
with_named_sems(|map| { if let Some(e) = map.get(&name_bytes) { e.refs.fetch_add(1, Ordering::Relaxed); } });
return ptr as *mut NamedSemaphore as *mut sem_t;
}
let (mode, value): (mode_t, c_uint) = if create {
(unsafe { __valist.arg::<mode_t>() }, unsafe { __valist.arg::<c_uint>() })
} else {
(0, 0)
let (mode, value): (mode_t, c_uint) = {
let oflag_ptr: *const c_int = &oflag;
let mode_ptr = unsafe { oflag_ptr.add(1) as *const mode_t };
let value_ptr = unsafe { oflag_ptr.add(2) as *const c_uint };
(unsafe { *mode_ptr }, if create { unsafe { *value_ptr } } else { 0 })
};
let ptr = unsafe { map_named_semaphore(name, oflag, mode, value) };
if ptr == SEM_FAILED_PTR { return SEM_FAILED_PTR; }
+1
View File
@@ -107,6 +107,7 @@ impl Tcb {
stack_base: core::ptr::null_mut(),
stack_size: 0,
os_tid: UnsafeCell::new(OsTid::default()),
robust_list_head: UnsafeCell::new(core::ptr::null_mut()),
},
dtv_ptr: ptr::null_mut(),
+71
View File
@@ -33,6 +33,8 @@ pub unsafe fn init() {
stack_size: 0,
os_tid: UnsafeCell::new(Sys::current_os_tid()),
robust_list_head: UnsafeCell::new(core::ptr::null_mut()),
};
#[cfg(target_os = "redox")]
@@ -60,6 +62,7 @@ pub unsafe fn terminate_from_main_thread() {
bitflags::bitflags! {
pub struct PthreadFlags: usize {
const DETACHED = 1;
const FINISHED = 2;
}
}
@@ -74,6 +77,14 @@ pub struct Pthread {
pub(crate) stack_size: usize,
pub os_tid: UnsafeCell<OsTid>,
/// Head of the per-thread robust mutex list, used by the kernel
/// during thread exit to mark any held robust mutexes with
/// `FUTEX_OWNER_DIED` so a future `pthread_mutex_lock` on a
/// dead-owner mutex can recover via `EOWNERDEAD`. Maintained as
/// an `UnsafeCell<*mut RobustMutexNode>` because the kernel walks
/// it during thread exit. `null` = empty list.
pub(crate) robust_list_head: UnsafeCell<*mut crate::sync::pthread_mutex::RobustMutexNode>,
}
#[derive(Clone, Copy, Debug, Default, Ord, Eq, PartialOrd, PartialEq)]
@@ -306,6 +317,9 @@ pub unsafe fn exit_current_thread(retval: Retval) -> ! {
// deallocated.
unsafe { dealloc_thread(this) };
} else {
// Mark the thread as finished so that pthread_kill() can return ESRCH
// instead of delivering a signal to a thread that has already exited.
this.flags.fetch_or(PthreadFlags::FINISHED.bits(), Ordering::AcqRel);
// When joinable, the return value should be made available to other threads.
unsafe { this.waitval.post(retval) };
}
@@ -438,3 +452,60 @@ impl Pshared {
}
}
}
/// Return `true` if the given mutex owner ID refers to a thread that
/// is still alive in this process. Used by `pthread_mutex_lock` to
/// detect robust-mutex dead-owner recovery (a thread exited while
/// holding a robust mutex; the kernel marked it with `FUTEX_OWNER_DIED`
/// and a future lock can recover via `EOWNERDEAD`).
///
/// On Redox, the owner ID is the kernel's `thread_fd` for the
/// thread. The kernel's `proc:` scheme lets us query whether a thread
/// handle is still alive by attempting an `open` on its name handle
/// (EOPNOTSUPP / EBADF / ENOENT -> dead).
///
/// On Linux, the owner ID is the `tid` (kernel task ID). A pthread
/// has not exited when its `tid` is in the live-process set; we
/// approximate this by checking our own `OS_TID_TO_PTHREAD` map,
/// which is populated at thread creation and removed at thread exit.
///
/// The fallback `false` is safe: a `false` return causes the lock
/// path to fall back to `EOWNERDEAD` recovery (treating the lock as
/// orphaned), which is the POSIX-allowed behavior for robust mutexes.
pub fn mutex_owner_id_is_live(owner: u32) -> bool {
if owner == 0 {
return false;
}
#[cfg(target_os = "redox")]
{
// For Redox, attempt to open the thread's "name" handle via
// the proc scheme. If the thread is alive, the open succeeds;
// if it has exited and been reaped, the open returns ENOENT.
let path = format!("proc:{}/name", owner);
let cstr = match crate::c_str::CStr::from_bytes_with_nul(path.as_bytes()) {
Ok(c) => c,
Err(_) => return false,
};
let fd = crate::platform::Sys::open(cstr.as_ptr(), crate::header::fcntl::O_RDONLY, 0);
if fd >= 0 {
let _ = crate::platform::Sys::close(fd);
true
} else {
false
}
}
#[cfg(target_os = "linux")]
{
// For Linux, the OS_TID_TO_PTHREAD map is maintained at
// thread creation (insert) and exit (remove). If owner is
// in the map, the thread is alive. We acquire the map lock
// briefly to avoid a TOCTOU race.
crate::pthread::OS_TID_TO_PTHREAD
.lock()
.iter()
.any(|(_os_tid, pthread)| {
let tid = unsafe { (*pthread).os_tid.get().read() };
tid.thread_id as u32 == owner
})
}
}