Skip to content

Commit 5006c46

Browse files
committed
fix(signal): preserve ptrace child exit siginfo
Deliver ptrace exit notifications with a child-layout siginfo instead of a generic SI_USER payload. Snapshot status, namespace-visible identity, and exit accounting before publishing Zombie so a polling tracer cannot reap and unhash the task first. Match Linux exit accounting by combining the exiting task with already released siblings while excluding live threads, and add a dunitest regression for the observable SIGCHLD fields. Signed-off-by: longjin <longjin@dragonos.org>
1 parent ebd7ccf commit 5006c46

3 files changed

Lines changed: 141 additions & 28 deletions

File tree

kernel/src/process/manager/exit.rs

Lines changed: 85 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,39 @@ use crate::{
2828
kthread::KernelThreadMechanism,
2929
namespace::user_namespace::map_id_up,
3030
pid::{Pid, PidType},
31-
ptrace,
32-
resource::RUsageWho,
33-
ProcessControlBlock, ProcessFlags, ProcessManager, ProcessState, RawPid,
31+
ptrace, ProcessControlBlock, ProcessFlags, ProcessManager, ProcessState, RawPid,
3432
},
3533
sched::{cputime::ns_to_clock_t, SchedMode, __schedule_with_current},
3634
smp::core::smp_get_processor_id,
3735
syscall::user_access::clear_user_protected,
3836
};
3937

