#include <sys/param.h>
#include <sys/capsicum.h>
#include <sys/event.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/imgact.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/poll.h>
#include <sys/proc.h>
#include <sys/procdesc.h>
#include <sys/resourcevar.h>
#include <sys/stat.h>
#include <sys/syscallsubr.h>
#include <sys/sysproto.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/ucred.h>
#include <sys/user.h>
#include <security/audit/audit.h>
#include <vm/uma.h>
FEATURE(process_descriptors, "Process Descriptors");
MALLOC_DEFINE(M_PROCDESC, "procdesc", "process descriptors");
static fo_poll_t procdesc_poll;
static fo_kqfilter_t procdesc_kqfilter;
static fo_stat_t procdesc_stat;
static fo_close_t procdesc_close;
static fo_fill_kinfo_t procdesc_fill_kinfo;
static fo_cmp_t procdesc_cmp;
static const struct fileops procdesc_ops = {
.fo_read = invfo_rdwr,
.fo_write = invfo_rdwr,
.fo_truncate = invfo_truncate,
.fo_ioctl = invfo_ioctl,
.fo_poll = procdesc_poll,
.fo_kqfilter = procdesc_kqfilter,
.fo_stat = procdesc_stat,
.fo_close = procdesc_close,
.fo_chmod = invfo_chmod,
.fo_chown = invfo_chown,
.fo_sendfile = invfo_sendfile,
.fo_fill_kinfo = procdesc_fill_kinfo,
.fo_cmp = procdesc_cmp,
.fo_flags = DFLAG_PASSABLE,
};
pid_t
procdesc_pid(struct file *fp_procdesc)
{
struct procdesc *pd;
KASSERT(fp_procdesc->f_type == DTYPE_PROCDESC,
("procdesc_pid: !procdesc"));
pd = fp_procdesc->f_data;
return (pd->pd_pid);
}
int
kern_pdgetpid(struct thread *td, int fd, const cap_rights_t *rightsp,
pid_t *pidp)
{
struct file *fp;
int error;
error = fget_procdesc(td, fd, rightsp, &fp, NULL, NULL);
if (error == 0)
*pidp = procdesc_pid(fp);
if (fp != NULL)
fdrop(fp, td);
return (error);
}
int
sys_pdgetpid(struct thread *td, struct pdgetpid_args *uap)
{
pid_t pid;
int error;
AUDIT_ARG_FD(uap->fd);
error = kern_pdgetpid(td, uap->fd, &cap_pdgetpid_rights, &pid);
if (error == 0)
error = copyout(&pid, uap->pidp, sizeof(pid));
return (error);
}
static struct procdesc *
procdesc_alloc(int flags)
{
struct procdesc *pd;
pd = malloc(sizeof(*pd), M_PROCDESC, M_WAITOK | M_ZERO);
pd->pd_flags = 0;
pd->pd_pid = -1;
PROCDESC_LOCK_INIT(pd);
knlist_init_mtx(&pd->pd_selinfo.si_note, &pd->pd_lock);
refcount_init(&pd->pd_refcount, 2);
pd->pd_fpcount = 1;
return (pd);
}
void
procdesc_new(struct proc *p, int flags)
{
struct procdesc *pd;
pd = procdesc_alloc(flags);
pd->pd_proc = p;
pd->pd_pid = p->p_pid;
MPASS(p->p_procdesc == NULL);
p->p_procdesc = pd;
}
static int
pdtofdflags(int flags)
{
int fflags;
fflags = 0;
if (flags & PD_CLOEXEC)
fflags |= O_CLOEXEC;
return (fflags);
}
int
procdesc_falloc(struct thread *td, struct file **resultfp, int *resultfd,
int flags, struct filecaps *fcaps)
{
int error;
error = falloc_caps(td, resultfp, resultfd, pdtofdflags(flags), fcaps);
if (error == 0 && (flags & PD_DAEMON) != 0)
(*resultfp)->f_pdflags |= F_PD_NOKILL;
return (error);
}
void
procdesc_finit(struct procdesc *pdp, struct file *fp)
{
finit(fp, FREAD | FWRITE, DTYPE_PROCDESC, pdp, &procdesc_ops);
}
static void
procdesc_destroy(struct procdesc *pd)
{
knlist_destroy(&pd->pd_selinfo.si_note);
PROCDESC_LOCK_DESTROY(pd);
free(pd, M_PROCDESC);
}
static void
procdesc_free(struct procdesc *pd)
{
if (refcount_release(&pd->pd_refcount)) {
KASSERT(pd->pd_proc == NULL,
("procdesc_free: pd_proc != NULL"));
KASSERT(pd->pd_fpcount == 0,
("procdesc_free: not closed %p %d", pd, pd->pd_fpcount));
if (pd->pd_pid != -1)
proc_id_clear(PROC_ID_PID, pd->pd_pid);
seldrain(&pd->pd_selinfo);
procdesc_destroy(pd);
}
}
void
procdesc_exit(struct proc *p)
{
struct procdesc *pd;
sx_assert(&proctree_lock, SA_XLOCKED);
PROC_LOCK_ASSERT(p, MA_OWNED);
MPASS((p->p_flag & P_WEXIT) != 0);
pd = p->p_procdesc;
if (pd == NULL)
return;
PROCDESC_LOCK(pd);
KASSERT(pd->pd_fpcount > 0, ("%s: closed procdesc %p", __func__, pd));
pd->pd_flags |= PDF_EXITED;
pd->pd_xstat = KW_EXITCODE(p->p_xexit, p->p_xsig);
selwakeup(&pd->pd_selinfo);
KNOTE_LOCKED(&pd->pd_selinfo.si_note, NOTE_EXIT | NOTE_PDSIGCHLD);
PROCDESC_UNLOCK(pd);
wakeup(&p->p_procdesc);
}
void
procdesc_jobstate(struct proc *p)
{
struct procdesc *pd;
PROC_LOCK_ASSERT(p, MA_OWNED);
pd = p->p_procdesc;
if (pd == NULL)
return;
PROCDESC_LOCK(pd);
KNOTE_LOCKED(&pd->pd_selinfo.si_note, NOTE_PDSIGCHLD);
PROCDESC_UNLOCK(pd);
wakeup(&p->p_procdesc);
}
void
procdesc_fork(struct proc *p, pid_t child_pid)
{
struct procdesc *pd;
PROC_LOCK(p);
pd = p->p_procdesc;
if (pd != NULL) {
PROCDESC_LOCK(pd);
pd->pd_last_child = child_pid;
KNOTE_LOCKED(&pd->pd_selinfo.si_note, NOTE_FORK);
PROCDESC_UNLOCK(pd);
}
PROC_UNLOCK(p);
}
void
procdesc_reap(struct proc *p)
{
struct procdesc *pd;
sx_assert(&proctree_lock, SA_XLOCKED);
KASSERT(p->p_procdesc != NULL, ("procdesc_reap: p_procdesc == NULL"));
pd = p->p_procdesc;
pd->pd_proc = NULL;
p->p_procdesc = NULL;
procdesc_free(pd);
}
static void
procdesc_close_tail(struct file *fp, struct proc *p)
{
if ((fp->f_pdflags & F_PD_NOKILL) == 0)
kern_psignal(p, SIGKILL);
PROC_UNLOCK(p);
sx_xunlock(&proctree_lock);
}
static int
procdesc_close(struct file *fp, struct thread *td)
{
struct procdesc *pd;
struct proc *p;
KASSERT(fp->f_type == DTYPE_PROCDESC, ("procdesc_close: !procdesc"));
pd = fp->f_data;
fp->f_ops = &badfileops;
fp->f_data = NULL;
sx_xlock(&proctree_lock);
PROCDESC_LOCK(pd);
MPASS(pd->pd_fpcount > 0);
pd->pd_fpcount--;
PROCDESC_UNLOCK(pd);
p = pd->pd_proc;
if (p == NULL) {
sx_xunlock(&proctree_lock);
} else {
PROC_LOCK(p);
AUDIT_ARG_PROCESS(p);
if (p->p_state == PRS_ZOMBIE) {
proc_reap(curthread, p, NULL, 0);
} else if (pd->pd_fpcount == 0) {
pd->pd_proc = NULL;
p->p_procdesc = NULL;
pd->pd_pid = -1;
procdesc_free(pd);
if ((fp->f_pdflags & F_PD_NOFINSTALL) == 0) {
p->p_sigparent = SIGCHLD;
if ((p->p_flag & P_TRACED) == 0) {
proc_reparent(p, p->p_reaper, true);
} else {
proc_clear_orphan(p);
p->p_oppid = p->p_reaper->p_pid;
proc_add_orphan(p, p->p_reaper);
}
}
procdesc_close_tail(fp, p);
} else {
procdesc_close_tail(fp, p);
}
}
procdesc_free(pd);
return (0);
}
static int
procdesc_poll(struct file *fp, int events, struct ucred *active_cred,
struct thread *td)
{
struct procdesc *pd;
int revents;
revents = 0;
pd = fp->f_data;
PROCDESC_LOCK(pd);
if (pd->pd_flags & PDF_EXITED)
revents |= POLLHUP;
else
selrecord(td, &pd->pd_selinfo);
PROCDESC_UNLOCK(pd);
return (revents);
}
static void
procdesc_kqops_detach(struct knote *kn)
{
struct procdesc *pd;
pd = kn->kn_fp->f_data;
knlist_remove(&pd->pd_selinfo.si_note, kn, 0);
}
static int
procdesc_kqops_event(struct knote *kn, long hint)
{
struct procdesc *pd;
struct proc *p;
u_int event;
pd = kn->kn_fp->f_data;
if (hint == 0) {
p = pd->pd_proc;
if ((pd->pd_flags & PDF_EXITED) != 0)
event = NOTE_EXIT | NOTE_PDSIGCHLD;
else if ((atomic_load_int(&p->p_flag) & (P_STOPPED_SIG |
P_STOPPED_TRACE)) != 0)
event = NOTE_PDSIGCHLD;
else
event = 0;
} else {
event = (u_int)hint & NOTE_PCTRLMASK;
}
if ((kn->kn_sfflags & event) != 0)
kn->kn_fflags |= kn->kn_sfflags & event;
if ((kn->kn_fflags & NOTE_EXIT) != 0)
kn->kn_data = pd->pd_xstat;
if ((event & NOTE_REAP) != 0 ||
((event & NOTE_EXIT) != 0 && (kn->kn_sfflags & NOTE_REAP) == 0)) {
kn->kn_flags |= EV_EOF | EV_ONESHOT;
if (kn->kn_fflags == 0)
kn->kn_flags |= EV_DROP;
return (1);
}
if ((kn->kn_fflags & NOTE_FORK) != 0)
kn->kn_data = pd->pd_last_child;
return (kn->kn_fflags != 0);
}
static const struct filterops procdesc_kqops = {
.f_isfd = 1,
.f_detach = procdesc_kqops_detach,
.f_event = procdesc_kqops_event,
.f_copy = knote_triv_copy,
};
static int
procdesc_kqfilter(struct file *fp, struct knote *kn)
{
struct procdesc *pd;
pd = fp->f_data;
switch (kn->kn_filter) {
case EVFILT_PROCDESC:
kn->kn_fop = &procdesc_kqops;
kn->kn_flags |= EV_CLEAR;
knlist_add(&pd->pd_selinfo.si_note, kn, 0);
return (0);
default:
return (EINVAL);
}
}
static int
procdesc_stat(struct file *fp, struct stat *sb, struct ucred *active_cred)
{
struct procdesc *pd;
struct timeval pstart, boottime;
bzero(sb, sizeof(*sb));
pd = fp->f_data;
sx_slock(&proctree_lock);
if (pd->pd_proc != NULL) {
PROC_LOCK(pd->pd_proc);
AUDIT_ARG_PROCESS(pd->pd_proc);
pstart = pd->pd_proc->p_stats->p_start;
getboottime(&boottime);
timevaladd(&pstart, &boottime);
TIMEVAL_TO_TIMESPEC(&pstart, &sb->st_birthtim);
sb->st_atim = sb->st_birthtim;
sb->st_ctim = sb->st_birthtim;
sb->st_mtim = sb->st_birthtim;
if (pd->pd_proc->p_state != PRS_ZOMBIE)
sb->st_mode = S_IFREG | S_IRWXU;
else
sb->st_mode = S_IFREG;
sb->st_uid = pd->pd_proc->p_ucred->cr_ruid;
sb->st_gid = pd->pd_proc->p_ucred->cr_rgid;
PROC_UNLOCK(pd->pd_proc);
} else
sb->st_mode = S_IFREG;
sx_sunlock(&proctree_lock);
return (0);
}
static int
procdesc_fill_kinfo(struct file *fp, struct kinfo_file *kif,
struct filedesc *fdp)
{
struct procdesc *pdp;
kif->kf_type = KF_TYPE_PROCDESC;
pdp = fp->f_data;
kif->kf_un.kf_proc.kf_pid = pdp->pd_pid;
return (0);
}
static int
procdesc_cmp(struct file *fp1, struct file *fp2, struct thread *td)
{
struct procdesc *pdp1, *pdp2;
if (fp2->f_type != DTYPE_PROCDESC)
return (3);
pdp1 = fp1->f_data;
pdp2 = fp2->f_data;
return (kcmp_cmp((uintptr_t)pdp1->pd_pid, (uintptr_t)pdp2->pd_pid));
}
static int
pdopenpid1(struct thread *td, pid_t pid, struct procdesc **pdf, struct file *fp)
{
struct proc *p;
struct procdesc *pd;
int error;
sx_assert(&proctree_lock, SX_XLOCKED);
error = pget(pid, PGET_NOTID | PGET_CANDEBUG, &p);
if (error != 0)
return (error);
if ((p->p_flag & (P_SYSTEM | P_WEXIT)) != 0) {
PROC_UNLOCK(p);
return (EBUSY);
}
pd = p->p_procdesc;
if (pd != NULL) {
refcount_acquire(&pd->pd_refcount);
PROCDESC_LOCK(pd);
MPASS(pd->pd_fpcount > 0);
pd->pd_fpcount++;
PROCDESC_UNLOCK(pd);
} else {
pd = *pdf;
*pdf = NULL;
pd->pd_proc = p;
pd->pd_pid = p->p_pid;
p->p_procdesc = pd;
}
procdesc_finit(pd, fp);
PROC_UNLOCK(p);
return (0);
}
static int
kern_pdopenpid(struct thread *td, pid_t pid, int flags)
{
struct file *fp;
struct procdesc *pdf;
int error, fd, fflags;
error = falloc_noinstall(td, &fp);
if (error != 0)
return (error);
fflags = pdtofdflags(flags);
pdf = procdesc_alloc(flags);
if ((flags & PD_DAEMON) != 0)
fp->f_pdflags |= F_PD_NOKILL;
sx_xlock(&proctree_lock);
error = pdopenpid1(td, pid, &pdf, fp);
sx_xunlock(&proctree_lock);
if (error == 0) {
error = finstall(td, fp, &fd, fflags, NULL);
if (error == 0) {
td->td_retval[0] = fd;
} else {
fp->f_pdflags |= F_PD_NOKILL | F_PD_NOFINSTALL;
}
}
fdrop(fp, td);
if (pdf != NULL) {
MPASS(pdf->pd_refcount == 2);
MPASS(pdf->pd_fpcount == 1);
MPASS(pdf->pd_proc == NULL);
MPASS(pdf->pd_pid == -1);
procdesc_destroy(pdf);
}
return (error);
}
int
sys_pdopenpid(struct thread *td, struct pdopenpid_args *args)
{
AUDIT_ARG_PID(args->pid);
AUDIT_ARG_FFLAGS(args->flags);
if ((args->flags & ~(PD_ALLOWED_AT_FORK)) != 0)
return (EINVAL);
return (kern_pdopenpid(td, args->pid, args->flags));
}
int
fget_procdesc(struct thread *td, int pdfd, const cap_rights_t *cap_rights,
struct file **pfp, struct procdesc **pdp, struct proc **pp)
{
struct file *fp;
struct procdesc *pd;
struct proc *p;
int error;
if (pp != NULL)
sx_assert(&proctree_lock, SX_LOCKED);
*pfp = NULL;
error = fget(td, pdfd, cap_rights, &fp);
if (error != 0)
return (error);
*pfp = fp;
if (fp->f_type != DTYPE_PROCDESC)
return (EINVAL);
pd = fp->f_data;
if (pp != NULL) {
p = pd->pd_proc;
if (p == NULL) {
return (ESRCH);
} else {
*pp = p;
PROC_LOCK(p);
}
}
if (pdp != NULL)
*pdp = pd;
return (0);
}
static int
kern_pddupfd(struct thread *td, int pdfd, int fd, int flags)
{
struct proc *p;
struct file *fp, *pfp;
struct filecaps fcaps;
uint8_t fd_flags;
int error, fdr;
sx_slock(&proctree_lock);
error = fget_procdesc(td, pdfd, &cap_pddupfd_rights, &pfp, NULL, &p);
if (error == 0) {
if ((p->p_flag & P_WEXIT) != 0) {
error = ESRCH;
PROC_UNLOCK(p);
} else {
_PHOLD(p);
}
}
sx_sunlock(&proctree_lock);
if (error != 0)
goto out;
AUDIT_ARG_PROCESS(p);
PROC_LOCK_ASSERT(p, MA_OWNED);
execve_block_wait(td, p);
error = p_candebug(td, p);
if (error == 0) {
PROC_UNLOCK(p);
error = fget_remote(td, p, fd, &fcaps, &fd_flags, &fp);
if (error == 0) {
if ((fp->f_ops->fo_flags & DFLAG_PASSABLE) == 0) {
error = EOPNOTSUPP;
} else {
error = finstall_refed(td, fp, &fdr, O_CLOEXEC |
((fd_flags & FD_RESOLVE_BENEATH) != 0 ?
O_RESOLVE_BENEATH : 0), &fcaps);
}
if (error != 0) {
fdrop(fp, td);
filecaps_free(&fcaps);
} else {
td->td_retval[0] = fdr;
}
}
PROC_LOCK(p);
}
execve_unblock(td, p);
_PRELE(p);
PROC_UNLOCK(p);
out:
if (pfp != NULL)
fdrop(pfp, td);
return (error);
}
int
sys_pddupfd(struct thread *td, struct pddupfd_args *args)
{
if (args->flags != 0)
return (EINVAL);
return (kern_pddupfd(td, args->pd, args->fd, args->flags));
}