Skip to content

Commit dae8113

Browse files
committed
fix(process): report leader identity in SIGCHLD
Build child-exit siginfo from the explicitly notified group leader instead of the thread that happens to complete the notification transaction. Map the leader PID and UID through the parent's namespaces and report Linux-compatible CLD status and CPU times. Add a deterministic multithreaded regression test that keeps the leader and final worker on one CPU, waits for the leader to become a zombie, differentiates the worker credentials, and verifies the parent's SIGCHLD metadata. Signed-off-by: longjin <longjin@dragonos.org>
1 parent b9f516a commit dae8113

4 files changed

Lines changed: 222 additions & 6 deletions

File tree

kernel/src/ipc/signal_types.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,26 @@ impl SigInfo {
572572
},
573573
},
574574
},
575+
SigType::SigChild {
576+
pid,
577+
uid,
578+
status,
579+
utime,
580+
stime,
581+
} => PosixSigInfo {
582+
si_signo: self.sig_no,
583+
si_errno: self.errno,
584+
si_code: self.sig_code.as_i32(),
585+
_sifields: PosixSiginfoFields {
586+
_sigchld: PosixSiginfoSigchld {
587+
si_pid: pid.data() as i32,
588+
si_uid: uid,
589+
si_status: status,
590+
si_utime: utime,
591+
si_stime: stime,
592+
},
593+
},
594+
},
575595
SigType::Alarm(pid) => PosixSigInfo {
576596
si_signo: self.sig_no,
577597
si_errno: self.errno,
@@ -679,6 +699,14 @@ pub enum SigType {
679699
uid: u32,
680700
sigval: PosixSigval,
681701
},
702+
/// Kernel-generated child state notification (CLD_*).
703+
SigChild {
704+
pid: RawPid,
705+
uid: u32,
706+
status: i32,
707+
utime: i64,
708+
stime: i64,
709+
},
682710
Alarm(RawPid),
683711
/// POSIX interval timer 发送的信号(SI_TIMER)。
684712
/// - `timerid`: 对应用户态 `siginfo_t::si_timerid`
@@ -708,7 +736,6 @@ pub enum SigType {
708736
// 后续完善下列中的具体字段
709737
// Timer,
710738
// Rt,
711-
// SigChild,
712739
// SigFault,
713740
// SigSys,
714741
}

kernel/src/process/exit.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ fn wstatus_to_waitid_status(raw_wstatus: i32) -> i32 {
3131
}
3232

3333
#[inline(always)]
34-
fn wstatus_to_waitid_exit_info(raw_wstatus: i32) -> (i32, i32) {
34+
pub(crate) fn wstatus_to_waitid_exit_info(raw_wstatus: i32) -> (i32, i32) {
3535
let signal = raw_wstatus & 0x7f;
3636
if signal == 0 {
3737
(

kernel/src/process/manager/exit.rs

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,32 @@ use crate::{
1414
},
1515
driver::tty::tty_job_control::TtyJobCtrlManager,
1616
exception::InterruptArch,
17-
ipc::sighand::{NaturalParentNotifyToken, ReapTransition},
17+
ipc::{
18+
sighand::{NaturalParentNotifyToken, ReapTransition},
19+
signal_types::{SigCode, SigInfo, SigType},
20+
},
1821
libs::futex::{
1922
constant::{FutexFlag, FUTEX_BITSET_MATCH_ANY},
2023
futex::{Futex, RobustListHead},
2124
},
2225
mm::IDLE_PROCESS_ADDRESS_SPACE,
2326
process::{
27+
exit::wstatus_to_waitid_exit_info,
2428
kthread::KernelThreadMechanism,
29+
namespace::user_namespace::map_id_up,
2530
pid::{Pid, PidType},
26-
ptrace, ProcessControlBlock, ProcessFlags, ProcessManager, ProcessState, RawPid,
31+
ptrace,
32+
resource::RUsageWho,
33+
ProcessControlBlock, ProcessFlags, ProcessManager, ProcessState, RawPid,
2734
},
28-
sched::{SchedMode, __schedule_with_current},
35+
sched::{cputime::ns_to_clock_t, SchedMode, __schedule_with_current},
2936
smp::core::smp_get_processor_id,
3037
syscall::user_access::clear_user_protected,
3138
};
3239

3340
impl ProcessManager {
41+
const DEFAULT_OVERFLOW_UID: u32 = 65534;
42+
3443
/// Notify the parent process after a child process exits.
3544
#[inline(never)]
3645
fn exit_notify(current: &Arc<ProcessControlBlock>) {
@@ -169,6 +178,44 @@ impl ProcessManager {
169178
}
170179
}
171180

181+
fn child_exit_siginfo(
182+
child: &Arc<ProcessControlBlock>,
183+
parent: &Arc<ProcessControlBlock>,
184+
signal: Signal,
185+
) -> SigInfo {
186+
let raw_status = child
187+
.sched_info()
188+
.state()
189+
.raw_wstatus()
190+
.expect("natural-parent exit notification requires an exited child")
191+
as i32;
192+
let (status, code) = wstatus_to_waitid_exit_info(raw_status);
193+
let pid = child
194+
.task_pid_nr_ns(PidType::PID, Some(parent.active_pid_ns()))
195+
.unwrap_or(RawPid::new(0));
196+
let child_uid =
197+
u32::try_from(child.cred().uid.data()).unwrap_or(Self::DEFAULT_OVERFLOW_UID);
198+
let parent_user_ns = parent.cred().user_ns.clone();
199+
let uid = map_id_up(&parent_user_ns.inner.lock().uid_map, child_uid)
200+
.unwrap_or(Self::DEFAULT_OVERFLOW_UID);
201+
let rusage = child.get_rusage(RUsageWho::RUsageSelf).unwrap_or_default();
202+
let utime = i64::try_from(ns_to_clock_t(rusage.ru_utime.to_ns())).unwrap_or(i64::MAX);
203+
let stime = i64::try_from(ns_to_clock_t(rusage.ru_stime.to_ns())).unwrap_or(i64::MAX);
204+
205+
SigInfo::new(
206+
signal,
207+
0,
208+
SigCode::Raw(code),
209+
SigType::SigChild {
210+
pid,
211+
uid,
212+
status,
213+
utime,
214+
stime,
215+
},
216+
)
217+
}
218+
172219
/// Complete the unique natural-parent notification transaction. Signal
173220
/// delivery and an optional owner-authorized autoreap happen while the
174221
/// phase is Pending; Done is then published before the final parent wake.
@@ -193,8 +240,10 @@ impl ProcessManager {
193240
let autoreap = exit_signal == Signal::SIGCHLD as i32 && (ignored || no_cldwait);
194241

195242
if exit_signal > 0 && !(autoreap && ignored) {
243+
let signal = Signal::from(exit_signal);
244+
let mut info = Self::child_exit_siginfo(child, &parent, signal);
196245
if let Err(e) =
197-
crate::ipc::kill::send_signal_to_pcb(parent.clone(), Signal::from(exit_signal))
246+
signal.send_signal_info_to_pcb(Some(&mut info), parent.clone(), PidType::TGID)
198247
{
199248
warn!(
200249
"failed to send exit signal for {:?} to parent {:?}: {:?}",

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

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ TEST(ProcessSignalFork, PosixForkAndJobControlUnavailableOnWindows) {
1212
#include <pthread.h>
1313
#include <sched.h>
1414
#include <signal.h>
15+
#include <stdio.h>
1516
#include <string.h>
17+
#include <sys/syscall.h>
1618
#include <sys/wait.h>
1719
#include <time.h>
1820
#include <unistd.h>
@@ -89,6 +91,90 @@ class ChildProcessGuard {
8991
pid_t child_;
9092
};
9193

94+
class SignalMaskGuard {
95+
public:
96+
explicit SignalMaskGuard(const sigset_t& blocked) : valid_(false) {
97+
valid_ = sigprocmask(SIG_BLOCK, &blocked, &old_mask_) == 0;
98+
}
99+
SignalMaskGuard(const SignalMaskGuard&) = delete;
100+
SignalMaskGuard& operator=(const SignalMaskGuard&) = delete;
101+
102+
~SignalMaskGuard() {
103+
if (valid_) {
104+
sigprocmask(SIG_SETMASK, &old_mask_, nullptr);
105+
}
106+
}
107+
108+
bool valid() const { return valid_; }
109+
110+
private:
111+
sigset_t old_mask_ {};
112+
bool valid_;
113+
};
114+
115+
struct LastThreadExitArgs {
116+
int ready_fd;
117+
pid_t leader_tid;
118+
};
119+
120+
bool PinCurrentTaskToOneAllowedCpu() {
121+
cpu_set_t available;
122+
CPU_ZERO(&available);
123+
if (sched_getaffinity(0, sizeof(available), &available) != 0) {
124+
return false;
125+
}
126+
127+
for (int cpu = 0; cpu < CPU_SETSIZE; ++cpu) {
128+
if (!CPU_ISSET(cpu, &available)) {
129+
continue;
130+
}
131+
cpu_set_t target;
132+
CPU_ZERO(&target);
133+
CPU_SET(cpu, &target);
134+
return sched_setaffinity(0, sizeof(target), &target) == 0;
135+
}
136+
errno = EINVAL;
137+
return false;
138+
}
139+
140+
char ReadProcTaskState(pid_t tgid, pid_t tid) {
141+
char path[128] {};
142+
snprintf(path, sizeof(path), "/proc/%d/task/%d/stat", tgid, tid);
143+
FILE* stat_file = fopen(path, "r");
144+
if (stat_file == nullptr) {
145+
return '\0';
146+
}
147+
148+
char line[1024] {};
149+
char state = '\0';
150+
if (fgets(line, sizeof(line), stat_file) != nullptr) {
151+
char* comm_end = strrchr(line, ')');
152+
if (comm_end != nullptr && comm_end[1] == ' ') {
153+
state = comm_end[2];
154+
}
155+
}
156+
fclose(stat_file);
157+
return state;
158+
}
159+
160+
void* ExitAfterLeaderThread(void* raw_args) {
161+
auto* args = static_cast<LastThreadExitArgs*>(raw_args);
162+
const int ready_fd = args->ready_fd;
163+
const pid_t leader_tid = args->leader_tid;
164+
WriteIntOrExit(ready_fd, static_cast<int>(syscall(SYS_gettid)));
165+
166+
for (int i = 0; i < 5000; ++i) {
167+
if (ReadProcTaskState(leader_tid, leader_tid) == 'Z') {
168+
if (getuid() == 0 && syscall(SYS_setuid, 1234) != 0) {
169+
_exit(124);
170+
}
171+
return nullptr;
172+
}
173+
SleepForMillis(1);
174+
}
175+
_exit(123);
176+
}
177+
92178
void* PauseForeverThread(void*) {
93179
for (;;) {
94180
pause();
@@ -121,6 +207,60 @@ void RunMultithreadedSignalChild(int ready_fd) {
121207

122208
} // namespace
123209

210+
TEST(ProcessSignalFork, SigchldInfoUsesGroupLeaderWhenLastNonleaderExits) {
211+
const uid_t leader_uid = getuid();
212+
sigset_t blocked;
213+
sigemptyset(&blocked);
214+
sigaddset(&blocked, SIGCHLD);
215+
SignalMaskGuard mask_guard(blocked);
216+
ASSERT_TRUE(mask_guard.valid()) << "sigprocmask failed: errno=" << errno << " ("
217+
<< strerror(errno) << ")";
218+
219+
int ready_pipe[2] = {-1, -1};
220+
ASSERT_EQ(0, pipe(ready_pipe)) << "pipe failed: " << strerror(errno);
221+
222+
pid_t child = fork();
223+
ASSERT_GE(child, 0) << "fork failed: errno=" << errno << " (" << strerror(errno) << ")";
224+
if (child == 0) {
225+
close(ready_pipe[0]);
226+
if (!PinCurrentTaskToOneAllowedCpu()) {
227+
_exit(120);
228+
}
229+
LastThreadExitArgs args {ready_pipe[1], getpid()};
230+
pthread_t worker {};
231+
if (pthread_create(&worker, nullptr, ExitAfterLeaderThread, &args) != 0) {
232+
_exit(121);
233+
}
234+
syscall(SYS_exit, 0);
235+
_exit(122);
236+
}
237+
ChildProcessGuard child_guard(child);
238+
239+
close(ready_pipe[1]);
240+
int worker_tid = -1;
241+
ASSERT_TRUE(ReadInt(ready_pipe[0], &worker_tid)) << "worker did not report its tid";
242+
close(ready_pipe[0]);
243+
ASSERT_GT(worker_tid, 0);
244+
ASSERT_NE(worker_tid, child);
245+
246+
siginfo_t info {};
247+
timespec timeout {};
248+
timeout.tv_sec = 5;
249+
ASSERT_EQ(SIGCHLD, sigtimedwait(&blocked, &info, &timeout))
250+
<< "sigtimedwait failed: errno=" << errno << " (" << strerror(errno) << ")";
251+
EXPECT_EQ(child, info.si_pid);
252+
EXPECT_NE(worker_tid, info.si_pid);
253+
EXPECT_EQ(leader_uid, info.si_uid);
254+
EXPECT_EQ(CLD_EXITED, info.si_code);
255+
EXPECT_EQ(0, info.si_status);
256+
257+
int status = 0;
258+
ASSERT_EQ(child, waitpid(child, &status, 0));
259+
child_guard.Release();
260+
ASSERT_TRUE(WIFEXITED(status)) << "child status=" << status;
261+
EXPECT_EQ(0, WEXITSTATUS(status));
262+
}
263+
124264
TEST(ProcessSignalFork, StopContinueRaceDoesNotLeaveChildStuck) {
125265
pid_t child = fork();
126266
ASSERT_GE(child, 0) << "fork failed: errno=" << errno << " (" << strerror(errno) << ")";

0 commit comments

Comments
 (0)