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 @@ -189,6 +189,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"); @@ -2977,7 +2981,17 @@ #endif break; /* == ignore */ } + /* + * If the process is being corestopped, signals + * that would otherwise coredump turn into stops. + */ + if ((p->p_flag2 & P2_CORESTOPPED) && + (prop & SIGPROP_CORE)) { + prop |= SIGPROP_STOP; + } + + /* * If there is a pending stop signal to process with * default action, stop here, then clear the signal. * Traced or exiting processes should ignore stops. @@ -3064,6 +3078,43 @@ } } +static void +corestop(int sig) +{ + char strbuf[32]; + struct thread *td; + struct proc *p; + int rv; + + td = curthread; + p = td->td_proc; + + KASSERT((p->p_flag2 & P2_CORESTOPPED) == 0, + ("%s: P2_CORESTOPPED already set", __func__)); + + /* + * Borrow policy checking from coredump(9). + */ + if ((!sugid_coredump && (p->p_flag & P_SUGID) != 0) || + (p->p_flag2 & P2_NOTRACE) != 0) { + PROC_UNLOCK(p); + return; + } + + 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); +} + /* * Take the action for the specified signal * from the current set of pending signals. @@ -3100,14 +3151,18 @@ #endif if (action == SIG_DFL) { - /* - * Default action, where the default is to kill - * the process. (Other cases were ignored above.) - */ - mtx_unlock(&ps->ps_mtx); - proc_td_siginfo_capture(td, &ksi.ksi_info); - sigexit(td, sig); - /* NOTREACHED */ + if (do_corestop && (sigprop(sig) & SIGPROP_CORE)) { + corestop(sig); + } else { + /* + * Default action, where the default is to kill + * the process. (Other cases were ignored above.) + */ + mtx_unlock(&ps->ps_mtx); + proc_td_siginfo_capture(td, &ksi.ksi_info); + sigexit(td, sig); + /* NOTREACHED */ + } } else { /* * If we get here, the signal must be caught. 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 */