Add `pidfd_send_signal` syscall

This commit is contained in:
li041 2026-01-28 03:13:50 +00:00 committed by Tate, Hongliang Tian
parent 3ae286980e
commit 84bced252b
14 changed files with 503 additions and 17 deletions

View File

@ -343,6 +343,7 @@ which are summarized in the table below.
| 327 | preadv2 | ✅ | [⚠️](syscall-flag-coverage/file-and-directory-operations/#preadv2-and-pwritev2) |
| 328 | pwritev2 | ✅ | [⚠️](syscall-flag-coverage/file-and-directory-operations/#preadv2-and-pwritev2) |
| 332 | statx | ✅ | [⚠️](syscall-flag-coverage/file-and-directory-operations/#statx) |
| 424 | pidfd_send_signal | ✅ | 💯 |
| 434 | pidfd_open | ✅ | 💯 |
| 435 | clone3 | ✅ | [⚠️](syscall-flag-coverage/process-and-thread-management/#clone-and-clone3) |
| 436 | close_range | ✅ | 💯 |

View File

@ -33,7 +33,7 @@ select(nfds, readfds, writefds, exceptfds, timeout);
pidfd_open(pid, flags = PIDFD_NONBLOCK);
// Obtain a duplicate of another process's file descriptor
pidfd_getfd(pid, targetfd, flags = 0);
pidfd_getfd(pidfd, targetfd, flags = 0);
// Close all file descriptors in the inclusive range [first, last]
close_range(first, last, flags = CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC);

View File

@ -16,6 +16,9 @@ kill(pid, sig);
// Send signal to a thread
tgkill(tgid, tid, sig);
// Send signal to the target process referred to by pidfd
pidfd_send_signal(pidfd, sig, info, flags = PIDFD_SIGNAL_THREAD | PIDFD_SIGNAL_THREAD_GROUP | PIDFD_SIGNAL_PROCESS_GROUP);
// Wait for signal
pause();

View File

@ -2,6 +2,7 @@
pub mod fault;
pub mod kernel;
pub mod raw;
pub mod user;
use core::{any::Any, fmt::Debug};

View File

@ -0,0 +1,40 @@
// SPDX-License-Identifier: MPL-2.0
use core::fmt::Debug;
use crate::process::signal::{c_types::siginfo_t, sig_num::SigNum, signals::Signal};
/// A signal that carries raw [`siginfo_t`] information.
#[derive(Clone, Copy)]
pub struct RawSignal {
info: siginfo_t,
}
impl Debug for RawSignal {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("RawSignal")
.field("signo", &self.info.si_signo)
.field("errno", &self.info.si_errno)
.field("code", &self.info.si_code)
.finish_non_exhaustive()
}
}
impl RawSignal {
/// Creates a signal that carries raw [`siginfo_t`] information.
///
/// The caller must ensure that the `info.si_signo` is a valid signal number.
pub fn new(info: siginfo_t) -> Self {
Self { info }
}
}
impl Signal for RawSignal {
fn num(&self) -> SigNum {
SigNum::from_u8(self.info.si_signo as u8)
}
fn to_info(&self) -> siginfo_t {
self.info
}
}

View File

@ -3,13 +3,16 @@
#![expect(dead_code)]
use super::Signal;
use crate::process::{
use crate::{
context::Context,
process::{
Pid, Uid,
signal::{
c_types::siginfo_t,
constants::{SI_QUEUE, SI_TKILL, SI_USER},
sig_num::SigNum,
},
},
};
#[derive(Debug, Clone, Copy)]
@ -37,6 +40,15 @@ impl UserSignal {
}
}
pub fn new_kill(num: SigNum, ctx: &Context) -> Self {
Self {
num,
kind: UserSignalKind::Kill,
pid: ctx.process.pid(),
uid: ctx.posix_thread.credentials().ruid(),
}
}
pub fn pid(&self) -> Pid {
self.pid
}

View File

@ -82,6 +82,7 @@ macro_rules! import_generic_syscall_entries {
open::sys_openat,
pidfd_getfd::sys_pidfd_getfd,
pidfd_open::sys_pidfd_open,
pidfd_send_signal::sys_pidfd_send_signal,
pipe::sys_pipe2,
ppoll::sys_ppoll,
prctl::sys_prctl,
@ -390,6 +391,7 @@ macro_rules! define_syscalls_with_generic_syscall_table {
SYS_PREADV2 = 286 => sys_preadv2(args[..6]);
SYS_PWRITEV2 = 287 => sys_pwritev2(args[..6]);
SYS_STATX = 291 => sys_statx(args[..5]);
SYS_PIDFD_SEND_SIGNAL = 424 => sys_pidfd_send_signal(args[..4]);
SYS_PIDFD_OPEN = 434 => sys_pidfd_open(args[..2]);
SYS_CLONE3 = 435 => sys_clone3(args[..2], &user_ctx);
SYS_CLOSE_RANGE = 436 => sys_close_range(args[..3]);

View File

@ -83,6 +83,7 @@ use super::{
pause::sys_pause,
pidfd_getfd::sys_pidfd_getfd,
pidfd_open::sys_pidfd_open,
pidfd_send_signal::sys_pidfd_send_signal,
pipe::{sys_pipe, sys_pipe2},
poll::sys_poll,
ppoll::sys_ppoll,
@ -405,6 +406,7 @@ impl_syscall_nums_and_dispatch_fn! {
SYS_PREADV2 = 327 => sys_preadv2(args[..6]);
SYS_PWRITEV2 = 328 => sys_pwritev2(args[..6]);
SYS_STATX = 332 => sys_statx(args[..5]);
SYS_PIDFD_SEND_SIGNAL = 424 => sys_pidfd_send_signal(args[..4]);
SYS_PIDFD_OPEN = 434 => sys_pidfd_open(args[..2]);
SYS_CLONE3 = 435 => sys_clone3(args[..2], &user_ctx);
SYS_CLOSE_RANGE = 436 => sys_close_range(args[..3]);

View File

@ -7,10 +7,7 @@ use crate::{
ProcessFilter, kill, kill_all, kill_group,
signal::{
sig_num::SigNum,
signals::{
Signal,
user::{UserSignal, UserSignalKind},
},
signals::{Signal, user::UserSignal},
},
},
};
@ -31,11 +28,7 @@ pub fn sys_kill(process_filter: u64, sig_num: u64, ctx: &Context) -> Result<Sysc
}
pub fn do_sys_kill(filter: ProcessFilter, sig_num: Option<SigNum>, ctx: &Context) -> Result<()> {
let signal = sig_num.map(|sig_num| {
let pid = ctx.process.pid();
let uid = ctx.posix_thread.credentials().ruid();
UserSignal::new(sig_num, UserSignalKind::Kill, pid, uid)
});
let signal = sig_num.map(|sig_num| UserSignal::new_kill(sig_num, ctx));
match filter {
ProcessFilter::Any => kill_all(signal, ctx)?,

View File

@ -95,6 +95,7 @@ mod open;
mod pause;
mod pidfd_getfd;
mod pidfd_open;
mod pidfd_send_signal;
mod pipe;
mod poll;
mod ppoll;

View File

@ -25,7 +25,7 @@ pub fn sys_pidfd_getfd(
let mut file_table = ctx.thread_local.borrow_file_table_mut();
let file = get_file_fast!(&mut file_table, pidfd);
let Some(pid_file) = file.downcast_ref::<PidFile>() else {
return_errno_with_message!(Errno::EINVAL, "the file is not a PID file");
return_errno_with_message!(Errno::EBADF, "the file is not a PID file");
};
let process = pid_file

View File

@ -0,0 +1,170 @@
// SPDX-License-Identifier: MPL-2.0
use ostd::mm::VmIo;
use crate::{
fs::file_table::{FileDesc, get_file_fast},
prelude::*,
process::{
Pgid, Pid, PidFile, kill, kill_group,
posix_thread::AsPosixThread,
signal::{
c_types::siginfo_t,
constants::SI_TKILL,
sig_num::SigNum,
signals::{Signal, raw::RawSignal, user::UserSignal},
},
tgkill,
},
syscall::SyscallReturn,
thread::Tid,
};
pub fn sys_pidfd_send_signal(
pidfd: FileDesc,
sig_num: u64,
info_ptr: Vaddr,
flags: u32,
ctx: &Context,
) -> Result<SyscallReturn> {
let flags = PidfdSendSignalFlags::try_from(flags)?;
let sig_num = SigNum::try_from(sig_num as u8)?;
debug!(
"pidfd={}, info_ptr={:#x}, flags={:?}",
pidfd, info_ptr, flags
);
let siginfo = read_siginfo_from_user(info_ptr, sig_num, ctx)?;
let signal = RawSignal::new(siginfo);
let target = get_target_from_pidfd(pidfd, flags, ctx)?;
let is_self = match &target {
SignalTarget::Thread { tid, tgid: _ } => *tid == ctx.posix_thread.tid(),
SignalTarget::Process { pid } => *pid == ctx.posix_thread.tid(),
SignalTarget::ProcessGroup { pgid: _ } => false,
};
if !is_self && (siginfo.si_code >= 0 || siginfo.si_code == SI_TKILL) {
return_errno_with_message!(
Errno::EPERM,
"signals with custom code can only be sent to the current thread/process"
);
}
match target {
SignalTarget::Thread { tid, tgid } => {
tgkill(tid, tgid, Some(Box::new(signal) as Box<dyn Signal>), ctx)?;
}
SignalTarget::Process { pid } => {
kill(pid, Some(Box::new(signal) as Box<dyn Signal>), ctx)?;
}
SignalTarget::ProcessGroup { pgid } => {
kill_group(pgid, Some(signal), ctx)?;
}
}
Ok(SyscallReturn::Return(0))
}
fn read_siginfo_from_user(info_ptr: Vaddr, sig_num: SigNum, ctx: &Context) -> Result<siginfo_t> {
if info_ptr != 0 {
let si = ctx.user_space().read_val::<siginfo_t>(info_ptr)?;
if si.si_signo != sig_num.as_u8() as i32 {
return_errno_with_message!(
Errno::EINVAL,
"`siginfo.si_signo` does not match the specified signal number"
);
}
Ok(si)
} else {
// If `info_ptr` is NULL, the kernel constructs a default `siginfo_t` structure
// whose fields match the values that are implicitly supplied when a signal is sent using the kill(2).
Ok(UserSignal::new_kill(sig_num, ctx).to_info())
}
}
fn get_target_from_pidfd(
pidfd: FileDesc,
flags: PidfdSendSignalFlags,
ctx: &Context,
) -> Result<SignalTarget> {
// Helper closures to create signal targets.
let thread_target = |tid: Tid, tgid: Pid| SignalTarget::Thread { tid, tgid };
let process_target = |pid: Pid| SignalTarget::Process { pid };
let group_target = |pgid: Pgid| SignalTarget::ProcessGroup { pgid };
let target = match pidfd {
PIDFD_SELF_THREAD => match flags {
PidfdSendSignalFlags::Default | PidfdSendSignalFlags::Thread => {
thread_target(ctx.posix_thread.tid(), ctx.process.pid())
}
PidfdSendSignalFlags::ThreadGroup => process_target(ctx.process.pid()),
PidfdSendSignalFlags::ProcessGroup => group_target(ctx.posix_thread.tid()),
},
PIDFD_SELF_THREAD_GROUP => match flags {
PidfdSendSignalFlags::Default | PidfdSendSignalFlags::ThreadGroup => {
process_target(ctx.process.pid())
}
PidfdSendSignalFlags::Thread => thread_target(
ctx.process.main_thread().as_posix_thread().unwrap().tid(),
ctx.process.pid(),
),
PidfdSendSignalFlags::ProcessGroup => group_target(ctx.process.pid()),
},
_ => {
let mut file_table = ctx.thread_local.borrow_file_table_mut();
let file = get_file_fast!(&mut file_table, pidfd);
// FIXME: On Linux, a pidfd can be also obtained by opening a `/proc/pid` directory.
// Reference: <https://man7.org/linux/man-pages/man2/pidfd_send_signal.2.html>
let Some(pid_file) = file.downcast_ref::<PidFile>() else {
return_errno_with_message!(Errno::EBADF, "the file is not a PID file");
};
let Some(process) = pid_file.process_opt() else {
return_errno_with_message!(Errno::ESRCH, "the target process has been reaped");
};
match flags {
PidfdSendSignalFlags::Default => {
// FIXME: On Linux, a pidfd can refer to either a process or a thread.
// We currently only support pidfds that refer to processes.
process_target(process.pid())
}
PidfdSendSignalFlags::Thread => {
// FIXME: On Linux, the signal can be sent to any thread.
// We currently only support the main thread.
thread_target(
process.main_thread().as_posix_thread().unwrap().tid(),
process.pid(),
)
}
PidfdSendSignalFlags::ThreadGroup => process_target(process.pid()),
PidfdSendSignalFlags::ProcessGroup => group_target(process.pid()),
}
}
};
Ok(target)
}
enum SignalTarget {
Thread { tid: Tid, tgid: Pid },
Process { pid: Pid },
ProcessGroup { pgid: Pgid },
}
// Reference: <https://elixir.bootlin.com/linux/v6.18/source/include/uapi/linux/pidfd.h#L19>.
#[derive(Clone, Copy, PartialEq, Eq, Debug, TryFromInt)]
#[repr(u32)]
enum PidfdSendSignalFlags {
Default = 0x0,
Thread = 0x1,
ThreadGroup = 0x2,
ProcessGroup = 0x4,
}
// Reference: <https://elixir.bootlin.com/linux/v6.18/source/include/uapi/linux/fcntl.h#L110>.
const PIDFD_SELF_THREAD: i32 = -10000;
const PIDFD_SELF_THREAD_GROUP: i32 = -10001;

View File

@ -70,6 +70,7 @@ sched/sched_param_idle
shm/posix_shm
signal_c/kill
signal_c/parent_death_signal
signal_c/pidfd_send_signal
signal_c/sigaltstack
signal_c/signal_fd
signal_c/signal_fpu

View File

@ -0,0 +1,260 @@
// SPDX-License-Identifier: MPL-2.0
#define _GNU_SOURCE
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <fcntl.h>
#include "../test.h"
// Reference: <https://elixir.bootlin.com/linux/v6.18/source/include/uapi/linux/fcntl.h#L110>.
#define PIDFD_SELF_THREAD -10000
#define PIDFD_SELF_THREAD_GROUP -10001
// Reference: <https://elixir.bootlin.com/linux/v6.18/source/include/uapi/linux/pidfd.h#L20>
#define PIDFD_SIGNAL_THREAD_GROUP (1UL << 1)
#define PIDFD_SIGNAL_PROCESS_GROUP (1UL << 2)
const int sig = SIGUSR1;
siginfo_t siginfo;
static int pidfd_open(pid_t pid, unsigned int flags)
{
return syscall(SYS_pidfd_open, pid, flags);
}
static int pidfd_send_signal(int pidfd, int sig, siginfo_t *info,
unsigned int flags)
{
return syscall(SYS_pidfd_send_signal, pidfd, sig, info, flags);
}
void setup_test_siginfo(siginfo_t *info, int sig, int si_code)
{
memset(info, 0, sizeof(*info));
info->si_signo = sig;
info->si_code = si_code;
info->si_pid = getpid();
info->si_uid = getuid();
}
/* ==========================
* Tests for processes
* ========================== */
int process_pid;
int process_pidfd;
FN_SETUP(create_process)
{
process_pid = CHECK(fork());
if (process_pid == 0) {
while (1) {
usleep(100);
}
}
process_pidfd = CHECK(pidfd_open(process_pid, 0));
}
END_SETUP()
FN_TEST(pidfd_send_signal_errnos)
{
setup_test_siginfo(&siginfo, sig, SI_USER);
TEST_ERRNO(pidfd_send_signal(process_pidfd, sig, &siginfo,
PIDFD_SIGNAL_PROCESS_GROUP),
EPERM);
setup_test_siginfo(&siginfo, sig, -666);
TEST_ERRNO(pidfd_send_signal(process_pidfd, sig + 1, &siginfo, 0),
EINVAL);
}
END_TEST()
FN_TEST(pidfd_send_signal_process)
{
TEST_SUCC(pidfd_send_signal(process_pidfd, sig, &siginfo, 0));
TEST_SUCC(waitid(P_PID, process_pid, NULL, WNOWAIT | WEXITED));
}
END_TEST()
FN_SETUP(cleanup_process)
{
CHECK(close(process_pidfd));
}
END_SETUP()
/* ==========================
* Tests for `PIDFD_SELF_*`
* ========================== */
// PIDFD_SELF_THREAD/PIDFD_SELF_THREAD_GROUP won't work with
// PIDFD_SIGNAL_PROCESS_GROUP unless the current process is
// the process group leader.
FN_TEST(pidfd_send_signal_self_process_group)
{
pid_t pid;
int stat;
pid = TEST_SUCC(fork());
if (pid == 0) {
setup_test_siginfo(&siginfo, SIGTERM, -666);
CHECK_WITH(pidfd_send_signal(PIDFD_SELF_THREAD, SIGTERM,
&siginfo,
PIDFD_SIGNAL_PROCESS_GROUP),
_ret == -1 && errno == ESRCH);
CHECK_WITH(pidfd_send_signal(PIDFD_SELF_THREAD_GROUP, SIGTERM,
&siginfo,
PIDFD_SIGNAL_PROCESS_GROUP),
_ret == -1 && errno == ESRCH);
exit(0);
}
TEST_RES(waitpid(pid, &stat, 0),
WIFEXITED(stat) && WEXITSTATUS(stat) == 0);
}
END_TEST()
void *pidfd_send_signal_self_child_thread(void *arg)
{
setup_test_siginfo(&siginfo, SIGTERM, -666);
CHECK_WITH(pidfd_send_signal(PIDFD_SELF_THREAD, SIGTERM, &siginfo,
PIDFD_SIGNAL_THREAD_GROUP),
_ret == 0);
return NULL;
}
// PIDFD_SELF_THREAD will work with PIDFD_SIGNAL_THREAD_GROUP
// even if the current thread is not the thread group leader.
FN_TEST(pidfd_send_signal_self_thread_group)
{
pid_t pid;
int stat;
pthread_t thread;
pid = TEST_SUCC(fork());
if (pid == 0) {
CHECK(pthread_create(&thread, NULL,
&pidfd_send_signal_self_child_thread,
NULL));
CHECK(pthread_join(thread, NULL));
exit(0);
}
TEST_RES(waitpid(pid, &stat, 0),
WIFSIGNALED(stat) && WTERMSIG(stat) == SIGTERM);
}
END_TEST()
void *pidfd_send_signal_self_process_group_child_thread(void *arg)
{
setup_test_siginfo(&siginfo, SIGTERM, -666);
CHECK_WITH(pidfd_send_signal(PIDFD_SELF_THREAD, SIGTERM, &siginfo,
PIDFD_SIGNAL_PROCESS_GROUP),
_ret == -1 && errno == ESRCH);
return NULL;
}
// PIDFD_SELF_THREAD won't work with PIDFD_SIGNAL_PROCESS_GROUP
// unless the current process is the process group leader and
// the current thread is the main thread.
FN_TEST(pidfd_send_signal_self_process_group_non_main_thread)
{
pid_t pid;
int stat;
pthread_t thread;
pid = TEST_SUCC(fork());
if (pid == 0) {
CHECK(setpgid(0, 0));
CHECK(pthread_create(
&thread, NULL,
pidfd_send_signal_self_process_group_child_thread,
NULL));
CHECK(pthread_join(thread, NULL));
exit(0);
}
TEST_RES(waitpid(pid, &stat, 0),
WIFEXITED(stat) && WEXITSTATUS(stat) == 0);
}
END_TEST()
// FIXME: Enable thread tests once pidfd for threads is supported
#ifndef __asterinas__
/* ==========================
* Tests for threads
* ========================== */
volatile sig_atomic_t signal_received = 0;
pthread_t thread;
volatile pid_t thread_tid;
int proc_fd;
int proc_task_fd;
void signal_handler(int signo)
{
signal_received = 1;
}
void *thread_func(void *arg)
{
thread_tid = syscall(SYS_gettid);
signal(sig, signal_handler);
while (!signal_received) {
usleep(100);
}
return NULL;
}
FN_SETUP(create_thread)
{
static char path[256];
CHECK(pthread_create(&thread, NULL, thread_func, NULL));
while (thread_tid == 0) {
usleep(100);
}
snprintf(path, sizeof(path), "/proc/%d/", thread_tid);
proc_fd = CHECK(open(path, O_DIRECTORY | O_CLOEXEC));
snprintf(path, sizeof(path), "/proc/%d/task/%d", getpid(), thread_tid);
proc_task_fd = CHECK(open(path, O_DIRECTORY | O_CLOEXEC));
}
END_SETUP()
FN_TEST(pidfd_send_signal_thread)
{
TEST_ERRNO(pidfd_send_signal(proc_task_fd, sig, &siginfo, 0), EBADF);
TEST_SUCC(pidfd_send_signal(proc_fd, sig, &siginfo, 0));
TEST_SUCC(pthread_join(thread, NULL));
}
END_TEST()
FN_SETUP(cleanup_thread)
{
CHECK(close(proc_fd));
CHECK(close(proc_task_fd));
}
END_SETUP()
#endif