Index: libexec/Makefile =================================================================== --- libexec/Makefile +++ libexec/Makefile @@ -7,6 +7,7 @@ ${_atrun} \ ${_blacklistd-helper} \ ${_comsat} \ + corestop \ ${_dma} \ flua \ getty \ Index: libexec/corestop/Makefile =================================================================== --- /dev/null +++ libexec/corestop/Makefile @@ -0,0 +1,5 @@ +# $FreeBSD$ + +SCRIPTS= corestop.sh + +.include Index: libexec/corestop/corestop.sh =================================================================== --- /dev/null +++ libexec/corestop/corestop.sh @@ -0,0 +1,92 @@ +#!/bin/sh + +die() { + echo "$*; exiting" 1>&2 + exit 1 +} + +PID="$1" +EUID="$2" + +if [ "$3" != "su-done" ]; then + # Reexecute ourselves as user $EUID. + LOGIN=$(getent passwd "${EUID}" | cut -f 1 -d ':') + + if [ -z "${LOGIN}" ]; then + die "cannot retrieve login name for EUID ${EUID}" + exit 1 + fi + + exec su -l ${LOGIN} -c "${0} ${PID} ${EUID} su-done" + + # This cannot happen, but hey, what if there's a bug in sh(1)? + exit 1 +fi + +# From this point on we're running as user $EUID. + +if [ "${EUID}" -ne $(id -u) ]; then + # Make extra sure it doesn't happen. + die "not running as EUID ${EUID}" +fi + +# Get the process working directory. +CWD=$(procstat -f "${PID}" | awk '$3 == "cwd" { print $10 }') +[ -n "${CWD}" ] || die "unable to query working directory" + +# Get process name. +COMM=$(procstat -b "${PID}" | awk 'NR == 2 { print $2 }') +[ -n "${CWD}" ] || die "unable to query process name" + +CORETMP=$(mktemp -d -t corestop.XXXXXX) +if [ $? -ne 0 -o -z "${CORETMP}" ]; then + die "mktemp failed" +fi + +cd "${CORETMP}" + +show() { + echo "\$ $@" + $@ + echo +} + +{ + echo bt | show /usr/bin/lldb -p "${PID}" + #echo bt | show /usr/local/bin/gdb -q -p "${PID}" + + show procstat arguments "${PID}" + show procstat environment "${PID}" + show procstat vm "${PID}" + show procstat files "${PID}" + show procstat threads "${PID}" + show procstat basic "${PID}" + show procstat binary "${PID}" + show procstat credentials "${PID}" + show procstat auxv "${PID}" + show procstat signal "${PID}" + show procstat tsignal "${PID}" + show procstat kstack "${PID}" + show procstat rlimit "${PID}" + # XXX: The one below doesn't seem to work. + #show procstat ptlwpinfo "${PID}" + show procstat rusage "${PID}" + show procstat cpuset "${PID}" + +} > "${CORETMP}/core.txt" 2>&1 + +# Get list of files mapped as executable into the process virtual memory, +# ie the main executable and its shared libraries. +FILES=$(procstat -v "${PID}" | awk '$4 ~ /x/ && $10 == "vn" {print $11 }' | sort -u | xargs) + +gcore -c "${COMM}.core" "${PID}" + +COREDIR="${COMM}.${PID}.coredir" +CORETAR="${CWD}/${COREDIR}.tar.gz" +tar -c -z -L -s ",^,${COREDIR}/," -f "${CORETAR}" * ${FILES} +rc=$? +rm -rf "${CORETMP}" + +if [ $rc -eq 0 ]; then + echo "${CORETAR} created for pid ${PID} (${COMM}), euid ${EUID}" +fi Index: sbin/devd/devd.conf =================================================================== --- sbin/devd/devd.conf +++ sbin/devd/devd.conf @@ -263,6 +263,13 @@ action "/etc/rc.resume acpi $notify"; }; +notify 10 { + match "system" "kernel"; + match "subsystem" "signal"; + match "type" "corestop"; + action "/usr/libexec/corestop $pid $euid 2>&1 | /usr/bin/logger -st corestop\(pid=$pid\); kill -KILL $pid"; +}; + /* EXAMPLES TO END OF FILE # An example of something that a vendor might install if you were to Index: sbin/devd/devd.conf.5 =================================================================== --- sbin/devd/devd.conf.5 +++ sbin/devd/devd.conf.5 @@ -272,6 +272,8 @@ Name of attached/detached device. .It Li endpoints Endpoint count (USB). +.It Li euid +Effective UID (corestop). .It Li function Card functions. .It Li interface @@ -295,7 +297,8 @@ .It Li parent Parent device. .It Li pid -PID of the process triggering the rule (RCTL). +PID of the process triggering the rule (RCTL), +or the process that crashed (corestop). .It Li port Hub port number (USB). .It Li product @@ -521,6 +524,9 @@ .It Sy Type .It Li coredump Notification that a process has crashed and dumped core. +.It Li corestop +Notification that a process would crash, but was stopped +just before executing the offending instruction. .El .El .Pp Index: sys/kern/kern_sig.c =================================================================== --- sys/kern/kern_sig.c +++ sys/kern/kern_sig.c @@ -104,6 +104,7 @@ "struct thread *", "struct proc *", "int"); static int coredump(struct thread *); +static void corestop(struct thread *, int sig); static int killpg1(struct thread *td, int sig, int pgid, int all, ksiginfo_t *ksi); static int issignal(struct thread *td); @@ -189,6 +190,10 @@ SYSCTL_INT(_kern, OID_AUTO, coredump, CTLFLAG_RW, &do_coredump, 0, "Enable/Disable coredumps"); +static int do_corestop = 0; +SYSCTL_INT(_kern, OID_AUTO, corestop, CTLFLAG_RW, + &do_corestop, 0, "Stop the process instead of dumping core"); + static int set_core_nodump_flag = 0; SYSCTL_INT(_kern, OID_AUTO, nodump_coredump, CTLFLAG_RW, &set_core_nodump_flag, 0, "Enable setting the NODUMP flag on coredump files"); @@ -3104,6 +3109,9 @@ * Default action, where the default is to kill * the process. (Other cases were ignored above.) */ + if (do_corestop && (sigprop(sig) & SIGPROP_CORE)) + corestop(td, sig); + mtx_unlock(&ps->ps_mtx); proc_td_siginfo_capture(td, &ksi.ksi_info); sigexit(td, sig); @@ -3769,6 +3777,81 @@ #endif free(name, M_TEMP); return (error); +} + +/* + * Stop the crashed process, so it can be analyzed and then killed. + */ +static void +corestop(struct thread *td, int sig) +{ + char strbuf[32]; + struct proc *p; + struct sigacts *ps; + int must_exit, rv; + + p = td->td_proc; + PROC_LOCK_ASSERT(p, MA_OWNED); + ps = p->p_sigacts; + mtx_assert(&ps->ps_mtx, MA_OWNED); + + /* + * The policy here mimics the one from coredump(). + */ + if ((!sugid_coredump && (p->p_flag & P_SUGID) != 0) || + (p->p_flag2 & P2_NOTRACE) != 0) { + return; + } + + must_exit = thread_single(p, SINGLE_NO_EXIT); + if (must_exit) + return; + + KASSERT((p->p_flag2 & P2_CORESTOPPED) == 0, + ("%s: P2_CORESTOPPED already set", __func__)); + + p->p_flag2 |= P2_CORESTOPPED; + + log(LOG_INFO, + "pid %d (%s), jid %d, uid %d: would exit on " + "signal %d (core stopped)\n", p->p_pid, p->p_comm, + p->p_ucred->cr_prison->pr_id, + td->td_ucred->cr_uid, + sig &~ WCOREFLAG); + rv = snprintf(strbuf, sizeof(strbuf), + "pid=%d euid=%d", p->p_pid, p->p_ucred->cr_uid); + KASSERT(rv > 0 && rv < sizeof(strbuf), + ("%s: snprintf failed\n", __func__)); + devctl_notify("kernel", "signal", "corestop", strbuf); + +again: + /* + * Actually stop executing now. The only caveat is that we need + * to provide a way for the debugger to attach and detach, more + * than once. This is somewhat complicated due to PT_ATTACH + * and PT_DETACH insisting on resuming the process. + */ + mtx_unlock(&ps->ps_mtx); + WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, + &p->p_mtx.lock_object, "corestopping"); + sigqueue_delete(&td->td_sigqueue, sig); + sigqueue_delete(&p->p_sigqueue, sig); + p->p_flag |= P_STOPPED_SIG; + p->p_xsig = sig; + PROC_SLOCK(p); + sig_suspend_threads(td, p, 0); + thread_suspend_switch(td, p); + PROC_SUNLOCK(p); + mtx_lock(&ps->ps_mtx); + + /* + * Handle signals as long as we are being ptraced. + */ + if (p->p_flag2 & P2_PTRACE_FSTP) { + while ((sig = cursig(td)) != 0) + postsig(sig); + goto again; + } } /* Index: sys/kern/sys_process.c =================================================================== --- sys/kern/sys_process.c +++ sys/kern/sys_process.c @@ -1238,11 +1238,18 @@ * Unsuspend all threads. To leave a thread * suspended, use PT_SUSPEND to suspend it before * continuing the process. + * + * Except for processes that are corestopped - those + * need to remain stopped, otherwise it wouldn't be + * possible to reattach to them. */ - PROC_SLOCK(p); - p->p_flag &= ~(P_STOPPED_TRACE | P_STOPPED_SIG | P_WAITED); - thread_unsuspend(p); - PROC_SUNLOCK(p); + p->p_flag &= ~(P_STOPPED_TRACE | P_WAITED); + if ((p->p_flag2 & P2_CORESTOPPED) == 0 || data != 0) { + p->p_flag &= ~P_STOPPED_SIG; + PROC_SLOCK(p); + thread_unsuspend(p); + PROC_SUNLOCK(p); + } break; case PT_WRITE_I: Index: sys/sys/proc.h =================================================================== --- sys/sys/proc.h +++ sys/sys/proc.h @@ -782,6 +782,7 @@ #define P2_PROTMAX_DISABLE 0x00000400 /* Force disable implied PROT_MAX. */ #define P2_STKGAP_DISABLE 0x00000800 /* Disable stack gap for MAP_STACK */ #define P2_STKGAP_DISABLE_EXEC 0x00001000 /* Stack gap disabled after exec */ +#define P2_CORESTOPPED 0x00002000 /* Stopped instead of dumping core */ /* Flags protected by proctree_lock, kept in p_treeflags. */ #define P_TREE_ORPHANED 0x00000001 /* Reparented, on orphan list */