summaryrefslogtreecommitdiffstats
path: root/sys/kern/sysv_shm.c
diff options
context:
space:
mode:
authorjamie <jamie@FreeBSD.org>2016-04-30 04:02:32 +0000
committerjamie <jamie@FreeBSD.org>2016-04-30 04:02:32 +0000
commit75525160315904c83a57f7fde43bdf7d0aa40a42 (patch)
treeb702d80e5fbb4b719a105ae465cdf6329ed024a7 /sys/kern/sysv_shm.c
parent8f241805478ee6cea6271b7f9181a35941763fbe (diff)
downloadFreeBSD-src-75525160315904c83a57f7fde43bdf7d0aa40a42.zip
FreeBSD-src-75525160315904c83a57f7fde43bdf7d0aa40a42.tar.gz
MFC r298584:
Note the existence of module-specific jail paramters, starting with the linux.* parameters when linux emulation is loaded. MFC r298585: Encapsulate SYSV IPC objects in jails. Define per-module parameters sysvmsg, sysvsem, and sysvshm, with the following bahavior: inherit: allow full access to the IPC primitives. This is the same as the current setup with allow.sysvipc is on. Jails and the base system can see (and moduly) each other's objects, which is generally considered a bad thing (though may be useful in some circumstances). disable: all no access, same as the current setup with allow.sysvipc off. new: A jail may see use the IPC objects that it has created. It also gets its own IPC key namespace, so different jails may have their own objects using the same key value. The parent jail (or base system) can see the jail's IPC objects, but not its keys. PR: 48471
Diffstat (limited to 'sys/kern/sysv_shm.c')
-rw-r--r--sys/kern/sysv_shm.c330
1 files changed, 304 insertions, 26 deletions
diff --git a/sys/kern/sysv_shm.c b/sys/kern/sysv_shm.c
index 613a462..cced3e3 100644
--- a/sys/kern/sysv_shm.c
+++ b/sys/kern/sysv_shm.c
@@ -112,7 +112,8 @@ static int shmget_existing(struct thread *td, struct shmget_args *uap,
static int shm_last_free, shm_nused, shmalloced;
vm_size_t shm_committed;
-static struct shmid_kernel *shmsegs;
+static struct shmid_kernel *shmsegs;
+static unsigned shm_prison_slot;
struct shmmap_state {
vm_offset_t va;
@@ -120,8 +121,8 @@ struct shmmap_state {
};
static void shm_deallocate_segment(struct shmid_kernel *);
-static int shm_find_segment_by_key(key_t);
-static struct shmid_kernel *shm_find_segment(int, bool);
+static int shm_find_segment_by_key(struct prison *, key_t);
+static struct shmid_kernel *shm_find_segment(struct prison *, int, bool);
static int shm_delete_mapping(struct vmspace *vm, struct shmmap_state *);
static void shmrealloc(void);
static int shminit(void);
@@ -130,6 +131,14 @@ static int shmunload(void);
static void shmexit_myhook(struct vmspace *vm);
static void shmfork_myhook(struct proc *p1, struct proc *p2);
static int sysctl_shmsegs(SYSCTL_HANDLER_ARGS);
+static void shm_remove(struct shmid_kernel *, int);
+static struct prison *shm_find_prison(struct ucred *);
+static int shm_prison_cansee(struct prison *, struct shmid_kernel *);
+static int shm_prison_check(void *, void *);
+static int shm_prison_set(void *, void *);
+static int shm_prison_get(void *, void *);
+static int shm_prison_remove(void *, void *);
+static void shm_prison_cleanup(struct prison *);
/*
* Tuneable values.
@@ -189,12 +198,14 @@ static struct sx sysvshmsx;
#define SYSVSHM_ASSERT_LOCKED() sx_assert(&sysvshmsx, SA_XLOCKED)
static int
-shm_find_segment_by_key(key_t key)
+shm_find_segment_by_key(struct prison *pr, key_t key)
{
int i;
for (i = 0; i < shmalloced; i++)
if ((shmsegs[i].u.shm_perm.mode & SHMSEG_ALLOCATED) &&
+ shmsegs[i].cred != NULL &&
+ shmsegs[i].cred->cr_prison == pr &&
shmsegs[i].u.shm_perm.key == key)
return (i);
return (-1);
@@ -205,7 +216,7 @@ shm_find_segment_by_key(key_t key)
* is_shmid is false.
*/
static struct shmid_kernel *
-shm_find_segment(int arg, bool is_shmid)
+shm_find_segment(struct prison *rpr, int arg, bool is_shmid)
{
struct shmid_kernel *shmseg;
int segnum;
@@ -217,7 +228,8 @@ shm_find_segment(int arg, bool is_shmid)
if ((shmseg->u.shm_perm.mode & SHMSEG_ALLOCATED) == 0 ||
(!shm_allow_removed &&
(shmseg->u.shm_perm.mode & SHMSEG_REMOVED) != 0) ||
- (is_shmid && shmseg->u.shm_perm.seq != IPCID_TO_SEQ(arg)))
+ (is_shmid && shmseg->u.shm_perm.seq != IPCID_TO_SEQ(arg)) ||
+ shm_prison_cansee(rpr, shmseg) != 0)
return (NULL);
return (shmseg);
}
@@ -271,6 +283,41 @@ shm_delete_mapping(struct vmspace *vm, struct shmmap_state *shmmap_s)
return (0);
}
+static void
+shm_remove(struct shmid_kernel *shmseg, int segnum)
+{
+
+ shmseg->u.shm_perm.key = IPC_PRIVATE;
+ shmseg->u.shm_perm.mode |= SHMSEG_REMOVED;
+ if (shmseg->u.shm_nattch <= 0) {
+ shm_deallocate_segment(shmseg);
+ shm_last_free = segnum;
+ }
+}
+
+static struct prison *
+shm_find_prison(struct ucred *cred)
+{
+ struct prison *pr, *rpr;
+
+ pr = cred->cr_prison;
+ prison_lock(pr);
+ rpr = osd_jail_get(pr, shm_prison_slot);
+ prison_unlock(pr);
+ return rpr;
+}
+
+static int
+shm_prison_cansee(struct prison *rpr, struct shmid_kernel *shmseg)
+{
+
+ if (shmseg->cred == NULL ||
+ !(rpr == shmseg->cred->cr_prison ||
+ prison_ischild(rpr, shmseg->cred->cr_prison)))
+ return (EINVAL);
+ return (0);
+}
+
static int
kern_shmdt_locked(struct thread *td, const void *shmaddr)
{
@@ -282,7 +329,7 @@ kern_shmdt_locked(struct thread *td, const void *shmaddr)
int error, i;
SYSVSHM_ASSERT_LOCKED();
- if (!prison_allow(td->td_ucred, PR_ALLOW_SYSVIPC))
+ if (shm_find_prison(td->td_ucred) == NULL)
return (ENOSYS);
shmmap_s = p->p_vmspace->vm_shm;
if (shmmap_s == NULL)
@@ -325,6 +372,7 @@ static int
kern_shmat_locked(struct thread *td, int shmid, const void *shmaddr,
int shmflg)
{
+ struct prison *rpr;
struct proc *p = td->td_proc;
struct shmid_kernel *shmseg;
struct shmmap_state *shmmap_s;
@@ -334,7 +382,8 @@ kern_shmat_locked(struct thread *td, int shmid, const void *shmaddr,
int error, i, rv;
SYSVSHM_ASSERT_LOCKED();
- if (!prison_allow(td->td_ucred, PR_ALLOW_SYSVIPC))
+ rpr = shm_find_prison(td->td_ucred);
+ if (rpr == NULL)
return (ENOSYS);
shmmap_s = p->p_vmspace->vm_shm;
if (shmmap_s == NULL) {
@@ -345,7 +394,7 @@ kern_shmat_locked(struct thread *td, int shmid, const void *shmaddr,
KASSERT(p->p_vmspace->vm_shm == NULL, ("raced"));
p->p_vmspace->vm_shm = shmmap_s;
}
- shmseg = shm_find_segment(shmid, true);
+ shmseg = shm_find_segment(rpr, shmid, true);
if (shmseg == NULL)
return (EINVAL);
error = ipcperm(td, &shmseg->u.shm_perm,
@@ -433,6 +482,7 @@ static int
kern_shmctl_locked(struct thread *td, int shmid, int cmd, void *buf,
size_t *bufsz)
{
+ struct prison *rpr;
struct shmid_kernel *shmseg;
struct shmid_ds *shmidp;
struct shm_info shm_info;
@@ -440,7 +490,8 @@ kern_shmctl_locked(struct thread *td, int shmid, int cmd, void *buf,
SYSVSHM_ASSERT_LOCKED();
- if (!prison_allow(td->td_ucred, PR_ALLOW_SYSVIPC))
+ rpr = shm_find_prison(td->td_ucred);
+ if (rpr == NULL)
return (ENOSYS);
error = 0;
@@ -474,7 +525,7 @@ kern_shmctl_locked(struct thread *td, int shmid, int cmd, void *buf,
return (0);
}
}
- shmseg = shm_find_segment(shmid, cmd != SHM_STAT);
+ shmseg = shm_find_segment(rpr, shmid, cmd != SHM_STAT);
if (shmseg == NULL)
return (EINVAL);
#ifdef MAC
@@ -485,10 +536,13 @@ kern_shmctl_locked(struct thread *td, int shmid, int cmd, void *buf,
switch (cmd) {
case SHM_STAT:
case IPC_STAT:
+ shmidp = (struct shmid_ds *)buf;
error = ipcperm(td, &shmseg->u.shm_perm, IPC_R);
if (error != 0)
return (error);
- memcpy(buf, &shmseg->u, sizeof(struct shmid_ds));
+ memcpy(shmidp, &shmseg->u, sizeof(struct shmid_ds));
+ if (td->td_ucred->cr_prison != shmseg->cred->cr_prison)
+ shmidp->shm_perm.key = IPC_PRIVATE;
if (bufsz != NULL)
*bufsz = sizeof(struct shmid_ds);
if (cmd == SHM_STAT) {
@@ -512,12 +566,7 @@ kern_shmctl_locked(struct thread *td, int shmid, int cmd, void *buf,
error = ipcperm(td, &shmseg->u.shm_perm, IPC_M);
if (error != 0)
return (error);
- shmseg->u.shm_perm.key = IPC_PRIVATE;
- shmseg->u.shm_perm.mode |= SHMSEG_REMOVED;
- if (shmseg->u.shm_nattch <= 0) {
- shm_deallocate_segment(shmseg);
- shm_last_free = IPCID_TO_IX(shmid);
- }
+ shm_remove(shmseg, IPCID_TO_IX(shmid));
break;
#if 0
case SHM_LOCK:
@@ -724,14 +773,15 @@ sys_shmget(struct thread *td, struct shmget_args *uap)
int segnum, mode;
int error;
- if (!prison_allow(td->td_ucred, PR_ALLOW_SYSVIPC))
+ if (shm_find_prison(td->td_ucred) == NULL)
return (ENOSYS);
mode = uap->shmflg & ACCESSPERMS;
SYSVSHM_LOCK();
if (uap->key == IPC_PRIVATE) {
error = shmget_allocate_segment(td, uap, mode);
} else {
- segnum = shm_find_segment_by_key(uap->key);
+ segnum = shm_find_segment_by_key(td->td_ucred->cr_prison,
+ uap->key);
if (segnum >= 0)
error = shmget_existing(td, uap, mode, segnum);
else if ((uap->shmflg & IPC_CREAT) == 0)
@@ -852,7 +902,15 @@ static struct syscall_helper_data shm32_syscalls[] = {
static int
shminit(void)
{
+ struct prison *pr;
+ void *rsv;
int i, error;
+ osd_method_t methods[PR_MAXMETHOD] = {
+ [PR_METHOD_CHECK] = shm_prison_check,
+ [PR_METHOD_SET] = shm_prison_set,
+ [PR_METHOD_GET] = shm_prison_get,
+ [PR_METHOD_REMOVE] = shm_prison_remove,
+ };
#ifndef BURN_BRIDGES
if (TUNABLE_ULONG_FETCH("kern.ipc.shmmaxpgs", &shminfo.shmall) != 0)
@@ -888,6 +946,29 @@ shminit(void)
shmexit_hook = &shmexit_myhook;
shmfork_hook = &shmfork_myhook;
+ /* Set current prisons according to their allow.sysvipc. */
+ shm_prison_slot = osd_jail_register(NULL, methods);
+ rsv = osd_reserve(shm_prison_slot);
+ prison_lock(&prison0);
+ (void)osd_jail_set_reserved(&prison0, shm_prison_slot, rsv, &prison0);
+ prison_unlock(&prison0);
+ rsv = NULL;
+ sx_slock(&allprison_lock);
+ TAILQ_FOREACH(pr, &allprison, pr_list) {
+ if (rsv == NULL)
+ rsv = osd_reserve(shm_prison_slot);
+ prison_lock(pr);
+ if ((pr->pr_allow & PR_ALLOW_SYSVIPC) && pr->pr_ref > 0) {
+ (void)osd_jail_set_reserved(pr, shm_prison_slot, rsv,
+ &prison0);
+ rsv = NULL;
+ }
+ prison_unlock(pr);
+ }
+ if (rsv != NULL)
+ osd_free_reserved(rsv);
+ sx_sunlock(&allprison_lock);
+
error = syscall_helper_register(shm_syscalls);
if (error != 0)
return (error);
@@ -911,6 +992,8 @@ shmunload(void)
syscall32_helper_unregister(shm32_syscalls);
#endif
syscall_helper_unregister(shm_syscalls);
+ if (shm_prison_slot != 0)
+ osd_jail_deregister(shm_prison_slot);
for (i = 0; i < shmalloced; i++) {
#ifdef MAC
@@ -934,14 +1017,209 @@ shmunload(void)
static int
sysctl_shmsegs(SYSCTL_HANDLER_ARGS)
{
- int error;
+ struct shmid_kernel tshmseg;
+ struct prison *pr, *rpr;
+ int error, i;
SYSVSHM_LOCK();
- error = SYSCTL_OUT(req, shmsegs, shmalloced * sizeof(shmsegs[0]));
+ pr = req->td->td_ucred->cr_prison;
+ rpr = shm_find_prison(req->td->td_ucred);
+ error = 0;
+ for (i = 0; i < shmalloced; i++) {
+ if ((shmsegs[i].u.shm_perm.mode & SHMSEG_ALLOCATED) == 0 ||
+ rpr == NULL || shm_prison_cansee(rpr, &shmsegs[i]) != 0) {
+ bzero(&tshmseg, sizeof(tshmseg));
+ tshmseg.u.shm_perm.mode = SHMSEG_FREE;
+ } else {
+ tshmseg = shmsegs[i];
+ if (tshmseg.cred->cr_prison != pr)
+ tshmseg.u.shm_perm.key = IPC_PRIVATE;
+ }
+ error = SYSCTL_OUT(req, &tshmseg, sizeof(tshmseg));
+ if (error != 0)
+ break;
+ }
SYSVSHM_UNLOCK();
return (error);
}
+static int
+shm_prison_check(void *obj, void *data)
+{
+ struct prison *pr = obj;
+ struct prison *prpr;
+ struct vfsoptlist *opts = data;
+ int error, jsys;
+
+ /*
+ * sysvshm is a jailsys integer.
+ * It must be "disable" if the parent jail is disabled.
+ */
+ error = vfs_copyopt(opts, "sysvshm", &jsys, sizeof(jsys));
+ if (error != ENOENT) {
+ if (error != 0)
+ return (error);
+ switch (jsys) {
+ case JAIL_SYS_DISABLE:
+ break;
+ case JAIL_SYS_NEW:
+ case JAIL_SYS_INHERIT:
+ prison_lock(pr->pr_parent);
+ prpr = osd_jail_get(pr->pr_parent, shm_prison_slot);
+ prison_unlock(pr->pr_parent);
+ if (prpr == NULL)
+ return (EPERM);
+ break;
+ default:
+ return (EINVAL);
+ }
+ }
+
+ return (0);
+}
+
+static int
+shm_prison_set(void *obj, void *data)
+{
+ struct prison *pr = obj;
+ struct prison *tpr, *orpr, *nrpr, *trpr;
+ struct vfsoptlist *opts = data;
+ void *rsv;
+ int jsys, descend;
+
+ /*
+ * sysvshm controls which jail is the root of the associated segments
+ * (this jail or same as the parent), or if the feature is available
+ * at all.
+ */
+ if (vfs_copyopt(opts, "sysvshm", &jsys, sizeof(jsys)) == ENOENT)
+ jsys = vfs_flagopt(opts, "allow.sysvipc", NULL, 0)
+ ? JAIL_SYS_INHERIT
+ : vfs_flagopt(opts, "allow.nosysvipc", NULL, 0)
+ ? JAIL_SYS_DISABLE
+ : -1;
+ if (jsys == JAIL_SYS_DISABLE) {
+ prison_lock(pr);
+ orpr = osd_jail_get(pr, shm_prison_slot);
+ if (orpr != NULL)
+ osd_jail_del(pr, shm_prison_slot);
+ prison_unlock(pr);
+ if (orpr != NULL) {
+ if (orpr == pr)
+ shm_prison_cleanup(pr);
+ /* Disable all child jails as well. */
+ FOREACH_PRISON_DESCENDANT(pr, tpr, descend) {
+ prison_lock(tpr);
+ trpr = osd_jail_get(tpr, shm_prison_slot);
+ if (trpr != NULL) {
+ osd_jail_del(tpr, shm_prison_slot);
+ prison_unlock(tpr);
+ if (trpr == tpr)
+ shm_prison_cleanup(tpr);
+ } else {
+ prison_unlock(tpr);
+ descend = 0;
+ }
+ }
+ }
+ } else if (jsys != -1) {
+ if (jsys == JAIL_SYS_NEW)
+ nrpr = pr;
+ else {
+ prison_lock(pr->pr_parent);
+ nrpr = osd_jail_get(pr->pr_parent, shm_prison_slot);
+ prison_unlock(pr->pr_parent);
+ }
+ rsv = osd_reserve(shm_prison_slot);
+ prison_lock(pr);
+ orpr = osd_jail_get(pr, shm_prison_slot);
+ if (orpr != nrpr)
+ (void)osd_jail_set_reserved(pr, shm_prison_slot, rsv,
+ nrpr);
+ else
+ osd_free_reserved(rsv);
+ prison_unlock(pr);
+ if (orpr != nrpr) {
+ if (orpr == pr)
+ shm_prison_cleanup(pr);
+ if (orpr != NULL) {
+ /* Change child jails matching the old root, */
+ FOREACH_PRISON_DESCENDANT(pr, tpr, descend) {
+ prison_lock(tpr);
+ trpr = osd_jail_get(tpr,
+ shm_prison_slot);
+ if (trpr == orpr) {
+ (void)osd_jail_set(tpr,
+ shm_prison_slot, nrpr);
+ prison_unlock(tpr);
+ if (trpr == tpr)
+ shm_prison_cleanup(tpr);
+ } else {
+ prison_unlock(tpr);
+ descend = 0;
+ }
+ }
+ }
+ }
+ }
+
+ return (0);
+}
+
+static int
+shm_prison_get(void *obj, void *data)
+{
+ struct prison *pr = obj;
+ struct prison *rpr;
+ struct vfsoptlist *opts = data;
+ int error, jsys;
+
+ /* Set sysvshm based on the jail's root prison. */
+ prison_lock(pr);
+ rpr = osd_jail_get(pr, shm_prison_slot);
+ prison_unlock(pr);
+ jsys = rpr == NULL ? JAIL_SYS_DISABLE
+ : rpr == pr ? JAIL_SYS_NEW : JAIL_SYS_INHERIT;
+ error = vfs_setopt(opts, "sysvshm", &jsys, sizeof(jsys));
+ if (error == ENOENT)
+ error = 0;
+ return (error);
+}
+
+static int
+shm_prison_remove(void *obj, void *data __unused)
+{
+ struct prison *pr = obj;
+ struct prison *rpr;
+
+ SYSVSHM_LOCK();
+ prison_lock(pr);
+ rpr = osd_jail_get(pr, shm_prison_slot);
+ prison_unlock(pr);
+ if (rpr == pr)
+ shm_prison_cleanup(pr);
+ SYSVSHM_UNLOCK();
+ return (0);
+}
+
+static void
+shm_prison_cleanup(struct prison *pr)
+{
+ struct shmid_kernel *shmseg;
+ int i;
+
+ /* Remove any segments that belong to this jail. */
+ for (i = 0; i < shmalloced; i++) {
+ shmseg = &shmsegs[i];
+ if ((shmseg->u.shm_perm.mode & SHMSEG_ALLOCATED) &&
+ shmseg->cred != NULL && shmseg->cred->cr_prison == pr) {
+ shm_remove(shmseg, i);
+ }
+ }
+}
+
+SYSCTL_JAIL_PARAM_SYS_NODE(sysvshm, CTLFLAG_RW, "SYSV shared memory");
+
#if defined(__i386__) && (defined(COMPAT_FREEBSD4) || defined(COMPAT_43))
struct oshmid_ds {
struct ipc_perm_old shm_perm; /* operation perms */
@@ -966,17 +1244,19 @@ oshmctl(struct thread *td, struct oshmctl_args *uap)
{
#ifdef COMPAT_43
int error = 0;
+ struct prison *rpr;
struct shmid_kernel *shmseg;
struct oshmid_ds outbuf;
- if (!prison_allow(td->td_ucred, PR_ALLOW_SYSVIPC))
+ rpr = shm_find_prison(td->td_ucred);
+ if (rpr == NULL)
return (ENOSYS);
if (uap->cmd != IPC_STAT) {
return (freebsd7_shmctl(td,
(struct freebsd7_shmctl_args *)uap));
}
SYSVSHM_LOCK();
- shmseg = shm_find_segment(uap->shmid, true);
+ shmseg = shm_find_segment(rpr, uap->shmid, true);
if (shmseg == NULL) {
SYSVSHM_UNLOCK();
return (EINVAL);
@@ -1031,8 +1311,6 @@ sys_shmsys(struct thread *td, struct shmsys_args *uap)
{
int error;
- if (!prison_allow(td->td_ucred, PR_ALLOW_SYSVIPC))
- return (ENOSYS);
if (uap->which < 0 || uap->which >= nitems(shmcalls))
return (EINVAL);
error = (*shmcalls[uap->which])(td, &uap->a2);
OpenPOWER on IntegriCloud