Index: lib/libc/sys/procctl.2 =================================================================== --- lib/libc/sys/procctl.2 +++ lib/libc/sys/procctl.2 @@ -391,6 +391,34 @@ See the note about sysctl .Dv kern.trap_enotcap above, which gives independent global control of signal delivery. +.It Dv PROC_PDEATHSIG_SET +Request the delivery of a signal when the parent of the calling +process exits. +.Fa id_type +must be +.Dv P_PID +and +.Fa id +must be the either caller's pid or 0, with no difference in effect. +The value is cleared for the children +of fork() and when executing set-user-ID or set-group-ID binaries. +.Fa arg +must point to a value of type int indicating the signal +that should be delivered to the caller. +Use zero to cancel a previously requested signal delivery. +.It Dv PROC_PDEATHSIG_GET +Query the current signal number that will be delivered when the parent +of the calling process exits. +.Fa id_type +must be +.Dv P_PID +and +.Fa id +must be the either caller's pid or 0, with no difference in effect. +.Fa arg +must point to a memory location that can hold a value of type int. +If signal delivery has not been requested, it will contain zero +on return. .El .Sh NOTES Disabling tracing on a process should not be considered a security @@ -487,6 +515,12 @@ or .Dv PROC_TRAPCAP_CTL request is invalid. +.It Bq Er EINVAL +The +.Dv PROC_PDEATHSIG_SET +or +.Dv PROC_PDEATHSIG_GET +request referenced an unsupported id, id_type or invalid signal number. .El .Sh SEE ALSO .Xr dtrace 1 , @@ -506,3 +540,8 @@ The reaper facility is based on a similar feature of Linux and DragonflyBSD, and first appeared in .Fx 10.2 . +The +.Dv PROC_PDEATHSIG_SET +facility is based on the prctl(PR_SET_PDEATHSIG, ...) feature of Linux, +and first appeared in +.Fx 12.0 . Index: sys/compat/freebsd32/freebsd32_misc.c =================================================================== --- sys/compat/freebsd32/freebsd32_misc.c +++ sys/compat/freebsd32/freebsd32_misc.c @@ -3352,7 +3352,7 @@ union { struct procctl_reaper_pids32 rp; } x32; - int error, error1, flags; + int error, error1, flags, signum; switch (uap->com) { case PROC_SPROTECT: @@ -3390,6 +3390,15 @@ case PROC_TRAPCAP_STATUS: data = &flags; break; + case PROC_PDEATHSIG_SET: + error = copyin(uap->data, &signum, sizeof(signum)); + if (error != 0) + return (error); + data = &signum; + break; + case PROC_PDEATHSIG_GET: + data = &signum; + break; default: return (EINVAL); } @@ -3410,6 +3419,10 @@ if (error == 0) error = copyout(&flags, uap->data, sizeof(flags)); break; + case PROC_PDEATHSIG_GET: + if (error == 0) + error = copyout(&signum, uap->data, sizeof(signum)); + break; } return (error); } Index: sys/kern/kern_exec.c =================================================================== --- sys/kern/kern_exec.c +++ sys/kern/kern_exec.c @@ -522,6 +522,10 @@ credential_changing |= will_transition; #endif + /* Don't inherit PROC_PDEATHSIG_SET value if setuid/setgid. */ + if (credential_changing) + imgp->proc->p_pdeathsig = 0; + if (credential_changing && #ifdef CAPABILITY_MODE ((oldcred->cr_flags & CRED_FLAG_CAPMODE) == 0) && Index: sys/kern/kern_exit.c =================================================================== --- sys/kern/kern_exit.c +++ sys/kern/kern_exit.c @@ -480,6 +480,12 @@ PROC_LOCK(q->p_reaper); pksignal(q->p_reaper, SIGCHLD, ksi1); PROC_UNLOCK(q->p_reaper); + } else if (q->p_pdeathsig > 0) { + /* + * The child asked to received a signal + * when we exit. + */ + kern_psignal(q, q->p_pdeathsig); } } else { /* @@ -520,6 +526,13 @@ */ while ((q = LIST_FIRST(&p->p_orphans)) != NULL) { PROC_LOCK(q); + /* + * If we are the real parent of this process + * but it has been reparented to a debugger, then + * check if it asked for a signal when we exit. + */ + if (q->p_pdeathsig > 0) + kern_psignal(q, q->p_pdeathsig); CTR2(KTR_PTRACE, "exit: pid %d, clearing orphan %d", p->p_pid, q->p_pid); clear_orphan(q); Index: sys/kern/kern_procctl.c =================================================================== --- sys/kern/kern_procctl.c +++ sys/kern/kern_procctl.c @@ -431,7 +431,7 @@ struct procctl_reaper_pids rp; struct procctl_reaper_kill rk; } x; - int error, error1, flags; + int error, error1, flags, signum; switch (uap->com) { case PROC_SPROTECT: @@ -467,6 +467,15 @@ case PROC_TRAPCAP_STATUS: data = &flags; break; + case PROC_PDEATHSIG_SET: + error = copyin(uap->data, &signum, sizeof(signum)); + if (error != 0) + return (error); + data = &signum; + break; + case PROC_PDEATHSIG_GET: + data = &signum; + break; default: return (EINVAL); } @@ -486,6 +495,10 @@ if (error == 0) error = copyout(&flags, uap->data, sizeof(flags)); break; + case PROC_PDEATHSIG_GET: + if (error == 0) + error = copyout(&signum, uap->data, sizeof(signum)); + break; } return (error); } @@ -527,6 +540,7 @@ struct pgrp *pg; struct proc *p; int error, first_error, ok; + int signum; bool tree_locked; switch (com) { @@ -537,10 +551,31 @@ case PROC_REAP_KILL: case PROC_TRACE_STATUS: case PROC_TRAPCAP_STATUS: + case PROC_PDEATHSIG_SET: + case PROC_PDEATHSIG_GET: if (idtype != P_PID) return (EINVAL); } + switch (com) { + case PROC_PDEATHSIG_SET: + signum = *(int *)data; + if ((id != 0 && id != td->td_proc->p_pid) || + (signum != 0 && !_SIG_VALID(signum))) + return (EINVAL); + PROC_LOCK(td->td_proc); + td->td_proc->p_pdeathsig = signum; + PROC_UNLOCK(td->td_proc); + return (0); + case PROC_PDEATHSIG_GET: + if ((id != 0 && id != td->td_proc->p_pid)) + return (EINVAL); + PROC_LOCK(td->td_proc); + *(int *)data = td->td_proc->p_pdeathsig; + PROC_UNLOCK(td->td_proc); + return (0); + } + switch (com) { case PROC_SPROTECT: case PROC_REAP_STATUS: Index: sys/kern/kern_thread.c =================================================================== --- sys/kern/kern_thread.c +++ sys/kern/kern_thread.c @@ -91,7 +91,7 @@ "struct proc KBI p_pid"); _Static_assert(offsetof(struct proc, p_filemon) == 0x3d0, "struct proc KBI p_filemon"); -_Static_assert(offsetof(struct proc, p_comm) == 0x3e0, +_Static_assert(offsetof(struct proc, p_comm) == 0x3e4, "struct proc KBI p_comm"); _Static_assert(offsetof(struct proc, p_emuldata) == 0x4b8, "struct proc KBI p_emuldata"); @@ -111,7 +111,7 @@ "struct proc KBI p_pid"); _Static_assert(offsetof(struct proc, p_filemon) == 0x27c, "struct proc KBI p_filemon"); -_Static_assert(offsetof(struct proc, p_comm) == 0x288, +_Static_assert(offsetof(struct proc, p_comm) == 0x28c, "struct proc KBI p_comm"); _Static_assert(offsetof(struct proc, p_emuldata) == 0x314, "struct proc KBI p_emuldata"); Index: sys/sys/proc.h =================================================================== --- sys/sys/proc.h +++ sys/sys/proc.h @@ -624,6 +624,7 @@ u_int p_treeflag; /* (e) P_TREE flags */ int p_pendingexits; /* (c) Count of pending thread exits. */ struct filemon *p_filemon; /* (c) filemon-specific data. */ + int p_pdeathsig; /* (c) Signal from parent on exit. */ /* End area that is zeroed on creation. */ #define p_endzero p_magic Index: sys/sys/procctl.h =================================================================== --- sys/sys/procctl.h +++ sys/sys/procctl.h @@ -51,6 +51,8 @@ #define PROC_TRACE_STATUS 8 /* query tracing status */ #define PROC_TRAPCAP_CTL 9 /* trap capability errors */ #define PROC_TRAPCAP_STATUS 10 /* query trap capability status */ +#define PROC_PDEATHSIG_SET 11 /* set parent death signal */ +#define PROC_PDEATHSIG_GET 12 /* get parent death signal */ /* Operations for PROC_SPROTECT (passed in integer arg). */ #define PPROT_OP(x) ((x) & 0xf) Index: tests/sys/kern/Makefile =================================================================== --- tests/sys/kern/Makefile +++ tests/sys/kern/Makefile @@ -16,11 +16,13 @@ ATF_TESTS_C+= unix_passfd_test TEST_METADATA.unix_seqpacket_test+= timeout="15" ATF_TESTS_C+= waitpid_nohang +ATF_TESTS_C+= pdeathsig ATF_TESTS_SH+= coredump_phnum_test BINDIR= ${TESTSDIR} PROGS+= coredump_phnum_helper +PROGS+= pdeathsig_helper CFLAGS.sys_getrandom+= -I${SRCTOP}/sys/contrib/zstd/lib LIBADD.sys_getrandom+= zstd Index: tests/sys/kern/pdeathsig.c =================================================================== --- /dev/null +++ tests/sys/kern/pdeathsig.c @@ -0,0 +1,328 @@ +/*- + * Copyright (c) 2018 Thomas Munro + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static volatile sig_atomic_t got_signal = 0; + +static void +my_signal_handler(int signum) +{ + if (signum == SIGINFO) + got_signal = 1; +} + +ATF_TC_WITHOUT_HEAD(arg_validation); +ATF_TC_BODY(arg_validation, tc) +{ + int signum; + int rc; + + /* bad signal */ + signum = 8888; + rc = procctl(P_PID, 0, PROC_PDEATHSIG_SET, &signum); + ATF_CHECK_EQ(-1, rc); + ATF_CHECK_EQ(EINVAL, errno); + + /* bad id type */ + signum = SIGINFO; + rc = procctl(8888, 0, PROC_PDEATHSIG_SET, &signum); + ATF_CHECK_EQ(-1, rc); + ATF_CHECK_EQ(EINVAL, errno); + + /* bad id (pid that doesn't match mine or zero) */ + signum = SIGINFO; + rc = procctl(P_PID, (((getpid() + 1) % 10) + 100), + PROC_PDEATHSIG_SET, &signum); + ATF_CHECK_EQ(-1, rc); + ATF_CHECK_EQ(EINVAL, errno); + + /* null pointer */ + signum = SIGINFO; + rc = procctl(P_PID, 0, PROC_PDEATHSIG_SET, NULL); + ATF_CHECK_EQ(-1, rc); + ATF_CHECK_EQ(EFAULT, errno); + + /* good (pid == 0) */ + signum = SIGINFO; + rc = procctl(P_PID, 0, PROC_PDEATHSIG_SET, &signum); + ATF_CHECK_EQ(0, rc); + + /* good (pid == my pid) */ + signum = SIGINFO; + rc = procctl(P_PID, getpid(), PROC_PDEATHSIG_SET, &signum); + ATF_CHECK_EQ(0, rc); + + /* check that we can read the signal number back */ + signum = 0xdeadbeef; + rc = procctl(P_PID, 0, PROC_PDEATHSIG_GET, &signum); + ATF_CHECK_EQ(0, rc); + ATF_CHECK_EQ(SIGINFO, signum); +} + +ATF_TC_WITHOUT_HEAD(fork_no_inherit); +ATF_TC_BODY(fork_no_inherit, tc) +{ + int signum; + int rc; + + /* request a signal on parent death in the parent */ + signum = SIGINFO; + rc = procctl(P_PID, 0, PROC_PDEATHSIG_SET, &signum); + + rc = fork(); + ATF_REQUIRE(rc != -1); + if (rc == 0) { + /* check that we didn't inherit the setting */ + signum = 0xdeadbeef; + rc = procctl(P_PID, 0, PROC_PDEATHSIG_GET, &signum); + assert(rc == 0); + assert(signum == 0); + } else { + int status; + + /* wait for the child to exit successfully */ + waitpid(rc, &status, 0); + ATF_CHECK_EQ(0, status); + } +} + +ATF_TC_WITHOUT_HEAD(exec_inherit); +ATF_TC_BODY(exec_inherit, tc) +{ + int rc; + + rc = fork(); + ATF_REQUIRE(rc != -1); + if (rc == 0) { + char exec_path[1024]; + int signum; + + /* compute the path of the helper executable */ + snprintf(exec_path, sizeof(exec_path), "%s/pdeathsig_helper", + atf_tc_get_config_var(tc, "srcdir")); + + /* request a signal on parent death and register a handler */ + signum = SIGINFO; + rc = procctl(P_PID, 0, PROC_PDEATHSIG_SET, &signum); + assert(rc == 0); + + /* execute helper program: it asserts that it has the setting */ + rc = execl(exec_path, exec_path, NULL); + assert(rc == 0); + } else { + int status; + + /* wait for the child to exit successfully */ + waitpid(rc, &status, 0); + ATF_CHECK_EQ(0, status); + } +} + +ATF_TC_WITHOUT_HEAD(signal_delivered); +ATF_TC_BODY(signal_delivered, tc) +{ + int signum; + int rc; + int pipe_ca[2]; + int pipe_cb[2]; + char buffer; + + rc = pipe(pipe_ca); + ATF_REQUIRE(rc == 0); + rc = pipe(pipe_cb); + ATF_REQUIRE(rc == 0); + + rc = fork(); + ATF_REQUIRE(rc != -1); + if (rc == 0) { + rc = fork(); + assert(rc >= 0); + if (rc == 0) { + /* process C */ + + /* request a signal on parent death and register a handler */ + signum = SIGINFO; + signal(signum, my_signal_handler); + rc = procctl(P_PID, 0, PROC_PDEATHSIG_SET, &signum); + assert(rc == 0); + + /* tell B that we're ready for it to exit now */ + rc = write(pipe_cb[1], ".", 1); + assert(rc == 1); + + /* wait for parent to die and signal us... */ + while (got_signal == 0) + sleep(1); + + assert(got_signal == 1); + + /* tell A the test passed */ + rc = write(pipe_ca[1], ".", 1); + assert(rc == 1); + return; + } + + /* process B */ + + /* wait for C to tell us it is ready for us to exit */ + rc = read(pipe_cb[0], &buffer, 1); + assert(rc == 1); + + /* now we exit so that C gets a signal */ + return; + } + /* process A */ + + /* wait for C to tell us the test passed */ + rc = read(pipe_ca[0], &buffer, 1); + ATF_CHECK_EQ(1, rc); +} + +ATF_TC_WITHOUT_HEAD(signal_delivered_ptrace); +ATF_TC_BODY(signal_delivered_ptrace, tc) +{ + int signum; + int rc; + int pipe_ca[2]; + int pipe_db[2]; + int pipe_cd[2]; + char buffer; + int status; + + rc = pipe(pipe_ca); + ATF_REQUIRE(rc == 0); + rc = pipe(pipe_db); + ATF_REQUIRE(rc == 0); + rc = pipe(pipe_cd); + ATF_REQUIRE(rc == 0); + + rc = fork(); + ATF_REQUIRE(rc != -1); + if (rc == 0) { + pid_t c_pid; + + /* process B */ + + rc = fork(); + assert(rc >= 0); + if (rc == 0) { + /* process C */ + + /* request a signal on parent death and register a handler */ + signum = SIGINFO; + signal(signum, my_signal_handler); + rc = procctl(P_PID, 0, PROC_PDEATHSIG_SET, &signum); + assert(rc == 0); + + /* tell D we are ready for it to attach */ + rc = write(pipe_cd[1], ".", 1); + assert(rc == 1); + + /* wait for the death signal... */ + while (got_signal == 0) + sleep(1); + + assert(got_signal == 1); + + /* tell A the test passed */ + rc = write(pipe_ca[1], ".", 1); + assert(rc == 1); + return; + } + c_pid = rc; + + + /* fork another process to ptrace C */ + rc = fork(); + assert(rc >= 0); + if (rc == 0) { + + /* process D */ + rc = ptrace(PT_ATTACH, c_pid, 0, 0); + assert(rc == 0); + + waitpid(c_pid, &status, 0); + assert(WIFSTOPPED(status)); + assert(WSTOPSIG(status) == SIGSTOP); + + rc = ptrace(PT_CONTINUE, c_pid, (caddr_t) 1, 0); + assert(rc == 0); + + /* tell B that we're ready for it to exit now */ + rc = write(pipe_db[1], ".", 1); + assert(rc == 1); + + waitpid(c_pid, &status, 0); + assert(WIFSTOPPED(status)); + assert(WSTOPSIG(status) == SIGINFO); + + rc = ptrace(PT_CONTINUE, c_pid, (caddr_t) 1, + WSTOPSIG(status)); + assert(rc == 0); + + ptrace(PT_DETACH, c_pid, 0, 0); + + return; + } + + /* wait for D to tell us it is ready for us to exit */ + rc = read(pipe_db[0], &buffer, 1); + assert(rc == 1); + + /* now we exit so that C gets a signal */ + return; + } + + /* process A */ + + /* wait for C to tell us the test passed */ + rc = read(pipe_ca[0], &buffer, 1); + ATF_CHECK_EQ(1, rc); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, arg_validation); + ATF_TP_ADD_TC(tp, fork_no_inherit); + ATF_TP_ADD_TC(tp, exec_inherit); + ATF_TP_ADD_TC(tp, signal_delivered); + ATF_TP_ADD_TC(tp, signal_delivered_ptrace); + return (atf_no_error()); +} Index: tests/sys/kern/pdeathsig_helper.c =================================================================== --- /dev/null +++ tests/sys/kern/pdeathsig_helper.c @@ -0,0 +1,21 @@ +#include +#include +#include + +int main(int argc, char **argv) +{ + int signum; + int rc; + + /* + * This program is executed by the pdeathsig test + * to check if the PROC_PDEATHSIG_SET setting was + * inherited. + */ + signum = 0xdeadbeef; + rc = procctl(P_PID, 0, PROC_PDEATHSIG_GET, &signum); + assert(rc == 0); + assert(signum == SIGINFO); + + return 0; +}