#include <sys/param.h>
#include <sys/acct.h>
#include <sys/compressor.h>
#include <sys/jail.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/signalvar.h>
#include <sys/racct.h>
#include <sys/resourcevar.h>
#include <sys/rmlock.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/ucoredump.h>
#include <sys/wait.h>
static int coredump(struct thread *td, const char **);
int compress_user_cores = 0;
static SLIST_HEAD(, coredumper) coredumpers =
SLIST_HEAD_INITIALIZER(coredumpers);
static struct rmlock coredump_rmlock;
RM_SYSINIT(coredump_lock, &coredump_rmlock, "coredump_lock");
static int kern_logsigexit = 1;
SYSCTL_INT(_kern, KERN_LOGSIGEXIT, logsigexit, CTLFLAG_RW,
&kern_logsigexit, 0,
"Log processes quitting on abnormal signals to syslog(3)");
static int sugid_coredump;
SYSCTL_INT(_kern, OID_AUTO, sugid_coredump, CTLFLAG_RWTUN,
&sugid_coredump, 0, "Allow setuid and setgid processes to dump core");
static int do_coredump = 1;
SYSCTL_INT(_kern, OID_AUTO, coredump, CTLFLAG_RW,
&do_coredump, 0, "Enable/Disable coredumps");
static int
sysctl_compress_user_cores(SYSCTL_HANDLER_ARGS)
{
int error, val;
val = compress_user_cores;
error = sysctl_handle_int(oidp, &val, 0, req);
if (error != 0 || req->newptr == NULL)
return (error);
if (val != 0 && !compressor_avail(val))
return (EINVAL);
compress_user_cores = val;
return (error);
}
SYSCTL_PROC(_kern, OID_AUTO, compress_user_cores,
CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int),
sysctl_compress_user_cores, "I",
"Enable compression of user corefiles ("
__XSTRING(COMPRESS_GZIP) " = gzip, "
__XSTRING(COMPRESS_ZSTD) " = zstd)");
int compress_user_cores_level = 6;
SYSCTL_INT(_kern, OID_AUTO, compress_user_cores_level, CTLFLAG_RWTUN,
&compress_user_cores_level, 0,
"Corefile compression level");
void
coredumper_register(struct coredumper *cd)
{
blockcount_init(&cd->cd_refcount);
rm_wlock(&coredump_rmlock);
SLIST_INSERT_HEAD(&coredumpers, cd, cd_entry);
rm_wunlock(&coredump_rmlock);
}
void
coredumper_unregister(struct coredumper *cd)
{
rm_wlock(&coredump_rmlock);
SLIST_REMOVE(&coredumpers, cd, coredumper, cd_entry);
rm_wunlock(&coredump_rmlock);
blockcount_wait(&cd->cd_refcount, NULL, "dumpwait", 0);
}
void
sigexit(struct thread *td, int sig)
{
struct proc *p = td->td_proc;
int rv;
bool logexit;
PROC_LOCK_ASSERT(p, MA_OWNED);
proc_set_p2_wexit(p);
p->p_acflag |= AXSIG;
if ((p->p_flag2 & P2_LOGSIGEXIT_CTL) == 0)
logexit = kern_logsigexit != 0;
else
logexit = (p->p_flag2 & P2_LOGSIGEXIT_ENABLE) != 0;
if (sig_do_core(sig) && thread_single(p, SINGLE_NO_EXIT) == 0) {
const char *err = NULL;
p->p_sig = sig;
rv = coredump(td, &err);
if (rv == 0) {
MPASS(err == NULL);
sig |= WCOREFLAG;
} else if (err == NULL) {
switch (rv) {
case EFAULT:
err = "bad address";
break;
case EINVAL:
err = "invalild argument";
break;
case EFBIG:
err = "too large";
break;
default:
err = "other error";
break;
}
}
if (logexit)
log(LOG_INFO,
"pid %d (%s), jid %d, uid %d: exited on "
"signal %d (%s%s)\n", p->p_pid, p->p_comm,
p->p_ucred->cr_prison->pr_id,
td->td_ucred->cr_uid, sig &~ WCOREFLAG,
err != NULL ? "no core dump - " : "core dumped",
err != NULL ? err : "");
} else
PROC_UNLOCK(p);
exit1(td, 0, sig);
}
static int
coredump(struct thread *td, const char **errmsg)
{
struct coredumper *iter, *chosen;
struct proc *p = td->td_proc;
struct rm_priotracker tracker;
off_t limit;
int error, priority;
PROC_LOCK_ASSERT(p, MA_OWNED);
MPASS((p->p_flag & P_HADTHREADS) == 0 || p->p_singlethread == td);
if (!do_coredump || (!sugid_coredump && (p->p_flag & P_SUGID) != 0) ||
(p->p_flag2 & P2_NOTRACE) != 0) {
PROC_UNLOCK(p);
if (!do_coredump)
*errmsg = "denied by kern.coredump";
else if ((p->p_flag2 & P2_NOTRACE) != 0)
*errmsg = "process has trace disabled";
else
*errmsg = "sugid process denied by kern.sugid_coredump";
return (EFAULT);
}
limit = (off_t)lim_cur(td, RLIMIT_CORE);
if (limit == 0 || racct_get_available(p, RACCT_CORE) == 0) {
PROC_UNLOCK(p);
*errmsg = "coredumpsize limit is 0";
return (EFBIG);
}
rm_rlock(&coredump_rmlock, &tracker);
priority = -1;
chosen = NULL;
SLIST_FOREACH(iter, &coredumpers, cd_entry) {
if (iter->cd_probe == NULL) {
if (priority < 0) {
priority = COREDUMPER_GENERIC;
chosen = iter;
}
continue;
}
error = (*iter->cd_probe)(td);
if (error < 0)
continue;
if (error > priority) {
priority = error;
chosen = iter;
}
}
blockcount_acquire(&chosen->cd_refcount, 1);
rm_runlock(&coredump_rmlock, &tracker);
MPASS(chosen != NULL);
error = ((*chosen->cd_handle)(td, limit));
PROC_LOCK_ASSERT(p, MA_NOTOWNED);
blockcount_release(&chosen->cd_refcount, 1);
return (error);
}