use alloc::{sync::Arc, vec::Vec};
use core::{
cell::UnsafeCell,
sync::atomic::{AtomicBool, AtomicI32, AtomicU8, AtomicU32, AtomicUsize, Ordering},
};
use ax_runtime::hal::{cpu::user::UserContext, percpu::CpuPin, time::TimeValue};
use axpoll_set::PollSet;
use scope_local::{ActiveScope, LocalItem, Scope};
use starry_signal::{SignalSet, Signo, api::ThreadSignalManager};
use super::{
CpuTimeAccounting, Cred, ExitPathLease, PidIdentity, PidNamespaceRef, PidRoleLease,
ProcessData, ROOT_PID_NS, RttimeWatchdog, SeccompDecision, SeccompState, SeccompStateStore,
SockFilter, Tid, TidNumber, UserTaskRef,
bounded_stack::BoundedStack,
futex::ThreadWaitState,
future,
interruption::{InterruptSnapshot, InterruptState},
ops,
scheduler_identity::SchedulerIdentity,
user_memory_access::{UserMemoryAccessDepth, UserMemoryAccessGuard},
wait_on_pollset,
};
use crate::sync::{IrqMutex, Mutex, NoPreemptIrqSave};
const KRETPROBE_STACK_CAPACITY: usize = 16;
const SYSCALL_WORK_SECCOMP: u32 = 1 << 0;
struct ThreadIdentity {
scheduler: SchedulerIdentity,
nice: AtomicI32,
}
impl ThreadIdentity {
fn new() -> Self {
Self {
scheduler: SchedulerIdentity::unbound(),
nice: AtomicI32::new(0),
}
}
}
struct ThreadPidOwnership {
tid_lease: Option<PidRoleLease<Tid>>,
identity: Arc<PidIdentity>,
}
struct ThreadScope {
scope: UnsafeCell<Scope>,
access: Mutex<()>,
}
unsafe impl Sync for ThreadScope {}
impl ThreadScope {
fn new(scope: Scope) -> Self {
Self {
scope: UnsafeCell::new(scope),
access: Mutex::new(()),
}
}
fn with_current_mut<R>(&self, operation: impl FnOnce(&mut Scope) -> R) -> R {
let _access = self.access.lock();
let _guard = NoPreemptIrqSave::new();
unsafe {
ax_runtime::hal::percpu::with_cpu_pin(|pin| {
let scope = &mut *self.scope.get();
assert!(
ActiveScope::is_pinned(scope, pin),
"Starry scope mutation does not belong to the current task"
);
operation(scope)
})
.expect("Starry scope mutation requires an installed CPU area")
}
}
fn clone_item<T>(&self, item: &LocalItem<T>) -> T
where
T: Clone + Send + Sync + 'static,
{
let _access = self.access.lock();
item.scope(unsafe { &*self.scope.get() }).clone()
}
unsafe fn activate_pinned(&self, pin: &CpuPin<'_>) {
assert!(
ActiveScope::is_global_pinned(pin),
"Starry scope activation requires the global scope"
);
unsafe { ActiveScope::set_pinned(&*self.scope.get(), pin) };
}
unsafe fn deactivate_pinned(&self, pin: &CpuPin<'_>) {
assert!(
ActiveScope::is_pinned(unsafe { &*self.scope.get() }, pin),
"Starry scope deactivation does not match the current task"
);
unsafe { ActiveScope::set_global_pinned(pin) };
}
}
struct ThreadAccounting {
cpu_time: CpuTimeAccounting,
rttime: Mutex<RttimeWatchdog>,
}
impl ThreadAccounting {
fn new() -> crate::StarryResult<Self> {
Ok(Self {
cpu_time: CpuTimeAccounting::new()?,
rttime: Mutex::new(RttimeWatchdog::new()),
})
}
}
struct VforkDone {
done: bool,
poll: Arc<PollSet>,
}
struct ThreadLifecycle {
clear_child_tid: AtomicUsize,
robust_list_head: AtomicUsize,
exit: Arc<AtomicBool>,
interrupted: InterruptState,
user_memory_access: UserMemoryAccessDepth,
block_next_signal_check: NextSignalCheckBlock,
exit_event: Arc<PollSet>,
vfork_done: IrqMutex<Option<VforkDone>>,
exit_request: OneShotFlag,
deadline_overrun: OneShotFlag,
rseq_area: AtomicUsize,
rseq_signature: AtomicU32,
}
struct ThreadWork {
syscall: AtomicU32,
}
impl ThreadWork {
const fn new() -> Self {
Self {
syscall: AtomicU32::new(0),
}
}
}
impl ThreadLifecycle {
fn new() -> crate::StarryResult<Self> {
Ok(Self {
clear_child_tid: AtomicUsize::new(0),
robust_list_head: AtomicUsize::new(0),
exit: super::allocation::try_arc(AtomicBool::new(false))?,
interrupted: InterruptState::new(),
user_memory_access: UserMemoryAccessDepth::new(),
block_next_signal_check: NextSignalCheckBlock::new(),
exit_event: super::allocation::try_arc(PollSet::new())?,
vfork_done: IrqMutex::new(None),
exit_request: OneShotFlag::new(),
deadline_overrun: OneShotFlag::new(),
rseq_area: AtomicUsize::new(0),
rseq_signature: AtomicU32::new(0),
})
}
}
struct ThreadSignals {
manager: Arc<ThreadSignalManager>,
signalfd_waker: PollSet,
deferred_mask_restore: IrqMutex<Option<SignalSet>>,
deferred_mask_restore_pending: AtomicBool,
}
impl ThreadSignals {
fn new(
tid: u32,
process_signal: Arc<starry_signal::api::ProcessSignalManager>,
signal_mask: SignalSet,
) -> crate::StarryResult<Self> {
Ok(Self {
manager: ThreadSignalManager::new_with_blocked(tid, process_signal, signal_mask)?,
signalfd_waker: PollSet::new(),
deferred_mask_restore: IrqMutex::new(None),
deferred_mask_restore_pending: AtomicBool::new(false),
})
}
}
struct ThreadSecurity {
oom_score_adj: AtomicI32,
pdeathsig: AtomicU32,
no_new_privs: AtomicBool,
seccomp: SeccompStateStore,
cred: Mutex<Arc<Cred>>,
uid_map_written: AtomicBool,
gid_map_written: AtomicBool,
setgroups_deny: AtomicBool,
}
impl ThreadSecurity {
fn new(parent_cred: Option<Arc<Cred>>) -> crate::StarryResult<Self> {
Ok(Self {
oom_score_adj: AtomicI32::new(200),
pdeathsig: AtomicU32::new(0),
no_new_privs: AtomicBool::new(false),
seccomp: SeccompStateStore::new()?,
cred: Mutex::new(match parent_cred {
Some(cred) => cred,
None => super::allocation::try_arc(Cred::root())?,
}),
uid_map_written: AtomicBool::new(false),
gid_map_written: AtomicBool::new(false),
setgroups_deny: AtomicBool::new(false),
})
}
}
struct ThreadTrace {
fault_dump_signo: AtomicU8,
kretprobe_stack:
IrqMutex<BoundedStack<kprobe::retprobe::RetprobeInstance, KRETPROBE_STACK_CAPACITY>>,
#[cfg(target_arch = "aarch64")]
perf: crate::perf::task_context::ThreadPerfContext,
}
impl ThreadTrace {
fn new() -> Self {
Self {
fault_dump_signo: AtomicU8::new(0),
kretprobe_stack: IrqMutex::new(BoundedStack::new()),
#[cfg(target_arch = "aarch64")]
perf: crate::perf::task_context::ThreadPerfContext::new(),
}
}
}
struct OneShotFlag {
pending: AtomicBool,
#[cfg(axtest)]
consume_rmws: AtomicUsize,
}
impl OneShotFlag {
const fn new() -> Self {
Self {
pending: AtomicBool::new(false),
#[cfg(axtest)]
consume_rmws: AtomicUsize::new(0),
}
}
fn publish(&self) {
self.pending.store(true, Ordering::Release);
}
fn is_pending(&self) -> bool {
self.pending.load(Ordering::Acquire)
}
fn consume(&self) -> bool {
if !self.is_pending() {
return false;
}
#[cfg(axtest)]
self.consume_rmws.fetch_add(1, Ordering::Relaxed);
self.pending.swap(false, Ordering::AcqRel)
}
#[cfg(axtest)]
fn consume_rmw_count(&self) -> usize {
self.consume_rmws.load(Ordering::Relaxed)
}
}
struct NextSignalCheckBlock(OneShotFlag);
impl NextSignalCheckBlock {
const fn new() -> Self {
Self(OneShotFlag::new())
}
fn block(&self) {
self.0.publish();
}
fn unblock(&self) -> bool {
self.0.consume()
}
}
pub struct Thread {
identity: ThreadIdentity,
pid: IrqMutex<ThreadPidOwnership>,
pub proc_data: Arc<ProcessData>,
scope: ThreadScope,
accounting: ThreadAccounting,
lifecycle: ThreadLifecycle,
work: ThreadWork,
wait: ThreadWaitState,
signals: ThreadSignals,
security: ThreadSecurity,
trace: ThreadTrace,
}
impl Thread {
pub(crate) fn prepare_vfork_done(&self) -> crate::StarryResult<()> {
let poll = super::allocation::try_arc(PollSet::new())?;
let mut completion = self.lifecycle.vfork_done.lock();
assert!(completion.is_none(), "vfork completion installed twice");
*completion = Some(VforkDone { done: false, poll });
Ok(())
}
pub(crate) fn wait_vfork_done(&self, parent: &UserTaskRef) -> bool {
let poll = {
let guard = self.lifecycle.vfork_done.lock();
match guard.as_ref() {
Some(vfork) => vfork.poll.clone(),
None => return true,
}
};
let curr_thr = parent.as_thread();
loop {
let result = future::block_on_user(
parent,
wait_on_pollset(&poll, || {
self.lifecycle
.vfork_done
.lock()
.as_ref()
.map(|vfork| vfork.done)
.unwrap_or(true)
.then_some(())
}),
);
match result {
future::UserWaitOutcome::Ready(()) => return true,
future::UserWaitOutcome::Interrupted
if curr_thr.has_exit_request()
|| curr_thr.signal().pending().has(Signo::SIGKILL) =>
{
let detached = self.lifecycle.vfork_done.lock().take();
drop(detached);
return false;
}
future::UserWaitOutcome::Interrupted => continue,
future::UserWaitOutcome::TimedOut => {
unreachable!("vfork completion wait has no deadline")
}
}
}
}
pub(crate) fn notify_vfork_done(&self) {
let poll = {
let mut guard = self.lifecycle.vfork_done.lock();
match guard.as_mut() {
Some(vfork) => {
vfork.done = true;
vfork.poll.clone()
}
None => return,
}
};
unsafe { poll.wake(axpoll::IoEvents::IN) };
}
pub fn new(
identity: Arc<PidIdentity>,
tid_lease: PidRoleLease<Tid>,
proc_data: Arc<ProcessData>,
parent_cred: Option<Arc<Cred>>,
signal_mask: SignalSet,
scope: Scope,
) -> crate::StarryResult<Self> {
let tid = identity
.visible_number(&ROOT_PID_NS)
.expect("new thread identity has no root PID binding")
.get();
let process_signal = proc_data.signal.clone();
let process_identity = proc_data.identity();
let thread = Self {
identity: ThreadIdentity::new(),
pid: IrqMutex::new(ThreadPidOwnership {
identity: identity.clone(),
tid_lease: Some(tid_lease),
}),
proc_data,
scope: ThreadScope::new(scope),
accounting: ThreadAccounting::new()?,
lifecycle: ThreadLifecycle::new()?,
work: ThreadWork::new(),
wait: ThreadWaitState::new(),
security: ThreadSecurity::new(parent_cred)?,
trace: ThreadTrace::new(),
signals: ThreadSignals::new(tid, process_signal, signal_mask)?,
};
identity.bind_thread_pidfd(&process_identity, thread.exit_flag());
Ok(thread)
}
pub(super) const fn wait_state(&self) -> &ThreadWaitState {
&self.wait
}
pub(crate) fn with_current_scope_mut<R>(&self, f: impl FnOnce(&mut Scope) -> R) -> R {
self.scope.with_current_mut(f)
}
pub(crate) fn clone_scope_item<T>(&self, item: &LocalItem<T>) -> T
where
T: Clone + Send + Sync + 'static,
{
self.scope.clone_item(item)
}
pub fn tid(&self) -> TidNumber {
self.tid_number()
}
pub(crate) fn tid_number(&self) -> TidNumber {
TidNumber::from(
self.pid
.lock()
.identity
.visible_number(&ROOT_PID_NS)
.expect("live thread lost its root PID binding"),
)
}
pub(crate) fn pid_identity(&self) -> Arc<PidIdentity> {
self.pid.lock().identity.clone()
}
pub(crate) fn active_pid_namespace(&self) -> PidNamespaceRef {
self.pid.lock().identity.active_namespace()
}
pub(crate) fn user_tid(&self) -> TidNumber {
let pid = self.pid.lock();
let active = pid.identity.active_namespace();
TidNumber::from(
pid.identity
.visible_number(&active)
.expect("thread identity is not visible from its active namespace"),
)
}
pub(crate) fn attach_pid_task(&self, task: &UserTaskRef) {
self.pid.lock().identity.attach_task(task);
}
pub(crate) fn retire_pid_retaining_tid(&self) -> (PidRoleLease<Tid>, ExitPathLease) {
let (identity, lease) = {
let mut pid = self.pid.lock();
(pid.identity.clone(), pid.tid_lease.take())
};
let exit_path = identity.mark_task_exited();
(
lease.expect("thread TID lease transferred twice"),
exit_path,
)
}
pub(crate) fn retire_pid(&self) -> ExitPathLease {
let (tid_lease, exit_path) = self.retire_pid_retaining_tid();
exit_path.retain_tid(tid_lease)
}
pub(crate) fn transfer_pid_identity(
&self,
task: &UserTaskRef,
identity: Arc<PidIdentity>,
tid_lease: PidRoleLease<Tid>,
) {
let previous = {
let mut pid = self.pid.lock();
let _irq_guard = NoPreemptIrqSave::new();
task.transfer_irq_pid_identity(&identity)
.expect("exec leader identity differs from the cached process identity");
core::mem::replace(
&mut *pid,
ThreadPidOwnership {
identity: identity.clone(),
tid_lease: Some(tid_lease),
},
)
};
let previous_identity = {
drop(previous.tid_lease);
previous.identity
};
previous_identity.mark_task_exited().complete();
identity.transfer_task(task, &self.proc_data.identity(), self.exit_flag());
}
pub fn nice(&self) -> i32 {
self.identity.nice.load(Ordering::Acquire)
}
pub fn set_nice(&self, nice: i32) {
self.identity.nice.store(nice, Ordering::Release);
}
pub fn scheduler_id(&self) -> Option<ax_std::os::arceos::task::thread::ThreadId> {
self.identity.scheduler.get()
}
pub(super) fn scheduler_runtime_ns(&self) -> u64 {
self.identity
.scheduler
.get()
.and_then(|id| {
ax_runtime::task::thread::ThreadHandle::lookup(id)
.and_then(|thread| thread.runtime())
.ok()
})
.map(|snapshot| snapshot.charged_runtime_ns())
.unwrap_or_else(|| self.accounting.cpu_time.published_runtime_ns())
}
pub(crate) fn bind_scheduler_id(
&self,
id: ax_std::os::arceos::task::thread::ThreadId,
) -> crate::StarryResult<()> {
self.identity.scheduler.bind(id)
}
pub(crate) fn validate_scheduler_id(
&self,
id: ax_std::os::arceos::task::thread::ThreadId,
) -> crate::StarryResult<()> {
self.identity.scheduler.validate_bound(id)
}
pub(super) fn scheduler_switch_in(
&self,
id: ax_std::os::arceos::task::thread::ThreadId,
realtime_policy: bool,
charged_runtime_ns: u64,
cpu_pin: &CpuPin<'_>,
) {
debug_assert!(self.validate_scheduler_id(id).is_ok());
self.accounting
.cpu_time
.scheduler_switch_in(realtime_policy, || charged_runtime_ns);
unsafe { self.scope.activate_pinned(cpu_pin) };
#[cfg(target_arch = "aarch64")]
crate::perf::task::perf_sched_in(self);
}
pub(super) fn scheduler_switch_out(
&self,
reason: ax_std::os::arceos::task::thread::SwitchReason,
cpu_pin: &CpuPin<'_>,
) {
#[cfg(target_arch = "aarch64")]
crate::perf::task::perf_sched_out(self);
unsafe { self.scope.deactivate_pinned(cpu_pin) };
self.accounting.cpu_time.scheduler_switch_out(reason);
}
pub(crate) fn apply_cpu_time_policy(&self, realtime_policy: bool, _observed_ns: u64) {
self.accounting
.cpu_time
.apply_realtime_policy(realtime_policy);
}
pub(crate) fn cpu_time_output(&self) -> (TimeValue, TimeValue) {
self.accounting.cpu_time.output(self.scheduler_runtime_ns())
}
pub(crate) fn commit_cpu_time_now(&self) {
let runtime_ns = self.scheduler_runtime_ns();
self.proc_data.record_cpu_time_transition(|| {
self.accounting.cpu_time.publish_committed_delta(runtime_ns)
});
}
pub(super) fn sample_scheduler_tick_cpu_time(&self, _observed_ns: u64) {
let runtime_ns = self.scheduler_runtime_ns();
self.proc_data.record_cpu_time_transition(|| {
self.accounting.cpu_time.sample_scheduler_tick(runtime_ns)
});
}
pub(crate) fn cpu_time(&self) -> &CpuTimeAccounting {
&self.accounting.cpu_time
}
pub(crate) fn rttime(&self) -> &Mutex<RttimeWatchdog> {
&self.accounting.rttime
}
pub fn clear_child_tid(&self) -> usize {
self.lifecycle.clear_child_tid.load(Ordering::Relaxed)
}
pub fn set_clear_child_tid(&self, clear_child_tid: usize) {
self.lifecycle
.clear_child_tid
.store(clear_child_tid, Ordering::Relaxed);
}
pub fn robust_list_head(&self) -> usize {
self.lifecycle.robust_list_head.load(Ordering::SeqCst)
}
pub fn set_robust_list_head(&self, robust_list_head: usize) {
self.lifecycle
.robust_list_head
.store(robust_list_head, Ordering::SeqCst);
}
pub fn pending_exit(&self) -> bool {
self.lifecycle.exit.load(Ordering::Acquire)
}
pub fn begin_exit(&self) -> bool {
self.signal().begin_exit()
}
pub fn set_exit(&self) {
self.lifecycle.exit.store(true, Ordering::Release);
}
pub(crate) fn exit_flag(&self) -> Arc<AtomicBool> {
self.lifecycle.exit.clone()
}
pub(crate) fn exit_event(&self) -> Arc<PollSet> {
self.lifecycle.exit_event.clone()
}
pub fn take_exit_request(&self) -> bool {
self.lifecycle.exit_request.consume()
}
pub fn has_exit_request(&self) -> bool {
self.lifecycle.exit_request.is_pending()
}
pub fn set_exit_request(&self) {
self.lifecycle.exit_request.publish();
}
pub(super) fn publish_deadline_overrun(&self) {
self.lifecycle.deadline_overrun.publish();
}
pub(super) fn take_deadline_overrun(&self) -> bool {
self.lifecycle.deadline_overrun.consume()
}
pub(crate) fn enter_user_memory_access(&self) -> UserMemoryAccessGuard<'_> {
self.lifecycle.user_memory_access.enter()
}
pub(crate) fn has_active_user_memory_access(&self) -> bool {
self.lifecycle.user_memory_access.is_active()
}
pub(super) fn interrupt(&self) {
self.lifecycle.interrupted.publish();
}
pub(super) fn take_interrupt(&self) -> bool {
self.lifecycle.interrupted.consume()
}
pub(super) fn interrupted(&self) -> bool {
self.lifecycle.interrupted.is_pending()
}
pub(super) fn interrupt_snapshot(&self) -> InterruptSnapshot {
self.lifecycle.interrupted.snapshot()
}
pub(super) fn acknowledge_interrupt(&self, snapshot: InterruptSnapshot) {
let _advanced = self.lifecycle.interrupted.acknowledge(snapshot);
}
pub fn rseq_area(&self) -> usize {
self.lifecycle.rseq_area.load(Ordering::SeqCst)
}
pub fn rseq_signature(&self) -> u32 {
self.lifecycle.rseq_signature.load(Ordering::SeqCst)
}
pub fn set_rseq_state(&self, addr: usize, sig: u32) {
self.lifecycle.rseq_area.store(addr, Ordering::SeqCst);
self.lifecycle.rseq_signature.store(sig, Ordering::SeqCst);
}
pub fn clear_rseq_state(&self) {
self.lifecycle.rseq_area.store(0, Ordering::SeqCst);
self.lifecycle.rseq_signature.store(0, Ordering::SeqCst);
}
pub fn block_next_signal_check(&self) {
self.lifecycle.block_next_signal_check.block();
}
pub fn unblock_next_signal_check(&self) -> bool {
self.lifecycle.block_next_signal_check.unblock()
}
pub fn signal(&self) -> &Arc<ThreadSignalManager> {
&self.signals.manager
}
pub(crate) fn defer_signal_mask_restore(&self, mask: SignalSet) {
let previous = self.signals.deferred_mask_restore.lock().replace(mask);
assert!(
previous.is_none(),
"one thread cannot own nested deferred signal-mask restores"
);
self.signals
.deferred_mask_restore_pending
.store(true, Ordering::Release);
}
pub(crate) fn take_deferred_signal_mask_restore(&self) -> Option<SignalSet> {
if !self
.signals
.deferred_mask_restore_pending
.load(Ordering::Acquire)
{
return None;
}
let restore = self.signals.deferred_mask_restore.lock().take();
self.signals
.deferred_mask_restore_pending
.store(false, Ordering::Release);
restore
}
pub(super) fn has_user_return_work(&self) -> bool {
self.interrupted()
|| self.signal().has_pending_signal_work()
|| self.has_exit_request()
|| self.lifecycle.deadline_overrun.is_pending()
|| self
.signals
.deferred_mask_restore_pending
.load(Ordering::Acquire)
}
pub(crate) fn wake_signalfd(&self) {
unsafe { self.signals.signalfd_waker.wake(axpoll::IoEvents::IN) };
}
pub(crate) fn signalfd_poll_source(&self) -> &PollSet {
&self.signals.signalfd_waker
}
pub fn oom_score_adj(&self) -> i32 {
self.security.oom_score_adj.load(Ordering::SeqCst)
}
pub fn set_oom_score_adj(&self, value: i32) {
self.security.oom_score_adj.store(value, Ordering::SeqCst);
}
pub fn pdeathsig(&self) -> u32 {
self.security.pdeathsig.load(Ordering::Relaxed)
}
pub fn set_pdeathsig(&self, sig: u32) {
self.security.pdeathsig.store(sig, Ordering::Relaxed);
}
pub fn no_new_privs(&self) -> bool {
self.security.no_new_privs.load(Ordering::Relaxed)
}
pub fn set_no_new_privs(&self) {
self.security.no_new_privs.store(true, Ordering::Relaxed);
}
pub fn seccomp_state(&self) -> Arc<SeccompState> {
self.security.seccomp.snapshot()
}
pub(crate) fn evaluate_seccomp(&self, uctx: &UserContext) -> SeccompDecision {
self.security.seccomp.evaluate(uctx)
}
pub(crate) fn has_seccomp_syscall_work(&self) -> bool {
self.work.syscall.load(Ordering::Acquire) & SYSCALL_WORK_SECCOMP != 0
}
fn publish_seccomp_syscall_work(&self) {
self.work
.syscall
.fetch_or(SYSCALL_WORK_SECCOMP, Ordering::Release);
}
pub(crate) fn inherit_security(&self, parent: &Thread) -> crate::StarryResult<()> {
let state = parent.seccomp_state();
let active = state.is_active();
self.security.seccomp.inherit(state)?;
if parent.no_new_privs() {
self.set_no_new_privs();
}
if active {
self.publish_seccomp_syscall_work();
}
Ok(())
}
pub fn set_seccomp_state(&self, state: Arc<SeccompState>) {
let active = state.is_active();
self.security.seccomp.replace(state);
if active {
self.publish_seccomp_syscall_work();
}
}
pub fn install_seccomp_strict(&self) -> crate::StarryResult<()> {
self.security.seccomp.update(SeccompState::install_strict)?;
self.publish_seccomp_syscall_work();
Ok(())
}
pub fn append_seccomp_filter(&self, insns: Vec<SockFilter>) -> crate::StarryResult<()> {
self.security
.seccomp
.update(move |state| state.append_filter(insns))?;
self.publish_seccomp_syscall_work();
Ok(())
}
pub fn cred(&self) -> Arc<Cred> {
self.security.cred.lock().clone()
}
fn set_cred_single(&self, new_cred: Arc<Cred>) {
let previous = {
let mut current = self.security.cred.lock();
core::mem::replace(&mut *current, new_cred)
};
drop(previous);
}
pub(crate) fn set_thread_cred(&self, new_cred: Cred) {
self.set_cred_single(Arc::new(new_cred));
}
pub fn set_cred(&self, new_cred: Cred) {
let new_arc = Arc::new(new_cred);
self.set_cred_single(new_arc.clone());
let mut tids = self.proc_data.proc.threads();
tids.sort_unstable();
for tid in &tids {
if let Ok(task) = ops::get_task_by_number(*tid) {
task.as_thread().set_cred_single(new_arc.clone());
}
}
}
pub(crate) fn update_process_creds(&self, update: impl Fn(&Cred) -> Cred) {
let old_cred = self.cred();
self.set_cred_single(Arc::new(update(&old_cred)));
let mut tids = self.proc_data.proc.threads();
tids.sort_unstable();
for tid in &tids {
if let Ok(task) = ops::get_task_by_number(*tid) {
let thread = task.as_thread();
if core::ptr::eq(thread, self) {
continue;
}
let old_cred = thread.cred();
thread.set_cred_single(Arc::new(update(&old_cred)));
}
}
}
pub fn uid_map_written(&self) -> bool {
self.security.uid_map_written.load(Ordering::Relaxed)
}
pub fn set_uid_map_written(&self, val: bool) {
self.security.uid_map_written.store(val, Ordering::Relaxed);
}
pub fn gid_map_written(&self) -> bool {
self.security.gid_map_written.load(Ordering::Relaxed)
}
pub fn set_gid_map_written(&self, val: bool) {
self.security.gid_map_written.store(val, Ordering::Relaxed);
}
pub fn setgroups_deny(&self) -> bool {
self.security.setgroups_deny.load(Ordering::Relaxed)
}
pub fn set_setgroups_deny(&self, val: bool) {
self.security.setgroups_deny.store(val, Ordering::Relaxed);
}
pub(crate) fn claim_fault_dump(&self, signo: u8) -> bool {
self.trace
.fault_dump_signo
.compare_exchange(signo, 0, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
}
pub(crate) fn set_fault_dump(&self, signo: u8) {
self.trace.fault_dump_signo.store(signo, Ordering::Release);
}
pub(crate) fn clear_fault_dump(&self) {
self.trace.fault_dump_signo.store(0, Ordering::Release);
}
pub(super) fn push_kretprobe(&self, instance: kprobe::retprobe::RetprobeInstance) {
let Some(mut stack) = self.trace.kretprobe_stack.try_lock() else {
panic!("nested kretprobe tried to re-enter the current task stack");
};
if let Err(instance) = stack.try_push(instance) {
core::mem::forget(instance);
panic!("current task exceeded its fixed kretprobe nesting capacity");
}
}
pub(super) fn pop_kretprobe(&self) -> kprobe::retprobe::RetprobeInstance {
let Some(mut stack) = self.trace.kretprobe_stack.try_lock() else {
panic!("nested kretprobe tried to re-enter the current task stack");
};
stack.pop().expect("kretprobe instance stack underflow")
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn perf_context(&self) -> &crate::perf::task_context::ThreadPerfContext {
&self.trace.perf
}
}
#[cfg(axtest)]
fn inactive_one_shot_flag_consumption_is_read_only_for_test() -> bool {
let flag = OneShotFlag::new();
if flag.consume() {
return false;
}
flag.publish();
flag.consume() && !flag.consume() && flag.consume_rmw_count() == 1
}
#[cfg(all(test, axtest))]
mod axtests {
#[axtest::axtest]
fn cancelled_thread_releases_tid_before_last_identity() {
use alloc::sync::Arc;
use crate::task::{PidReservation, PidReservationKind, Tid};
let namespace = crate::task::new_test_pid_namespace();
let reservation = PidReservation::reserve(&namespace, PidReservationKind::Thread).unwrap();
let identity = reservation.identity();
let number = identity.root_number();
let retired = Arc::downgrade(&identity);
let ownership = super::ThreadPidOwnership {
tid_lease: Some(identity.acquire_role::<Tid>().unwrap()),
identity,
};
drop(reservation);
assert!(namespace.lookup(number).is_none());
drop(ownership);
assert!(retired.upgrade().is_none());
}
#[axtest::axtest]
fn inactive_one_shot_flag_consumption_is_read_only() {
assert!(super::inactive_one_shot_flag_consumption_is_read_only_for_test());
}
}
#[cfg(all(test, not(axtest)))]
mod tests {
use core::sync::atomic::{AtomicBool, Ordering};
use super::{NextSignalCheckBlock, ThreadSecurity};
use crate::{sync::Mutex, task::SeccompStateStore};
#[test]
fn seccomp_reads_use_an_immutable_snapshot_store() {
fn assert_pi_mutex<T>(_: &Mutex<T>) {}
fn assert_seccomp_store(_: &SeccompStateStore) {}
fn assert_security_lock_types(security: &ThreadSecurity) {
assert_seccomp_store(&security.seccomp);
assert_pi_mutex(&security.cred);
}
let _ = assert_security_lock_types as fn(&ThreadSecurity);
}
#[test]
fn old_global_signal_check_block_leaks_between_threads() {
static OLD_BLOCK_NEXT_SIGNAL_CHECK: AtomicBool = AtomicBool::new(false);
fn block_next_signal() {
OLD_BLOCK_NEXT_SIGNAL_CHECK.store(true, Ordering::SeqCst);
}
fn unblock_next_signal() -> bool {
OLD_BLOCK_NEXT_SIGNAL_CHECK.swap(false, Ordering::SeqCst)
}
block_next_signal();
assert!(unblock_next_signal());
assert!(!unblock_next_signal());
}
#[test]
fn per_thread_signal_check_block_is_isolated() {
let thread_a = NextSignalCheckBlock::new();
let thread_b = NextSignalCheckBlock::new();
thread_a.block();
assert!(!thread_b.unblock());
assert!(thread_a.unblock());
assert!(!thread_a.unblock());
}
}
#[cfg(axtest)]
#[axtest::axtest]
fn thread_state_creation_returns_allocation_failure() {
use ax_std::os::arceos::task::thread::ThreadAllocationProbe;
use crate::task::{PidReservation, PidReservationKind, Tgid};
let attempt = |failure| {
let reservation =
PidReservation::reserve(&ROOT_PID_NS, PidReservationKind::ProcessLeader).unwrap();
let identity = reservation.identity();
let tid = identity.acquire_role::<Tid>().unwrap();
let tgid = identity.acquire_role::<Tgid>().unwrap();
let process = crate::task::new_test_process_data(identity.clone(), tgid);
let retired = Arc::downgrade(&process);
let probe = ThreadAllocationProbe::fail_at(failure).unwrap();
let result = Thread::new(
identity,
tid,
process,
None,
Default::default(),
Scope::new(),
);
let attempts = probe.attempts();
drop(probe);
if failure == usize::MAX {
let thread = result.expect("private thread construction must succeed");
let probe = ThreadAllocationProbe::fail_at(0).unwrap();
let error = thread
.prepare_vfork_done()
.expect_err("vfork completion allocation failure must return ENOMEM");
assert_eq!(error.linux_errno(), syscalls::Errno::ENOMEM);
assert_eq!(probe.attempts(), 1);
drop(probe);
assert!(thread.lifecycle.vfork_done.lock().is_none());
thread.prepare_vfork_done().unwrap();
drop(thread);
} else {
let error = result
.err()
.expect("thread state allocation must return ENOMEM");
assert_eq!(error.linux_errno(), syscalls::Errno::ENOMEM);
assert_eq!(attempts, failure + 1);
}
assert!(
retired.upgrade().is_none(),
"failed thread state retained process ownership"
);
drop(reservation);
attempts
};
let attempts = attempt(usize::MAX);
assert!(attempts > 0);
for failure in 0..attempts {
attempt(failure);
}
}