38+
struct ChildExitSiginfoSnapshot {
39+
code: i32,
40+
pid: RawPid,
41+
uid: u32,
42+
status: i32,
43+
utime: i64,
44+
stime: i64,
45+
}
46+
47+
impl ChildExitSiginfoSnapshot {
48+
fn into_siginfo(self, signal: Signal) -> SigInfo {
49+
SigInfo::new(
50+
signal,
51+
0,
52+
SigCode::Raw(self.code),
53+
SigType::SigChild {
54+
pid: self.pid,
55+
uid: self.uid,
56+
status: self.status,
57+
utime: self.utime,
58+
stime: self.stime,
59+
},
60+
)
61+
}
62+
}
63+
4064
impl ProcessManager {
4165
const DEFAULT_OVERFLOW_UID: u32 = 65534;
4266

@@ -49,11 +73,23 @@ impl ProcessManager {
4973
// A claimed old leader must retain all hashed identity until the exec
5074
// owner swaps it. In particular it is never autoreaped here.
5175
if claimed_exec_leader {
52-
current.set_exit_state_zombie();
76+
let ptrace_notification = {
77+
let _relation_guard = crate::process::PTRACE_RELATION_LOCK.lock_irqsave();
78+
let tracer = ptrace::ptracer_of_locked(current);
79+
let snapshot = tracer
80+
.as_ref()
81+
.map(|tracer| Self::child_exit_siginfo_snapshot(current, tracer));
82+
current.set_exit_state_zombie();
83+
tracer.map(|tracer| {
84+
(
85+
tracer,
86+
Signal::SIGCHLD as i32,
87+
snapshot.expect("ptrace tracer requires an exit siginfo snapshot"),
88+
)
89+
})
90+
};
5391
Self::wake_pidfd_pollers_for_task_exit(current);
54-
Self::notify_ptrace_parent(
55-
ptrace::ptracer_of(current).map(|tracer| (tracer, Signal::SIGCHLD as i32)),
56-
);
92+
Self::notify_ptrace_parent(ptrace_notification);
5793
current.mark_exit_notify_complete();
5894
let completed = sighand.complete_group_exec_leader_exit(current);
5995
debug_assert!(completed);
@@ -75,6 +111,13 @@ impl ProcessManager {
75111
// cannot lose the transition either.
76112
let _relation_guard = crate::process::PTRACE_RELATION_LOCK.lock_irqsave();
77113
let tracer = ptrace::ptracer_of_locked(current);
114+
// A polling tracer may reap and unhash the child as soon as
115+
// Zombie becomes visible. Freeze every child-derived field
116+
// before publishing Zombie; signal delivery must consume only
117+
// this immutable snapshot.
118+
let ptrace_snapshot = tracer
119+
.as_ref()
120+
.map(|tracer| Self::child_exit_siginfo_snapshot(current, tracer));
78121
let leader = current.is_thread_group_leader();
79122
let (group_empty, natural_token) = if leader {
80123
// Publish Zombie and inspect group emptiness under the same
@@ -107,7 +150,11 @@ impl ProcessManager {
107150
} else {
108151
Signal::SIGCHLD as i32
109152
};
110-
(tracer, signal)
153+
(
154+
tracer,
155+
signal,
156+
ptrace_snapshot.expect("ptrace tracer requires an exit siginfo snapshot"),
157+
)
111158
});
112159
(ptrace_notification, natural_token, autoreap_nonleader)
113160
};
@@ -134,20 +181,31 @@ impl ProcessManager {
134181
sighand.complete_group_exec_leader_exit(current);
135182
}
136183

137-
fn notify_ptrace_parent(tracer: Option<(Arc<ProcessControlBlock>, i32)>) {
138-
if let Some((tracer, signal)) = tracer {
184+
fn notify_ptrace_parent(
185+
tracer: Option<(Arc<ProcessControlBlock>, i32, ChildExitSiginfoSnapshot)>,
186+
) {
187+
if let Some((tracer, signal, snapshot)) = tracer {
139188
if signal > 0 {
140-
let _ = crate::ipc::kill::send_signal_to_pcb(tracer.clone(), Signal::from(signal));
189+
let signal = Signal::from(signal);
190+
let mut info = snapshot.into_siginfo(signal);
191+
if let Err(e) =
192+
signal.send_signal_info_to_pcb(Some(&mut info), tracer.clone(), PidType::TGID)
193+
{
194+
warn!(
195+
"failed to send ptrace exit signal to tracer {:?}: {:?}",
196+
tracer.raw_pid(),
197+
e
198+
);
199+
}
141200
}
142201
ProcessManager::wake_wait_parent(&tracer);
143202
}
144203
}
145204

146-
fn child_exit_siginfo(
205+
fn child_exit_siginfo_snapshot(
147206
child: &Arc<ProcessControlBlock>,
148207
parent: &Arc<ProcessControlBlock>,
149-
signal: Signal,
150-
) -> SigInfo {
208+
) -> ChildExitSiginfoSnapshot {
151209
let raw_status = child
152210
.sched_info()
153211
.state()
@@ -171,22 +229,18 @@ impl ProcessManager {
171229
let parent_user_ns = parent.cred().user_ns.clone();
172230
let uid = map_id_up(&parent_user_ns.inner.lock().uid_map, child_uid)
173231
.unwrap_or(Self::DEFAULT_OVERFLOW_UID);
174-
let rusage = child.get_rusage(RUsageWho::RUsageSelf).unwrap_or_default();
232+
let rusage = child.exit_notification_rusage();
175233
let utime = i64::try_from(ns_to_clock_t(rusage.ru_utime.to_ns())).unwrap_or(i64::MAX);
176234
let stime = i64::try_from(ns_to_clock_t(rusage.ru_stime.to_ns())).unwrap_or(i64::MAX);
177235

178-
SigInfo::new(
179-
signal,
180-
0,
181-
SigCode::Raw(code),
182-
SigType::SigChild {
183-
pid,
184-
uid,
185-
status,
186-
utime,
187-
stime,
188-
},
189-
)
236+
ChildExitSiginfoSnapshot {
237+
code,
238+
pid,
239+
uid,
240+
status,
241+
utime,
242+
stime,
243+
}
190244
}
191245

192246
/// Complete the unique natural-parent notification transaction. Build the
@@ -217,7 +271,10 @@ impl ProcessManager {
217271
// Snapshot every field before autoreap can unhash the child's PID.
218272
let notification = if exit_signal > 0 && !(autoreap && ignored) {
219273
let signal = Signal::from(exit_signal);
220-
Some((signal, Self::child_exit_siginfo(child, &parent, signal)))
274+
Some((
275+
signal,
276+
Self::child_exit_siginfo_snapshot(child, &parent).into_siginfo(signal),
277+
))
221278
} else {
222279
None
223280
};

kernel/src/process/resource.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,20 @@ impl ProcessControlBlock {
229229
usage
230230
}
231231

232+
/// CPU/resource usage reported with an exit notification.
233+
///
234+
/// Match Linux's `task_cputime(tsk) + tsk->signal->{u,s}time`: include the
235+
/// exiting task and already released siblings, but not siblings that are
236+
/// still alive. `exit_notify()` runs before `__exit_signal()` accounts the
237+
/// current task into `exited_thread_group_rusage`, so the current task is
238+
/// not counted twice.
239+
pub(crate) fn exit_notification_rusage(&self) -> RUsage {
240+
let leader = self.leader_for_rusage();
241+
let mut usage = *leader.exited_thread_group_rusage.lock();
242+
usage.add_assign_saturating(&self.task_rusage());
243+
usage
244+
}
245+
232246
pub fn add_exited_thread_group_rusage(&self, rusage: &RUsage) {
233247
let leader = self.leader_for_rusage();
234248
leader

user/apps/tests/dunitest/suites/normal/process_signal_fork.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ TEST(ProcessSignalFork, PosixForkAndJobControlUnavailableOnWindows) {
1919
#include <time.h>
2020
#include <unistd.h>
2121

22+
#ifndef PTRACE_TRACEME
23+
#define PTRACE_TRACEME 0
24+
#endif
25+
2226
namespace {
2327

2428
void SleepForMillis(long millis) {
@@ -358,6 +362,44 @@ TEST(ProcessSignalFork, SigchldInfoUsesGroupLeaderWhenLastNonleaderExits) {
358362
EXPECT_EQ(0, WEXITSTATUS(status));
359363
}
360364

365+
TEST(ProcessSignalFork, PtraceExitSignalCarriesChildSiginfo) {
366+
const uid_t expected_uid = getuid();
367+
sigset_t blocked;
368+
sigemptyset(&blocked);
369+
sigaddset(&blocked, SIGCHLD);
370+
SignalMaskGuard mask_guard(blocked);
371+
ASSERT_TRUE(mask_guard.valid()) << "sigprocmask failed: errno=" << errno << " ("
372+
<< strerror(errno) << ")";
373+
374+
const pid_t child = fork();
375+
ASSERT_GE(child, 0) << "fork failed: errno=" << errno << " (" << strerror(errno) << ")";
376+
if (child == 0) {
377+
if (syscall(SYS_ptrace, PTRACE_TRACEME, 0, 0, 0) != 0) {
378+
_exit(120);
379+
}
380+
_exit(47);
381+
}
382+
ChildProcessGuard child_guard(child);
383+
384+
siginfo_t info {};
385+
timespec timeout {};
386+
timeout.tv_sec = 5;
387+
ASSERT_EQ(SIGCHLD, sigtimedwait(&blocked, &info, &timeout))
388+
<< "sigtimedwait failed: errno=" << errno << " (" << strerror(errno) << ")";
389+
EXPECT_EQ(child, info.si_pid);
390+
EXPECT_EQ(expected_uid, info.si_uid);
391+
EXPECT_EQ(CLD_EXITED, info.si_code);
392+
EXPECT_EQ(47, info.si_status);
393+
EXPECT_GE(info.si_utime, 0);
394+
EXPECT_GE(info.si_stime, 0);
395+
396+
int status = 0;
397+
ASSERT_EQ(child, waitpid(child, &status, 0));
398+
child_guard.Release();
399+
ASSERT_TRUE(WIFEXITED(status)) << "child status=" << status;
400+
EXPECT_EQ(47, WEXITSTATUS(status));
401+
}
402+
361403
TEST(ProcessSignalFork, StopContinueRaceDoesNotLeaveChildStuck) {
362404
pid_t child = fork();
363405
ASSERT_GE(child, 0) << "fork failed: errno=" << errno << " (" << strerror(errno) << ")";

0 commit comments

Comments
 (0)