prcontrol.c revision f48205be61a214698b763ff550ab9e657525104c
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/param.h>
#include <sys/cmn_err.h>
#include <sys/cred.h>
#include <sys/policy.h>
#include <sys/debug.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/inline.h>
#include <sys/kmem.h>
#include <sys/proc.h>
#include <sys/regset.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/vfs.h>
#include <sys/vnode.h>
#include <sys/signal.h>
#include <sys/auxv.h>
#include <sys/user.h>
#include <sys/class.h>
#include <sys/fault.h>
#include <sys/syscall.h>
#include <sys/procfs.h>
#include <sys/zone.h>
#include <sys/copyops.h>
#include <sys/schedctl.h>
#include <vm/as.h>
#include <vm/seg.h>
#include <fs/proc/prdata.h>
#include <sys/contract/process_impl.h>
static void pr_settrace(proc_t *, sigset_t *);
static int pr_setfpregs(prnode_t *, prfpregset_t *);
#if defined(__sparc)
static int pr_setxregs(prnode_t *, prxregset_t *);
static int pr_setasrs(prnode_t *, asrset_t);
#endif
static int pr_setvaddr(prnode_t *, caddr_t);
static int pr_clearsig(prnode_t *);
static int pr_clearflt(prnode_t *);
static int pr_watch(prnode_t *, prwatch_t *, int *);
static int pr_agent(prnode_t *, prgregset_t, int *);
static int pr_rdwr(proc_t *, enum uio_rw, priovec_t *);
static int pr_scred(proc_t *, prcred_t *, cred_t *, boolean_t);
static int pr_spriv(proc_t *, prpriv_t *, cred_t *);
static int pr_szoneid(proc_t *, zoneid_t, cred_t *);
static void pauselwps(proc_t *);
static void unpauselwps(proc_t *);
typedef union {
long sig; /* PCKILL, PCUNKILL */
long nice; /* PCNICE */
long timeo; /* PCTWSTOP */
ulong_t flags; /* PCRUN, PCSET, PCUNSET */
caddr_t vaddr; /* PCSVADDR */
siginfo_t siginfo; /* PCSSIG */
sigset_t sigset; /* PCSTRACE, PCSHOLD */
fltset_t fltset; /* PCSFAULT */
sysset_t sysset; /* PCSENTRY, PCSEXIT */
prgregset_t prgregset; /* PCSREG, PCAGENT */
prfpregset_t prfpregset; /* PCSFPREG */
#if defined(__sparc)
prxregset_t prxregset; /* PCSXREG */
asrset_t asrset; /* PCSASRS */
#endif
prwatch_t prwatch; /* PCWATCH */
priovec_t priovec; /* PCREAD, PCWRITE */
prcred_t prcred; /* PCSCRED */
prpriv_t prpriv; /* PCSPRIV */
long przoneid; /* PCSZONE */
} arg_t;
static int pr_control(long, arg_t *, prnode_t *, cred_t *);
static size_t
ctlsize(long cmd, size_t resid, arg_t *argp)
{
size_t size = sizeof (long);
size_t rnd;
int ngrp;
switch (cmd) {
case PCNULL:
case PCSTOP:
case PCDSTOP:
case PCWSTOP:
case PCCSIG:
case PCCFAULT:
break;
case PCSSIG:
size += sizeof (siginfo_t);
break;
case PCTWSTOP:
size += sizeof (long);
break;
case PCKILL:
case PCUNKILL:
case PCNICE:
size += sizeof (long);
break;
case PCRUN:
case PCSET:
case PCUNSET:
size += sizeof (ulong_t);
break;
case PCSVADDR:
size += sizeof (caddr_t);
break;
case PCSTRACE:
case PCSHOLD:
size += sizeof (sigset_t);
break;
case PCSFAULT:
size += sizeof (fltset_t);
break;
case PCSENTRY:
case PCSEXIT:
size += sizeof (sysset_t);
break;
case PCSREG:
case PCAGENT:
size += sizeof (prgregset_t);
break;
case PCSFPREG:
size += sizeof (prfpregset_t);
break;
#if defined(__sparc)
case PCSXREG:
size += sizeof (prxregset_t);
break;
case PCSASRS:
size += sizeof (asrset_t);
break;
#endif
case PCWATCH:
size += sizeof (prwatch_t);
break;
case PCREAD:
case PCWRITE:
size += sizeof (priovec_t);
break;
case PCSCRED:
size += sizeof (prcred_t);
break;
case PCSCREDX:
/*
* We cannot derefence the pr_ngroups fields if it
* we don't have enough data.
*/
if (resid < size + sizeof (prcred_t) - sizeof (gid_t))
return (0);
ngrp = argp->prcred.pr_ngroups;
if (ngrp < 0 || ngrp > ngroups_max)
return (0);
/* The result can be smaller than sizeof (prcred_t) */
size += sizeof (prcred_t) - sizeof (gid_t);
size += ngrp * sizeof (gid_t);
break;
case PCSPRIV:
if (resid >= size + sizeof (prpriv_t))
size += priv_prgetprivsize(&argp->prpriv);
else
return (0);
break;
case PCSZONE:
size += sizeof (long);
break;
default:
return (0);
}
/* Round up to a multiple of long, unless exact amount written */
if (size < resid) {
rnd = size & (sizeof (long) - 1);
if (rnd != 0)
size += sizeof (long) - rnd;
}
if (size > resid)
return (0);
return (size);
}
/*
* Control operations (lots).
*/
int
prwritectl(vnode_t *vp, uio_t *uiop, cred_t *cr)
{
#define MY_BUFFER_SIZE \
100 > 1 + sizeof (arg_t) / sizeof (long) ? \
100 : 1 + sizeof (arg_t) / sizeof (long)
long buf[MY_BUFFER_SIZE];
long *bufp;
size_t resid = 0;
size_t size;
prnode_t *pnp = VTOP(vp);
int error;
int locked = 0;
while (uiop->uio_resid) {
/*
* Read several commands in one gulp.
*/
bufp = buf;
if (resid) { /* move incomplete command to front of buffer */
long *tail;
if (resid >= sizeof (buf))
break;
tail = (long *)((char *)buf + sizeof (buf) - resid);
do {
*bufp++ = *tail++;
} while ((resid -= sizeof (long)) != 0);
}
resid = sizeof (buf) - ((char *)bufp - (char *)buf);
if (resid > uiop->uio_resid)
resid = uiop->uio_resid;
if (error = uiomove((caddr_t)bufp, resid, UIO_WRITE, uiop))
return (error);
resid += (char *)bufp - (char *)buf;
bufp = buf;
do { /* loop over commands in buffer */
long cmd = bufp[0];
arg_t *argp = (arg_t *)&bufp[1];
size = ctlsize(cmd, resid, argp);
if (size == 0) /* incomplete or invalid command */
break;
/*
* Perform the specified control operation.
*/
if (!locked) {
if ((error = prlock(pnp, ZNO)) != 0)
return (error);
locked = 1;
}
if (error = pr_control(cmd, argp, pnp, cr)) {
if (error == -1) /* -1 is timeout */
locked = 0;
else
return (error);
}
bufp = (long *)((char *)bufp + size);
} while ((resid -= size) != 0);
if (locked) {
prunlock(pnp);
locked = 0;
}
}
return (resid? EINVAL : 0);
}
static int
pr_control(long cmd, arg_t *argp, prnode_t *pnp, cred_t *cr)
{
prcommon_t *pcp;
proc_t *p;
int unlocked;
int error = 0;
if (cmd == PCNULL)
return (0);
pcp = pnp->pr_common;
p = pcp->prc_proc;
ASSERT(p != NULL);
switch (cmd) {
default:
error = EINVAL;
break;
case PCSTOP: /* direct process or lwp to stop and wait for stop */
case PCDSTOP: /* direct process or lwp to stop, don't wait */
case PCWSTOP: /* wait for process or lwp to stop */
case PCTWSTOP: /* wait for process or lwp to stop, with timeout */
{
time_t timeo;
/*
* Can't apply to a system process.
*/
if ((p->p_flag & SSYS) || p->p_as == &kas) {
error = EBUSY;
break;
}
if (cmd == PCSTOP || cmd == PCDSTOP)
pr_stop(pnp);
if (cmd == PCDSTOP)
break;
/*
* If an lwp is waiting for itself or its process, don't wait.
* The stopped lwp would never see the fact that it is stopped.
*/
if ((pcp->prc_flags & PRC_LWP)?
(pcp->prc_thread == curthread) : (p == curproc)) {
if (cmd == PCWSTOP || cmd == PCTWSTOP)
error = EBUSY;
break;
}
timeo = (cmd == PCTWSTOP)? (time_t)argp->timeo : 0;
if ((error = pr_wait_stop(pnp, timeo)) != 0)
return (error);
break;
}
case PCRUN: /* make lwp or process runnable */
error = pr_setrun(pnp, argp->flags);
break;
case PCSTRACE: /* set signal trace mask */
pr_settrace(p, &argp->sigset);
break;
case PCSSIG: /* set current signal */
error = pr_setsig(pnp, &argp->siginfo);
if (argp->siginfo.si_signo == SIGKILL && error == 0) {
prunlock(pnp);
pr_wait_die(pnp);
return (-1);
}
break;
case PCKILL: /* send signal */
error = pr_kill(pnp, (int)argp->sig, cr);
if (error == 0 && argp->sig == SIGKILL) {
prunlock(pnp);
pr_wait_die(pnp);
return (-1);
}
break;
case PCUNKILL: /* delete a pending signal */
error = pr_unkill(pnp, (int)argp->sig);
break;
case PCNICE: /* set nice priority */
error = pr_nice(p, (int)argp->nice, cr);
break;
case PCSENTRY: /* set syscall entry bit mask */
case PCSEXIT: /* set syscall exit bit mask */
pr_setentryexit(p, &argp->sysset, cmd == PCSENTRY);
break;
case PCSET: /* set process flags */
error = pr_set(p, argp->flags);
break;
case PCUNSET: /* unset process flags */
error = pr_unset(p, argp->flags);
break;
case PCSREG: /* set general registers */
{
kthread_t *t = pr_thread(pnp);
if (!ISTOPPED(t) && !VSTOPPED(t) && !DSTOPPED(t)) {
thread_unlock(t);
error = EBUSY;
} else {
thread_unlock(t);
mutex_exit(&p->p_lock);
prsetprregs(ttolwp(t), argp->prgregset, 0);
mutex_enter(&p->p_lock);
}
break;
}
case PCSFPREG: /* set floating-point registers */
error = pr_setfpregs(pnp, &argp->prfpregset);
break;
case PCSXREG: /* set extra registers */
#if defined(__sparc)
error = pr_setxregs(pnp, &argp->prxregset);
#else
error = EINVAL;
#endif
break;
#if defined(__sparc)
case PCSASRS: /* set ancillary state registers */
error = pr_setasrs(pnp, argp->asrset);
break;
#endif
case PCSVADDR: /* set virtual address at which to resume */
error = pr_setvaddr(pnp, argp->vaddr);
break;
case PCSHOLD: /* set signal-hold mask */
pr_sethold(pnp, &argp->sigset);
break;
case PCSFAULT: /* set mask of traced faults */
pr_setfault(p, &argp->fltset);
break;
case PCCSIG: /* clear current signal */
error = pr_clearsig(pnp);
break;
case PCCFAULT: /* clear current fault */
error = pr_clearflt(pnp);
break;
case PCWATCH: /* set or clear watched areas */
error = pr_watch(pnp, &argp->prwatch, &unlocked);
if (error && unlocked)
return (error);
break;
case PCAGENT: /* create the /proc agent lwp in the target process */
error = pr_agent(pnp, argp->prgregset, &unlocked);
if (error && unlocked)
return (error);
break;
case PCREAD: /* read from the address space */
error = pr_rdwr(p, UIO_READ, &argp->priovec);
break;
case PCWRITE: /* write to the address space */
error = pr_rdwr(p, UIO_WRITE, &argp->priovec);
break;
case PCSCRED: /* set the process credentials */
case PCSCREDX:
error = pr_scred(p, &argp->prcred, cr, cmd == PCSCREDX);
break;
case PCSPRIV: /* set the process privileges */
error = pr_spriv(p, &argp->prpriv, cr);
break;
case PCSZONE: /* set the process's zoneid credentials */
error = pr_szoneid(p, (zoneid_t)argp->przoneid, cr);
break;
}
if (error)
prunlock(pnp);
return (error);
}
#ifdef _SYSCALL32_IMPL
typedef union {
int32_t sig; /* PCKILL, PCUNKILL */
int32_t nice; /* PCNICE */
int32_t timeo; /* PCTWSTOP */
uint32_t flags; /* PCRUN, PCSET, PCUNSET */
caddr32_t vaddr; /* PCSVADDR */
siginfo32_t siginfo; /* PCSSIG */
sigset_t sigset; /* PCSTRACE, PCSHOLD */
fltset_t fltset; /* PCSFAULT */
sysset_t sysset; /* PCSENTRY, PCSEXIT */
prgregset32_t prgregset; /* PCSREG, PCAGENT */
prfpregset32_t prfpregset; /* PCSFPREG */
#if defined(__sparc)
prxregset_t prxregset; /* PCSXREG */
#endif
prwatch32_t prwatch; /* PCWATCH */
priovec32_t priovec; /* PCREAD, PCWRITE */
prcred32_t prcred; /* PCSCRED */
prpriv_t prpriv; /* PCSPRIV */
int32_t przoneid; /* PCSZONE */
} arg32_t;
static int pr_control32(int32_t, arg32_t *, prnode_t *, cred_t *);
static int pr_setfpregs32(prnode_t *, prfpregset32_t *);
/*
* Note that while ctlsize32() can use argp, it must do so only in a way
* that assumes 32-bit rather than 64-bit alignment as argp is a pointer
* to an array of 32-bit values and only 32-bit alignment is ensured.
*/
static size_t
ctlsize32(int32_t cmd, size_t resid, arg32_t *argp)
{
size_t size = sizeof (int32_t);
size_t rnd;
int ngrp;
switch (cmd) {
case PCNULL:
case PCSTOP:
case PCDSTOP:
case PCWSTOP:
case PCCSIG:
case PCCFAULT:
break;
case PCSSIG:
size += sizeof (siginfo32_t);
break;
case PCTWSTOP:
size += sizeof (int32_t);
break;
case PCKILL:
case PCUNKILL:
case PCNICE:
size += sizeof (int32_t);
break;
case PCRUN:
case PCSET:
case PCUNSET:
size += sizeof (uint32_t);
break;
case PCSVADDR:
size += sizeof (caddr32_t);
break;
case PCSTRACE:
case PCSHOLD:
size += sizeof (sigset_t);
break;
case PCSFAULT:
size += sizeof (fltset_t);
break;
case PCSENTRY:
case PCSEXIT:
size += sizeof (sysset_t);
break;
case PCSREG:
case PCAGENT:
size += sizeof (prgregset32_t);
break;
case PCSFPREG:
size += sizeof (prfpregset32_t);
break;
#if defined(__sparc)
case PCSXREG:
size += sizeof (prxregset_t);
break;
#endif
case PCWATCH:
size += sizeof (prwatch32_t);
break;
case PCREAD:
case PCWRITE:
size += sizeof (priovec32_t);
break;
case PCSCRED:
size += sizeof (prcred32_t);
break;
case PCSCREDX:
/*
* We cannot derefence the pr_ngroups fields if it
* we don't have enough data.
*/
if (resid < size + sizeof (prcred32_t) - sizeof (gid32_t))
return (0);
ngrp = argp->prcred.pr_ngroups;
if (ngrp < 0 || ngrp > ngroups_max)
return (0);
/* The result can be smaller than sizeof (prcred32_t) */
size += sizeof (prcred32_t) - sizeof (gid32_t);
size += ngrp * sizeof (gid32_t);
break;
case PCSPRIV:
if (resid >= size + sizeof (prpriv_t))
size += priv_prgetprivsize(&argp->prpriv);
else
return (0);
break;
case PCSZONE:
size += sizeof (int32_t);
break;
default:
return (0);
}
/* Round up to a multiple of int32_t */
rnd = size & (sizeof (int32_t) - 1);
if (rnd != 0)
size += sizeof (int32_t) - rnd;
if (size > resid)
return (0);
return (size);
}
/*
* Control operations (lots).
*/
int
prwritectl32(struct vnode *vp, struct uio *uiop, cred_t *cr)
{
#define MY_BUFFER_SIZE32 \
100 > 1 + sizeof (arg32_t) / sizeof (int32_t) ? \
100 : 1 + sizeof (arg32_t) / sizeof (int32_t)
int32_t buf[MY_BUFFER_SIZE32];
int32_t *bufp;
arg32_t arg;
size_t resid = 0;
size_t size;
prnode_t *pnp = VTOP(vp);
int error;
int locked = 0;
while (uiop->uio_resid) {
/*
* Read several commands in one gulp.
*/
bufp = buf;
if (resid) { /* move incomplete command to front of buffer */
int32_t *tail;
if (resid >= sizeof (buf))
break;
tail = (int32_t *)((char *)buf + sizeof (buf) - resid);
do {
*bufp++ = *tail++;
} while ((resid -= sizeof (int32_t)) != 0);
}
resid = sizeof (buf) - ((char *)bufp - (char *)buf);
if (resid > uiop->uio_resid)
resid = uiop->uio_resid;
if (error = uiomove((caddr_t)bufp, resid, UIO_WRITE, uiop))
return (error);
resid += (char *)bufp - (char *)buf;
bufp = buf;
do { /* loop over commands in buffer */
int32_t cmd = bufp[0];
arg32_t *argp = (arg32_t *)&bufp[1];
size = ctlsize32(cmd, resid, argp);
if (size == 0) /* incomplete or invalid command */
break;
/*
* Perform the specified control operation.
*/
if (!locked) {
if ((error = prlock(pnp, ZNO)) != 0)
return (error);
locked = 1;
}
/*
* Since some members of the arg32_t union contain
* 64-bit values (which must be 64-bit aligned), we
* can't simply pass a pointer to the structure as
* it may be unaligned. Note that we do pass the
* potentially unaligned structure to ctlsize32()
* above, but that uses it a way that makes no
* assumptions about alignment.
*/
ASSERT(size - sizeof (cmd) <= sizeof (arg));
bcopy(argp, &arg, size - sizeof (cmd));
if (error = pr_control32(cmd, &arg, pnp, cr)) {
if (error == -1) /* -1 is timeout */
locked = 0;
else
return (error);
}
bufp = (int32_t *)((char *)bufp + size);
} while ((resid -= size) != 0);
if (locked) {
prunlock(pnp);
locked = 0;
}
}
return (resid? EINVAL : 0);
}
static int
pr_control32(int32_t cmd, arg32_t *argp, prnode_t *pnp, cred_t *cr)
{
prcommon_t *pcp;
proc_t *p;
int unlocked;
int error = 0;
if (cmd == PCNULL)
return (0);
pcp = pnp->pr_common;
p = pcp->prc_proc;
ASSERT(p != NULL);
switch (cmd) {
default:
error = EINVAL;
break;
case PCSTOP: /* direct process or lwp to stop and wait for stop */
case PCDSTOP: /* direct process or lwp to stop, don't wait */
case PCWSTOP: /* wait for process or lwp to stop */
case PCTWSTOP: /* wait for process or lwp to stop, with timeout */
{
time_t timeo;
/*
* Can't apply to a system process.
*/
if ((p->p_flag & SSYS) || p->p_as == &kas) {
error = EBUSY;
break;
}
if (cmd == PCSTOP || cmd == PCDSTOP)
pr_stop(pnp);
if (cmd == PCDSTOP)
break;
/*
* If an lwp is waiting for itself or its process, don't wait.
* The lwp will never see the fact that itself is stopped.
*/
if ((pcp->prc_flags & PRC_LWP)?
(pcp->prc_thread == curthread) : (p == curproc)) {
if (cmd == PCWSTOP || cmd == PCTWSTOP)
error = EBUSY;
break;
}
timeo = (cmd == PCTWSTOP)? (time_t)argp->timeo : 0;
if ((error = pr_wait_stop(pnp, timeo)) != 0)
return (error);
break;
}
case PCRUN: /* make lwp or process runnable */
error = pr_setrun(pnp, (ulong_t)argp->flags);
break;
case PCSTRACE: /* set signal trace mask */
pr_settrace(p, &argp->sigset);
break;
case PCSSIG: /* set current signal */
if (PROCESS_NOT_32BIT(p))
error = EOVERFLOW;
else {
int sig = (int)argp->siginfo.si_signo;
siginfo_t siginfo;
bzero(&siginfo, sizeof (siginfo));
siginfo_32tok(&argp->siginfo, (k_siginfo_t *)&siginfo);
error = pr_setsig(pnp, &siginfo);
if (sig == SIGKILL && error == 0) {
prunlock(pnp);
pr_wait_die(pnp);
return (-1);
}
}
break;
case PCKILL: /* send signal */
error = pr_kill(pnp, (int)argp->sig, cr);
if (error == 0 && argp->sig == SIGKILL) {
prunlock(pnp);
pr_wait_die(pnp);
return (-1);
}
break;
case PCUNKILL: /* delete a pending signal */
error = pr_unkill(pnp, (int)argp->sig);
break;
case PCNICE: /* set nice priority */
error = pr_nice(p, (int)argp->nice, cr);
break;
case PCSENTRY: /* set syscall entry bit mask */
case PCSEXIT: /* set syscall exit bit mask */
pr_setentryexit(p, &argp->sysset, cmd == PCSENTRY);
break;
case PCSET: /* set process flags */
error = pr_set(p, (long)argp->flags);
break;
case PCUNSET: /* unset process flags */
error = pr_unset(p, (long)argp->flags);
break;
case PCSREG: /* set general registers */
if (PROCESS_NOT_32BIT(p))
error = EOVERFLOW;
else {
kthread_t *t = pr_thread(pnp);
if (!ISTOPPED(t) && !VSTOPPED(t) && !DSTOPPED(t)) {
thread_unlock(t);
error = EBUSY;
} else {
prgregset_t prgregset;
klwp_t *lwp = ttolwp(t);
thread_unlock(t);
mutex_exit(&p->p_lock);
prgregset_32ton(lwp, argp->prgregset,
prgregset);
prsetprregs(lwp, prgregset, 0);
mutex_enter(&p->p_lock);
}
}
break;
case PCSFPREG: /* set floating-point registers */
if (PROCESS_NOT_32BIT(p))
error = EOVERFLOW;
else
error = pr_setfpregs32(pnp, &argp->prfpregset);
break;
case PCSXREG: /* set extra registers */
#if defined(__sparc)
if (PROCESS_NOT_32BIT(p))
error = EOVERFLOW;
else
error = pr_setxregs(pnp, &argp->prxregset);
#else
error = EINVAL;
#endif
break;
case PCSVADDR: /* set virtual address at which to resume */
if (PROCESS_NOT_32BIT(p))
error = EOVERFLOW;
else
error = pr_setvaddr(pnp,
(caddr_t)(uintptr_t)argp->vaddr);
break;
case PCSHOLD: /* set signal-hold mask */
pr_sethold(pnp, &argp->sigset);
break;
case PCSFAULT: /* set mask of traced faults */
pr_setfault(p, &argp->fltset);
break;
case PCCSIG: /* clear current signal */
error = pr_clearsig(pnp);
break;
case PCCFAULT: /* clear current fault */
error = pr_clearflt(pnp);
break;
case PCWATCH: /* set or clear watched areas */
if (PROCESS_NOT_32BIT(p))
error = EOVERFLOW;
else {
prwatch_t prwatch;
prwatch.pr_vaddr = argp->prwatch.pr_vaddr;
prwatch.pr_size = argp->prwatch.pr_size;
prwatch.pr_wflags = argp->prwatch.pr_wflags;
prwatch.pr_pad = argp->prwatch.pr_pad;
error = pr_watch(pnp, &prwatch, &unlocked);
if (error && unlocked)
return (error);
}
break;
case PCAGENT: /* create the /proc agent lwp in the target process */
if (PROCESS_NOT_32BIT(p))
error = EOVERFLOW;
else {
prgregset_t prgregset;
kthread_t *t = pr_thread(pnp);
klwp_t *lwp = ttolwp(t);
thread_unlock(t);
mutex_exit(&p->p_lock);
prgregset_32ton(lwp, argp->prgregset, prgregset);
mutex_enter(&p->p_lock);
error = pr_agent(pnp, prgregset, &unlocked);
if (error && unlocked)
return (error);
}
break;
case PCREAD: /* read from the address space */
case PCWRITE: /* write to the address space */
if (PROCESS_NOT_32BIT(p))
error = EOVERFLOW;
else {
enum uio_rw rw = (cmd == PCREAD)? UIO_READ : UIO_WRITE;
priovec_t priovec;
priovec.pio_base =
(void *)(uintptr_t)argp->priovec.pio_base;
priovec.pio_len = (size_t)argp->priovec.pio_len;
priovec.pio_offset = (off_t)
(uint32_t)argp->priovec.pio_offset;
error = pr_rdwr(p, rw, &priovec);
}
break;
case PCSCRED: /* set the process credentials */
case PCSCREDX:
{
/*
* All the fields in these structures are exactly the same
* and so the structures are compatible. In case this
* ever changes, we catch this with the ASSERT below.
*/
prcred_t *prcred = (prcred_t *)&argp->prcred;
#ifndef __lint
ASSERT(sizeof (prcred_t) == sizeof (prcred32_t));
#endif
error = pr_scred(p, prcred, cr, cmd == PCSCREDX);
break;
}
case PCSPRIV: /* set the process privileges */
{
error = pr_spriv(p, &argp->prpriv, cr);
break;
}
case PCSZONE: /* set the process's zoneid */
error = pr_szoneid(p, (zoneid_t)argp->przoneid, cr);
break;
}
if (error)
prunlock(pnp);
return (error);
}
#endif /* _SYSCALL32_IMPL */
/*
* Return the specific or chosen thread/lwp for a control operation.
* Returns with the thread locked via thread_lock(t).
*/
kthread_t *
pr_thread(prnode_t *pnp)
{
prcommon_t *pcp = pnp->pr_common;
kthread_t *t;
if (pcp->prc_flags & PRC_LWP) {
t = pcp->prc_thread;
ASSERT(t != NULL);
thread_lock(t);
} else {
proc_t *p = pcp->prc_proc;
t = prchoose(p); /* returns locked thread */
ASSERT(t != NULL);
}
return (t);
}
/*
* Direct the process or lwp to stop.
*/
void
pr_stop(prnode_t *pnp)
{
prcommon_t *pcp = pnp->pr_common;
proc_t *p = pcp->prc_proc;
kthread_t *t;
vnode_t *vp;
/*
* If already stopped, do nothing; otherwise flag
* it to be stopped the next time it tries to run.
* If sleeping at interruptible priority, set it
* running so it will stop within cv_wait_sig().
*
* Take care to cooperate with jobcontrol: if an lwp
* is stopped due to the default action of a jobcontrol
* stop signal, flag it to be stopped the next time it
* starts due to a SIGCONT signal.
*/
if (pcp->prc_flags & PRC_LWP)
t = pcp->prc_thread;
else
t = p->p_tlist;
ASSERT(t != NULL);
do {
int notify;
notify = 0;
thread_lock(t);
if (!ISTOPPED(t)) {
t->t_proc_flag |= TP_PRSTOP;
t->t_sig_check = 1; /* do ISSIG */
}
/* Move the thread from wait queue to run queue */
if (ISWAITING(t))
setrun_locked(t);
if (ISWAKEABLE(t)) {
if (t->t_wchan0 == NULL)
setrun_locked(t);
else if (!VSTOPPED(t)) {
/*
* Mark it virtually stopped.
*/
t->t_proc_flag |= TP_PRVSTOP;
notify = 1;
}
}
/*
* force the thread into the kernel
* if it is not already there.
*/
prpokethread(t);
thread_unlock(t);
if (notify &&
(vp = p->p_lwpdir[t->t_dslot].ld_entry->le_trace) != NULL)
prnotify(vp);
if (pcp->prc_flags & PRC_LWP)
break;
} while ((t = t->t_forw) != p->p_tlist);
/*
* We do this just in case the thread we asked
* to stop is in holdlwps() (called from cfork()).
*/
cv_broadcast(&p->p_holdlwps);
}
/*
* Sleep until the lwp stops, but cooperate with
* jobcontrol: Don't wake up if the lwp is stopped
* due to the default action of a jobcontrol stop signal.
* If this is the process file descriptor, sleep
* until all of the process's lwps stop.
*/
int
pr_wait_stop(prnode_t *pnp, time_t timeo)
{
prcommon_t *pcp = pnp->pr_common;
proc_t *p = pcp->prc_proc;
timestruc_t rqtime;
timestruc_t *rqtp = NULL;
int timecheck = 0;
kthread_t *t;
int error;
if (timeo > 0) { /* millisecond timeout */
/*
* Determine the precise future time of the requested timeout.
*/
timestruc_t now;
timecheck = timechanged;
gethrestime(&now);
rqtp = &rqtime;
rqtp->tv_sec = timeo / MILLISEC;
rqtp->tv_nsec = (timeo % MILLISEC) * MICROSEC;
timespecadd(rqtp, &now);
}
if (pcp->prc_flags & PRC_LWP) { /* lwp file descriptor */
t = pcp->prc_thread;
ASSERT(t != NULL);
thread_lock(t);
while (!ISTOPPED(t) && !VSTOPPED(t)) {
thread_unlock(t);
mutex_enter(&pcp->prc_mutex);
prunlock(pnp);
error = pr_wait(pcp, rqtp, timecheck);
if (error) /* -1 is timeout */
return (error);
if ((error = prlock(pnp, ZNO)) != 0)
return (error);
ASSERT(p == pcp->prc_proc);
ASSERT(t == pcp->prc_thread);
thread_lock(t);
}
thread_unlock(t);
} else { /* process file descriptor */
t = prchoose(p); /* returns locked thread */
ASSERT(t != NULL);
ASSERT(MUTEX_HELD(&p->p_lock));
while ((!ISTOPPED(t) && !VSTOPPED(t) && !SUSPENDED(t)) ||
(p->p_flag & SEXITLWPS)) {
thread_unlock(t);
mutex_enter(&pcp->prc_mutex);
prunlock(pnp);
error = pr_wait(pcp, rqtp, timecheck);
if (error) /* -1 is timeout */
return (error);
if ((error = prlock(pnp, ZNO)) != 0)
return (error);
ASSERT(p == pcp->prc_proc);
t = prchoose(p); /* returns locked t */
ASSERT(t != NULL);
}
thread_unlock(t);
}
ASSERT(!(pcp->prc_flags & PRC_DESTROY) && p->p_stat != SZOMB &&
t != NULL && t->t_state != TS_ZOMB);
return (0);
}
int
pr_setrun(prnode_t *pnp, ulong_t flags)
{
prcommon_t *pcp = pnp->pr_common;
proc_t *p = pcp->prc_proc;
kthread_t *t;
klwp_t *lwp;
/*
* Cannot set an lwp running if it is not stopped.
* Also, no lwp other than the /proc agent lwp can
* be set running so long as the /proc agent lwp exists.
*/
t = pr_thread(pnp); /* returns locked thread */
if ((!ISTOPPED(t) && !VSTOPPED(t) &&
!(t->t_proc_flag & TP_PRSTOP)) ||
(p->p_agenttp != NULL &&
(t != p->p_agenttp || !(pcp->prc_flags & PRC_LWP)))) {
thread_unlock(t);
return (EBUSY);
}
thread_unlock(t);
if (flags & ~(PRCSIG|PRCFAULT|PRSTEP|PRSTOP|PRSABORT))
return (EINVAL);
lwp = ttolwp(t);
if ((flags & PRCSIG) && lwp->lwp_cursig != SIGKILL) {
/*
* Discard current siginfo_t, if any.
*/
lwp->lwp_cursig = 0;
lwp->lwp_extsig = 0;
if (lwp->lwp_curinfo) {
siginfofree(lwp->lwp_curinfo);
lwp->lwp_curinfo = NULL;
}
}
if (flags & PRCFAULT)
lwp->lwp_curflt = 0;
/*
* We can't hold p->p_lock when we touch the lwp's registers.
* It may be swapped out and we will get a page fault.
*/
if (flags & PRSTEP) {
mutex_exit(&p->p_lock);
prstep(lwp, 0);
mutex_enter(&p->p_lock);
}
if (flags & PRSTOP) {
t->t_proc_flag |= TP_PRSTOP;
t->t_sig_check = 1; /* do ISSIG */
}
if (flags & PRSABORT)
lwp->lwp_sysabort = 1;
thread_lock(t);
if ((pcp->prc_flags & PRC_LWP) || (flags & (PRSTEP|PRSTOP))) {
/*
* Here, we are dealing with a single lwp.
*/
if (ISTOPPED(t)) {
t->t_schedflag |= TS_PSTART;
t->t_dtrace_stop = 0;
setrun_locked(t);
} else if (flags & PRSABORT) {
t->t_proc_flag &=
~(TP_PRSTOP|TP_PRVSTOP|TP_STOPPING);
setrun_locked(t);
} else if (!(flags & PRSTOP)) {
t->t_proc_flag &=
~(TP_PRSTOP|TP_PRVSTOP|TP_STOPPING);
}
thread_unlock(t);
} else {
/*
* Here, we are dealing with the whole process.
*/
if (ISTOPPED(t)) {
/*
* The representative lwp is stopped on an event
* of interest. We demote it to PR_REQUESTED and
* choose another representative lwp. If the new
* representative lwp is not stopped on an event of
* interest (other than PR_REQUESTED), we set the
* whole process running, else we leave the process
* stopped showing the next event of interest.
*/
kthread_t *tx = NULL;
if (!(flags & PRSABORT) &&
t->t_whystop == PR_SYSENTRY &&
t->t_whatstop == SYS_lwp_exit)
tx = t; /* remember the exiting lwp */
t->t_whystop = PR_REQUESTED;
t->t_whatstop = 0;
thread_unlock(t);
t = prchoose(p); /* returns locked t */
ASSERT(ISTOPPED(t) || VSTOPPED(t));
if (VSTOPPED(t) ||
t->t_whystop == PR_REQUESTED) {
thread_unlock(t);
allsetrun(p);
} else {
thread_unlock(t);
/*
* As a special case, if the old representative
* lwp was stopped on entry to _lwp_exit()
* (and we are not aborting the system call),
* we set the old representative lwp running.
* We do this so that the next process stop
* will find the exiting lwp gone.
*/
if (tx != NULL) {
thread_lock(tx);
tx->t_schedflag |= TS_PSTART;
t->t_dtrace_stop = 0;
setrun_locked(tx);
thread_unlock(tx);
}
}
} else {
/*
* No event of interest; set all of the lwps running.
*/
if (flags & PRSABORT) {
t->t_proc_flag &=
~(TP_PRSTOP|TP_PRVSTOP|TP_STOPPING);
setrun_locked(t);
}
thread_unlock(t);
allsetrun(p);
}
}
return (0);
}
/*
* Wait until process/lwp stops or until timer expires.
* Return EINTR for an interruption, -1 for timeout, else 0.
*/
int
pr_wait(prcommon_t *pcp, /* prcommon referring to process/lwp */
timestruc_t *ts, /* absolute time of timeout, if any */
int timecheck)
{
int rval;
ASSERT(MUTEX_HELD(&pcp->prc_mutex));
rval = cv_waituntil_sig(&pcp->prc_wait, &pcp->prc_mutex, ts, timecheck);
mutex_exit(&pcp->prc_mutex);
switch (rval) {
case 0:
return (EINTR);
case -1:
return (-1);
default:
return (0);
}
}
/*
* Make all threads in the process runnable.
*/
void
allsetrun(proc_t *p)
{
kthread_t *t;
ASSERT(MUTEX_HELD(&p->p_lock));
if ((t = p->p_tlist) != NULL) {
do {
thread_lock(t);
ASSERT(!(t->t_proc_flag & TP_LWPEXIT));
t->t_proc_flag &= ~(TP_PRSTOP|TP_PRVSTOP|TP_STOPPING);
if (ISTOPPED(t)) {
t->t_schedflag |= TS_PSTART;
t->t_dtrace_stop = 0;
setrun_locked(t);
}
thread_unlock(t);
} while ((t = t->t_forw) != p->p_tlist);
}
}
/*
* Wait for the process to die.
* We do this after sending SIGKILL because we know it will
* die soon and we want subsequent operations to return ENOENT.
*/
void
pr_wait_die(prnode_t *pnp)
{
proc_t *p;
mutex_enter(&pidlock);
while ((p = pnp->pr_common->prc_proc) != NULL && p->p_stat != SZOMB) {
if (!cv_wait_sig(&p->p_srwchan_cv, &pidlock))
break;
}
mutex_exit(&pidlock);
}
static void
pr_settrace(proc_t *p, sigset_t *sp)
{
prdelset(sp, SIGKILL);
prassignset(&p->p_sigmask, sp);
if (!sigisempty(&p->p_sigmask))
p->p_proc_flag |= P_PR_TRACE;
else if (prisempty(&p->p_fltmask)) {
user_t *up = PTOU(p);
if (up->u_systrap == 0)
p->p_proc_flag &= ~P_PR_TRACE;
}
}
int
pr_setsig(prnode_t *pnp, siginfo_t *sip)
{
int sig = sip->si_signo;
prcommon_t *pcp = pnp->pr_common;
proc_t *p = pcp->prc_proc;
kthread_t *t;
klwp_t *lwp;
int error = 0;
t = pr_thread(pnp); /* returns locked thread */
thread_unlock(t);
lwp = ttolwp(t);
if (sig < 0 || sig >= NSIG)
/* Zero allowed here */
error = EINVAL;
else if (lwp->lwp_cursig == SIGKILL)
/* "can't happen", but just in case */
error = EBUSY;
else if ((lwp->lwp_cursig = (uchar_t)sig) == 0) {
lwp->lwp_extsig = 0;
/*
* Discard current siginfo_t, if any.
*/
if (lwp->lwp_curinfo) {
siginfofree(lwp->lwp_curinfo);
lwp->lwp_curinfo = NULL;
}
} else {
kthread_t *tx;
sigqueue_t *sqp;
/* drop p_lock to do kmem_alloc(KM_SLEEP) */
mutex_exit(&p->p_lock);
sqp = kmem_zalloc(sizeof (sigqueue_t), KM_SLEEP);
mutex_enter(&p->p_lock);
if (lwp->lwp_curinfo == NULL)
lwp->lwp_curinfo = sqp;
else
kmem_free(sqp, sizeof (sigqueue_t));
/*
* Copy contents of info to current siginfo_t.
*/
bcopy(sip, &lwp->lwp_curinfo->sq_info,
sizeof (lwp->lwp_curinfo->sq_info));
/*
* Prevent contents published by si_zoneid-unaware /proc
* consumers from being incorrectly filtered. Because
* an uninitialized si_zoneid is the same as
* GLOBAL_ZONEID, this means that you can't pr_setsig a
* process in a non-global zone with a siginfo which
* appears to come from the global zone.
*/
if (SI_FROMUSER(sip) && sip->si_zoneid == 0)
lwp->lwp_curinfo->sq_info.si_zoneid =
p->p_zone->zone_id;
/*
* Side-effects for SIGKILL and jobcontrol signals.
*/
if (sig == SIGKILL) {
p->p_flag |= SKILLED;
p->p_flag &= ~SEXTKILLED;
} else if (sig == SIGCONT) {
p->p_flag |= SSCONT;
sigdelq(p, NULL, SIGSTOP);
sigdelq(p, NULL, SIGTSTP);
sigdelq(p, NULL, SIGTTOU);
sigdelq(p, NULL, SIGTTIN);
sigdiffset(&p->p_sig, &stopdefault);
sigdiffset(&p->p_extsig, &stopdefault);
if ((tx = p->p_tlist) != NULL) {
do {
sigdelq(p, tx, SIGSTOP);
sigdelq(p, tx, SIGTSTP);
sigdelq(p, tx, SIGTTOU);
sigdelq(p, tx, SIGTTIN);
sigdiffset(&tx->t_sig, &stopdefault);
sigdiffset(&tx->t_extsig, &stopdefault);
} while ((tx = tx->t_forw) != p->p_tlist);
}
} else if (sigismember(&stopdefault, sig)) {
if (PTOU(p)->u_signal[sig-1] == SIG_DFL &&
(sig == SIGSTOP || !p->p_pgidp->pid_pgorphaned))
p->p_flag &= ~SSCONT;
sigdelq(p, NULL, SIGCONT);
sigdelset(&p->p_sig, SIGCONT);
sigdelset(&p->p_extsig, SIGCONT);
if ((tx = p->p_tlist) != NULL) {
do {
sigdelq(p, tx, SIGCONT);
sigdelset(&tx->t_sig, SIGCONT);
sigdelset(&tx->t_extsig, SIGCONT);
} while ((tx = tx->t_forw) != p->p_tlist);
}
}
thread_lock(t);
if (ISWAKEABLE(t) || ISWAITING(t)) {
/* Set signalled sleeping/waiting lwp running */
setrun_locked(t);
} else if (t->t_state == TS_STOPPED && sig == SIGKILL) {
/* If SIGKILL, set stopped lwp running */
p->p_stopsig = 0;
t->t_schedflag |= TS_XSTART | TS_PSTART;
t->t_dtrace_stop = 0;
setrun_locked(t);
}
t->t_sig_check = 1; /* so ISSIG will be done */
thread_unlock(t);
/*
* More jobcontrol side-effects.
*/
if (sig == SIGCONT && (tx = p->p_tlist) != NULL) {
p->p_stopsig = 0;
do {
thread_lock(tx);
if (tx->t_state == TS_STOPPED &&
tx->t_whystop == PR_JOBCONTROL) {
tx->t_schedflag |= TS_XSTART;
setrun_locked(tx);
}
thread_unlock(tx);
} while ((tx = tx->t_forw) != p->p_tlist);
}
}
return (error);
}
int
pr_kill(prnode_t *pnp, int sig, cred_t *cr)
{
prcommon_t *pcp = pnp->pr_common;
proc_t *p = pcp->prc_proc;
k_siginfo_t info;
if (sig <= 0 || sig >= NSIG)
return (EINVAL);
bzero(&info, sizeof (info));
info.si_signo = sig;
info.si_code = SI_USER;
info.si_pid = curproc->p_pid;
info.si_ctid = PRCTID(curproc);
info.si_zoneid = getzoneid();
info.si_uid = crgetruid(cr);
sigaddq(p, (pcp->prc_flags & PRC_LWP)?
pcp->prc_thread : NULL, &info, KM_NOSLEEP);
return (0);
}
int
pr_unkill(prnode_t *pnp, int sig)
{
prcommon_t *pcp = pnp->pr_common;
proc_t *p = pcp->prc_proc;
sigqueue_t *infop = NULL;
if (sig <= 0 || sig >= NSIG || sig == SIGKILL)
return (EINVAL);
if (pcp->prc_flags & PRC_LWP)
sigdeq(p, pcp->prc_thread, sig, &infop);
else
sigdeq(p, NULL, sig, &infop);
if (infop)
siginfofree(infop);
return (0);
}
int
pr_nice(proc_t *p, int nice, cred_t *cr)
{
kthread_t *t;
int err;
int error = 0;
t = p->p_tlist;
do {
ASSERT(!(t->t_proc_flag & TP_LWPEXIT));
err = CL_DONICE(t, cr, nice, (int *)NULL);
if (error == 0)
error = err;
} while ((t = t->t_forw) != p->p_tlist);
return (error);
}
void
pr_setentryexit(proc_t *p, sysset_t *sysset, int entry)
{
user_t *up = PTOU(p);
if (entry) {
prassignset(&up->u_entrymask, sysset);
} else {
prassignset(&up->u_exitmask, sysset);
}
if (!prisempty(&up->u_entrymask) ||
!prisempty(&up->u_exitmask)) {
up->u_systrap = 1;
p->p_proc_flag |= P_PR_TRACE;
set_proc_sys(p); /* set pre and post-sys flags */
} else {
up->u_systrap = 0;
if (sigisempty(&p->p_sigmask) &&
prisempty(&p->p_fltmask))
p->p_proc_flag &= ~P_PR_TRACE;
}
}
#define ALLFLAGS \
(PR_FORK|PR_RLC|PR_KLC|PR_ASYNC|PR_BPTADJ|PR_MSACCT|PR_MSFORK|PR_PTRACE)
int
pr_set(proc_t *p, long flags)
{
if ((p->p_flag & SSYS) || p->p_as == &kas)
return (EBUSY);
if (flags & ~ALLFLAGS)
return (EINVAL);
if (flags & PR_FORK)
p->p_proc_flag |= P_PR_FORK;
if (flags & PR_RLC)
p->p_proc_flag |= P_PR_RUNLCL;
if (flags & PR_KLC)
p->p_proc_flag |= P_PR_KILLCL;
if (flags & PR_ASYNC)
p->p_proc_flag |= P_PR_ASYNC;
if (flags & PR_BPTADJ)
p->p_proc_flag |= P_PR_BPTADJ;
if (flags & PR_MSACCT)
if ((p->p_flag & SMSACCT) == 0)
estimate_msacct(p->p_tlist, gethrtime());
if (flags & PR_MSFORK)
p->p_flag |= SMSFORK;
if (flags & PR_PTRACE) {
p->p_proc_flag |= P_PR_PTRACE;
/* ptraced process must die if parent dead */
if (p->p_ppid == 1)
sigtoproc(p, NULL, SIGKILL);
}
return (0);
}
int
pr_unset(proc_t *p, long flags)
{
if ((p->p_flag & SSYS) || p->p_as == &kas)
return (EBUSY);
if (flags & ~ALLFLAGS)
return (EINVAL);
if (flags & PR_FORK)
p->p_proc_flag &= ~P_PR_FORK;
if (flags & PR_RLC)
p->p_proc_flag &= ~P_PR_RUNLCL;
if (flags & PR_KLC)
p->p_proc_flag &= ~P_PR_KILLCL;
if (flags & PR_ASYNC)
p->p_proc_flag &= ~P_PR_ASYNC;
if (flags & PR_BPTADJ)
p->p_proc_flag &= ~P_PR_BPTADJ;
if (flags & PR_MSACCT)
disable_msacct(p);
if (flags & PR_MSFORK)
p->p_flag &= ~SMSFORK;
if (flags & PR_PTRACE)
p->p_proc_flag &= ~P_PR_PTRACE;
return (0);
}
static int
pr_setfpregs(prnode_t *pnp, prfpregset_t *prfpregset)
{
proc_t *p = pnp->pr_common->prc_proc;
kthread_t *t = pr_thread(pnp); /* returns locked thread */
if (!ISTOPPED(t) && !VSTOPPED(t) && !DSTOPPED(t)) {
thread_unlock(t);
return (EBUSY);
}
if (!prhasfp()) {
thread_unlock(t);
return (EINVAL); /* No FP support */
}
/* drop p_lock while touching the lwp's stack */
thread_unlock(t);
mutex_exit(&p->p_lock);
prsetprfpregs(ttolwp(t), prfpregset);
mutex_enter(&p->p_lock);
return (0);
}
#ifdef _SYSCALL32_IMPL
static int
pr_setfpregs32(prnode_t *pnp, prfpregset32_t *prfpregset)
{
proc_t *p = pnp->pr_common->prc_proc;
kthread_t *t = pr_thread(pnp); /* returns locked thread */
if (!ISTOPPED(t) && !VSTOPPED(t) && !DSTOPPED(t)) {
thread_unlock(t);
return (EBUSY);
}
if (!prhasfp()) {
thread_unlock(t);
return (EINVAL); /* No FP support */
}
/* drop p_lock while touching the lwp's stack */
thread_unlock(t);
mutex_exit(&p->p_lock);
prsetprfpregs32(ttolwp(t), prfpregset);
mutex_enter(&p->p_lock);
return (0);
}
#endif /* _SYSCALL32_IMPL */
#if defined(__sparc)
/* ARGSUSED */
static int
pr_setxregs(prnode_t *pnp, prxregset_t *prxregset)
{
proc_t *p = pnp->pr_common->prc_proc;
kthread_t *t = pr_thread(pnp); /* returns locked thread */
if (!ISTOPPED(t) && !VSTOPPED(t) && !DSTOPPED(t)) {
thread_unlock(t);
return (EBUSY);
}
thread_unlock(t);
if (!prhasx(p))
return (EINVAL); /* No extra register support */
/* drop p_lock while touching the lwp's stack */
mutex_exit(&p->p_lock);
prsetprxregs(ttolwp(t), (caddr_t)prxregset);
mutex_enter(&p->p_lock);
return (0);
}
static int
pr_setasrs(prnode_t *pnp, asrset_t asrset)
{
proc_t *p = pnp->pr_common->prc_proc;
kthread_t *t = pr_thread(pnp); /* returns locked thread */
if (!ISTOPPED(t) && !VSTOPPED(t) && !DSTOPPED(t)) {
thread_unlock(t);
return (EBUSY);
}
thread_unlock(t);
/* drop p_lock while touching the lwp's stack */
mutex_exit(&p->p_lock);
prsetasregs(ttolwp(t), asrset);
mutex_enter(&p->p_lock);
return (0);
}
#endif
static int
pr_setvaddr(prnode_t *pnp, caddr_t vaddr)
{
proc_t *p = pnp->pr_common->prc_proc;
kthread_t *t = pr_thread(pnp); /* returns locked thread */
if (!ISTOPPED(t) && !VSTOPPED(t) && !DSTOPPED(t)) {
thread_unlock(t);
return (EBUSY);
}
/* drop p_lock while touching the lwp's stack */
thread_unlock(t);
mutex_exit(&p->p_lock);
prsvaddr(ttolwp(t), vaddr);
mutex_enter(&p->p_lock);
return (0);
}
void
pr_sethold(prnode_t *pnp, sigset_t *sp)
{
proc_t *p = pnp->pr_common->prc_proc;
kthread_t *t = pr_thread(pnp); /* returns locked thread */
schedctl_finish_sigblock(t);
sigutok(sp, &t->t_hold);
if (ISWAKEABLE(t) &&
(fsig(&p->p_sig, t) || fsig(&t->t_sig, t)))
setrun_locked(t);
t->t_sig_check = 1; /* so thread will see new holdmask */
thread_unlock(t);
}
void
pr_setfault(proc_t *p, fltset_t *fltp)
{
prassignset(&p->p_fltmask, fltp);
if (!prisempty(&p->p_fltmask))
p->p_proc_flag |= P_PR_TRACE;
else if (sigisempty(&p->p_sigmask)) {
user_t *up = PTOU(p);
if (up->u_systrap == 0)
p->p_proc_flag &= ~P_PR_TRACE;
}
}
static int
pr_clearsig(prnode_t *pnp)
{
kthread_t *t = pr_thread(pnp); /* returns locked thread */
klwp_t *lwp = ttolwp(t);
thread_unlock(t);
if (lwp->lwp_cursig == SIGKILL)
return (EBUSY);
/*
* Discard current siginfo_t, if any.
*/
lwp->lwp_cursig = 0;
lwp->lwp_extsig = 0;
if (lwp->lwp_curinfo) {
siginfofree(lwp->lwp_curinfo);
lwp->lwp_curinfo = NULL;
}
return (0);
}
static int
pr_clearflt(prnode_t *pnp)
{
kthread_t *t = pr_thread(pnp); /* returns locked thread */
thread_unlock(t);
ttolwp(t)->lwp_curflt = 0;
return (0);
}
static int
pr_watch(prnode_t *pnp, prwatch_t *pwp, int *unlocked)
{
proc_t *p = pnp->pr_common->prc_proc;
struct as *as = p->p_as;
uintptr_t vaddr = pwp->pr_vaddr;
size_t size = pwp->pr_size;
int wflags = pwp->pr_wflags;
ulong_t newpage = 0;
struct watched_area *pwa;
int error;
*unlocked = 0;
/*
* Can't apply to a system process.
*/
if ((p->p_flag & SSYS) || p->p_as == &kas)
return (EBUSY);
/*
* Verify that the address range does not wrap
* and that only the proper flags were specified.
*/
if ((wflags & ~WA_TRAPAFTER) == 0)
size = 0;
if (vaddr + size < vaddr ||
(wflags & ~(WA_READ|WA_WRITE|WA_EXEC|WA_TRAPAFTER)) != 0 ||
((wflags & ~WA_TRAPAFTER) != 0 && size == 0))
return (EINVAL);
/*
* Don't let the address range go above as->a_userlimit.
* There is no error here, just a limitation.
*/
if (vaddr >= (uintptr_t)as->a_userlimit)
return (0);
if (vaddr + size > (uintptr_t)as->a_userlimit)
size = (uintptr_t)as->a_userlimit - vaddr;
/*
* Compute maximum number of pages this will add.
*/
if ((wflags & ~WA_TRAPAFTER) != 0) {
ulong_t pagespan = (vaddr + size) - (vaddr & PAGEMASK);
newpage = btopr(pagespan);
if (newpage > 2 * prnwatch)
return (E2BIG);
}
/*
* Force the process to be fully stopped.
*/
if (p == curproc) {
prunlock(pnp);
while (holdwatch() != 0)
continue;
if ((error = prlock(pnp, ZNO)) != 0) {
continuelwps(p);
*unlocked = 1;
return (error);
}
} else {
pauselwps(p);
while (pr_allstopped(p, 0) > 0) {
/*
* This cv/mutex pair is persistent even
* if the process disappears after we
* unmark it and drop p->p_lock.
*/
kcondvar_t *cv = &pr_pid_cv[p->p_slot];
kmutex_t *mp = &p->p_lock;
prunmark(p);
(void) cv_wait(cv, mp);
mutex_exit(mp);
if ((error = prlock(pnp, ZNO)) != 0) {
/*
* Unpause the process if it exists.
*/
p = pr_p_lock(pnp);
mutex_exit(&pr_pidlock);
if (p != NULL) {
unpauselwps(p);
prunlock(pnp);
}
*unlocked = 1;
return (error);
}
}
}
/*
* Drop p->p_lock in order to perform the rest of this.
* The process is still locked with the P_PR_LOCK flag.
*/
mutex_exit(&p->p_lock);
pwa = kmem_alloc(sizeof (struct watched_area), KM_SLEEP);
pwa->wa_vaddr = (caddr_t)vaddr;
pwa->wa_eaddr = (caddr_t)vaddr + size;
pwa->wa_flags = (ulong_t)wflags;
error = ((pwa->wa_flags & ~WA_TRAPAFTER) == 0)?
clear_watched_area(p, pwa) :
set_watched_area(p, pwa);
if (p == curproc) {
setallwatch();
mutex_enter(&p->p_lock);
continuelwps(p);
} else {
mutex_enter(&p->p_lock);
unpauselwps(p);
}
return (error);
}
/* jobcontrol stopped, but with a /proc directed stop in effect */
#define JDSTOPPED(t) \
((t)->t_state == TS_STOPPED && \
(t)->t_whystop == PR_JOBCONTROL && \
((t)->t_proc_flag & TP_PRSTOP))
/*
* pr_agent() creates the agent lwp. If the process is exiting while
* we are creating an agent lwp, then exitlwps() waits until the
* agent has been created using prbarrier().
*/
static int
pr_agent(prnode_t *pnp, prgregset_t prgregset, int *unlocked)
{
proc_t *p = pnp->pr_common->prc_proc;
prcommon_t *pcp;
kthread_t *t;
kthread_t *ct;
klwp_t *clwp;
k_sigset_t smask;
int cid;
void *bufp = NULL;
int error;
*unlocked = 0;
/*
* Cannot create the /proc agent lwp if :-
* - the process is not fully stopped or directed to stop.
* - there is an agent lwp already.
* - the process has been killed.
* - the process is exiting.
* - it's a vfork(2) parent.
*/
t = prchoose(p); /* returns locked thread */
ASSERT(t != NULL);
if ((!ISTOPPED(t) && !VSTOPPED(t) && !SUSPENDED(t) && !JDSTOPPED(t)) ||
p->p_agenttp != NULL ||
(p->p_flag & (SKILLED | SEXITING | SVFWAIT))) {
thread_unlock(t);
return (EBUSY);
}
thread_unlock(t);
mutex_exit(&p->p_lock);
sigfillset(&smask);
sigdiffset(&smask, &cantmask);
clwp = lwp_create(lwp_rtt, NULL, 0, p, TS_STOPPED,
t->t_pri, &smask, NOCLASS, 0);
if (clwp == NULL) {
mutex_enter(&p->p_lock);
return (ENOMEM);
}
prsetprregs(clwp, prgregset, 1);
retry:
cid = t->t_cid;
(void) CL_ALLOC(&bufp, cid, KM_SLEEP);
mutex_enter(&p->p_lock);
if (cid != t->t_cid) {
/*
* Someone just changed this thread's scheduling class,
* so try pre-allocating the buffer again. Hopefully we
* don't hit this often.
*/
mutex_exit(&p->p_lock);
CL_FREE(cid, bufp);
goto retry;
}
clwp->lwp_ap = clwp->lwp_arg;
clwp->lwp_eosys = NORMALRETURN;
ct = lwptot(clwp);
ct->t_clfuncs = t->t_clfuncs;
CL_FORK(t, ct, bufp);
ct->t_cid = t->t_cid;
ct->t_proc_flag |= TP_PRSTOP;
/*
* Setting t_sysnum to zero causes post_syscall()
* to bypass all syscall checks and go directly to
* if (issig()) psig();
* so that the agent lwp will stop in issig_forreal()
* showing PR_REQUESTED.
*/
ct->t_sysnum = 0;
ct->t_post_sys = 1;
ct->t_sig_check = 1;
p->p_agenttp = ct;
ct->t_proc_flag &= ~TP_HOLDLWP;
pcp = pnp->pr_pcommon;
mutex_enter(&pcp->prc_mutex);
lwp_create_done(ct);
/*
* Don't return until the agent is stopped on PR_REQUESTED.
*/
for (;;) {
prunlock(pnp);
*unlocked = 1;
/*
* Wait for the agent to stop and notify us.
* If we've been interrupted, return that information.
*/
error = pr_wait(pcp, NULL, 0);
if (error == EINTR) {
error = 0;
break;
}
/*
* Confirm that the agent LWP has stopped.
*/
if ((error = prlock(pnp, ZNO)) != 0)
break;
*unlocked = 0;
/*
* Since we dropped the lock on the process, the agent
* may have disappeared or changed. Grab the current
* agent and check fail if it has disappeared.
*/
if ((ct = p->p_agenttp) == NULL) {
error = ENOENT;
break;
}
mutex_enter(&pcp->prc_mutex);
thread_lock(ct);
if (ISTOPPED(ct)) {
thread_unlock(ct);
mutex_exit(&pcp->prc_mutex);
break;
}
thread_unlock(ct);
}
return (error ? error : -1);
}
static int
pr_rdwr(proc_t *p, enum uio_rw rw, priovec_t *pio)
{
caddr_t base = (caddr_t)pio->pio_base;
size_t cnt = pio->pio_len;
uintptr_t offset = (uintptr_t)pio->pio_offset;
struct uio auio;
struct iovec aiov;
int error = 0;
if ((p->p_flag & SSYS) || p->p_as == &kas)
error = EIO;
else if ((base + cnt) < base || (offset + cnt) < offset)
error = EINVAL;
else if (cnt != 0) {
aiov.iov_base = base;
aiov.iov_len = cnt;
auio.uio_loffset = offset;
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
auio.uio_resid = cnt;
auio.uio_segflg = UIO_USERSPACE;
auio.uio_llimit = (longlong_t)MAXOFFSET_T;
auio.uio_fmode = FREAD|FWRITE;
auio.uio_extflg = UIO_COPY_DEFAULT;
mutex_exit(&p->p_lock);
error = prusrio(p, rw, &auio, 0);
mutex_enter(&p->p_lock);
/*
* We have no way to return the i/o count,
* like read() or write() would do, so we
* return an error if the i/o was truncated.
*/
if (auio.uio_resid != 0 && error == 0)
error = EIO;
}
return (error);
}
static int
pr_scred(proc_t *p, prcred_t *prcred, cred_t *cr, boolean_t dogrps)
{
kthread_t *t;
cred_t *oldcred;
cred_t *newcred;
uid_t oldruid;
int error;
if (!VALID_UID(prcred->pr_euid) ||
!VALID_UID(prcred->pr_ruid) ||
!VALID_UID(prcred->pr_suid) ||
!VALID_GID(prcred->pr_egid) ||
!VALID_GID(prcred->pr_rgid) ||
!VALID_GID(prcred->pr_sgid))
return (EINVAL);
if (dogrps) {
int ngrp = prcred->pr_ngroups;
int i;
if (ngrp < 0 || ngrp > ngroups_max)
return (EINVAL);
for (i = 0; i < ngrp; i++) {
if (!VALID_GID(prcred->pr_groups[i]))
return (EINVAL);
}
}
error = secpolicy_allow_setid(cr, prcred->pr_euid, B_FALSE);
if (error == 0 && prcred->pr_ruid != prcred->pr_euid)
error = secpolicy_allow_setid(cr, prcred->pr_ruid, B_FALSE);
if (error == 0 && prcred->pr_suid != prcred->pr_euid &&
prcred->pr_suid != prcred->pr_ruid)
error = secpolicy_allow_setid(cr, prcred->pr_suid, B_FALSE);
if (error)
return (error);
mutex_exit(&p->p_lock);
/* hold old cred so it doesn't disappear while we dup it */
mutex_enter(&p->p_crlock);
crhold(oldcred = p->p_cred);
mutex_exit(&p->p_crlock);
newcred = crdup(oldcred);
oldruid = crgetruid(oldcred);
crfree(oldcred);
/* Error checking done above */
(void) crsetresuid(newcred, prcred->pr_ruid, prcred->pr_euid,
prcred->pr_suid);
(void) crsetresgid(newcred, prcred->pr_rgid, prcred->pr_egid,
prcred->pr_sgid);
if (dogrps) {
(void) crsetgroups(newcred, prcred->pr_ngroups,
prcred->pr_groups);
}
mutex_enter(&p->p_crlock);
oldcred = p->p_cred;
p->p_cred = newcred;
mutex_exit(&p->p_crlock);
crfree(oldcred);
/*
* Keep count of processes per uid consistent.
*/
if (oldruid != prcred->pr_ruid) {
zoneid_t zoneid = crgetzoneid(newcred);
mutex_enter(&pidlock);
upcount_dec(oldruid, zoneid);
upcount_inc(prcred->pr_ruid, zoneid);
mutex_exit(&pidlock);
}
/*
* Broadcast the cred change to the threads.
*/
mutex_enter(&p->p_lock);
t = p->p_tlist;
do {
t->t_pre_sys = 1; /* so syscall will get new cred */
} while ((t = t->t_forw) != p->p_tlist);
return (0);
}
/*
* Change process credentials to specified zone. Used to temporarily
* set a process to run in the global zone; only transitions between
* the process's actual zone and the global zone are allowed.
*/
static int
pr_szoneid(proc_t *p, zoneid_t zoneid, cred_t *cr)
{
kthread_t *t;
cred_t *oldcred;
cred_t *newcred;
zone_t *zptr;
zoneid_t oldzoneid;
if (secpolicy_zone_config(cr) != 0)
return (EPERM);
if (zoneid != GLOBAL_ZONEID && zoneid != p->p_zone->zone_id)
return (EINVAL);
if ((zptr = zone_find_by_id(zoneid)) == NULL)
return (EINVAL);
mutex_exit(&p->p_lock);
mutex_enter(&p->p_crlock);
oldcred = p->p_cred;
crhold(oldcred);
mutex_exit(&p->p_crlock);
newcred = crdup(oldcred);
oldzoneid = crgetzoneid(oldcred);
crfree(oldcred);
crsetzone(newcred, zptr);
zone_rele(zptr);
mutex_enter(&p->p_crlock);
oldcred = p->p_cred;
p->p_cred = newcred;
mutex_exit(&p->p_crlock);
crfree(oldcred);
/*
* The target process is changing zones (according to its cred), so
* update the per-zone upcounts, which are based on process creds.
*/
if (oldzoneid != zoneid) {
uid_t ruid = crgetruid(newcred);
mutex_enter(&pidlock);
upcount_dec(ruid, oldzoneid);
upcount_inc(ruid, zoneid);
mutex_exit(&pidlock);
}
/*
* Broadcast the cred change to the threads.
*/
mutex_enter(&p->p_lock);
t = p->p_tlist;
do {
t->t_pre_sys = 1; /* so syscall will get new cred */
} while ((t = t->t_forw) != p->p_tlist);
return (0);
}
static int
pr_spriv(proc_t *p, prpriv_t *prpriv, cred_t *cr)
{
kthread_t *t;
int err;
ASSERT(MUTEX_HELD(&p->p_lock));
if ((err = priv_pr_spriv(p, prpriv, cr)) == 0) {
/*
* Broadcast the cred change to the threads.
*/
t = p->p_tlist;
do {
t->t_pre_sys = 1; /* so syscall will get new cred */
} while ((t = t->t_forw) != p->p_tlist);
}
return (err);
}
/*
* Return -1 if the process is the parent of a vfork(1) whose child has yet to
* terminate or perform an exec(2).
*
* Returns 0 if the process is fully stopped except for the current thread (if
* we are operating on our own process), 1 otherwise.
*
* If the watchstop flag is set, then we ignore threads with TP_WATCHSTOP set.
* See holdwatch() for details.
*/
int
pr_allstopped(proc_t *p, int watchstop)
{
kthread_t *t;
int rv = 0;
ASSERT(MUTEX_HELD(&p->p_lock));
if (p->p_flag & SVFWAIT) /* waiting for vfork'd child to exec */
return (-1);
if ((t = p->p_tlist) != NULL) {
do {
if (t == curthread || VSTOPPED(t) ||
(watchstop && (t->t_proc_flag & TP_WATCHSTOP)))
continue;
thread_lock(t);
switch (t->t_state) {
case TS_ZOMB:
case TS_STOPPED:
break;
case TS_SLEEP:
if (!(t->t_flag & T_WAKEABLE) ||
t->t_wchan0 == NULL)
rv = 1;
break;
default:
rv = 1;
break;
}
thread_unlock(t);
} while (rv == 0 && (t = t->t_forw) != p->p_tlist);
}
return (rv);
}
/*
* Cause all lwps in the process to pause (for watchpoint operations).
*/
static void
pauselwps(proc_t *p)
{
kthread_t *t;
ASSERT(MUTEX_HELD(&p->p_lock));
ASSERT(p != curproc);
if ((t = p->p_tlist) != NULL) {
do {
thread_lock(t);
t->t_proc_flag |= TP_PAUSE;
aston(t);
if ((ISWAKEABLE(t) && (t->t_wchan0 == NULL)) ||
ISWAITING(t)) {
setrun_locked(t);
}
prpokethread(t);
thread_unlock(t);
} while ((t = t->t_forw) != p->p_tlist);
}
}
/*
* undo the effects of pauselwps()
*/
static void
unpauselwps(proc_t *p)
{
kthread_t *t;
ASSERT(MUTEX_HELD(&p->p_lock));
ASSERT(p != curproc);
if ((t = p->p_tlist) != NULL) {
do {
thread_lock(t);
t->t_proc_flag &= ~TP_PAUSE;
if (t->t_state == TS_STOPPED) {
t->t_schedflag |= TS_UNPAUSE;
t->t_dtrace_stop = 0;
setrun_locked(t);
}
thread_unlock(t);
} while ((t = t->t_forw) != p->p_tlist);
}
}
/*
* Cancel all watched areas. Called from prclose().
*/
proc_t *
pr_cancel_watch(prnode_t *pnp)
{
proc_t *p = pnp->pr_pcommon->prc_proc;
struct as *as;
kthread_t *t;
ASSERT(MUTEX_HELD(&p->p_lock) && (p->p_proc_flag & P_PR_LOCK));
if (!pr_watch_active(p))
return (p);
/*
* Pause the process before dealing with the watchpoints.
*/
if (p == curproc) {
prunlock(pnp);
while (holdwatch() != 0)
continue;
p = pr_p_lock(pnp);
mutex_exit(&pr_pidlock);
ASSERT(p == curproc);
} else {
pauselwps(p);
while (p != NULL && pr_allstopped(p, 0) > 0) {
/*
* This cv/mutex pair is persistent even
* if the process disappears after we
* unmark it and drop p->p_lock.
*/
kcondvar_t *cv = &pr_pid_cv[p->p_slot];
kmutex_t *mp = &p->p_lock;
prunmark(p);
(void) cv_wait(cv, mp);
mutex_exit(mp);
p = pr_p_lock(pnp); /* NULL if process disappeared */
mutex_exit(&pr_pidlock);
}
}
if (p == NULL) /* the process disappeared */
return (NULL);
ASSERT(p == pnp->pr_pcommon->prc_proc);
ASSERT(MUTEX_HELD(&p->p_lock) && (p->p_proc_flag & P_PR_LOCK));
if (pr_watch_active(p)) {
pr_free_watchpoints(p);
if ((t = p->p_tlist) != NULL) {
do {
watch_disable(t);
} while ((t = t->t_forw) != p->p_tlist);
}
}
if ((as = p->p_as) != NULL) {
avl_tree_t *tree;
struct watched_page *pwp;
/*
* If this is the parent of a vfork, the watched page
* list has been moved temporarily to p->p_wpage.
*/
if (avl_numnodes(&p->p_wpage) != 0)
tree = &p->p_wpage;
else
tree = &as->a_wpage;
mutex_exit(&p->p_lock);
AS_LOCK_ENTER(as, &as->a_lock, RW_WRITER);
for (pwp = avl_first(tree); pwp != NULL;
pwp = AVL_NEXT(tree, pwp)) {
pwp->wp_read = 0;
pwp->wp_write = 0;
pwp->wp_exec = 0;
if ((pwp->wp_flags & WP_SETPROT) == 0) {
pwp->wp_flags |= WP_SETPROT;
pwp->wp_prot = pwp->wp_oprot;
pwp->wp_list = p->p_wprot;
p->p_wprot = pwp;
}
}
AS_LOCK_EXIT(as, &as->a_lock);
mutex_enter(&p->p_lock);
}
/*
* Unpause the process now.
*/
if (p == curproc)
continuelwps(p);
else
unpauselwps(p);
return (p);
}