summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/os/klpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/os/klpd.c')
-rw-r--r--usr/src/uts/common/os/klpd.c722
1 files changed, 722 insertions, 0 deletions
diff --git a/usr/src/uts/common/os/klpd.c b/usr/src/uts/common/os/klpd.c
new file mode 100644
index 0000000000..dc22fc5c41
--- /dev/null
+++ b/usr/src/uts/common/os/klpd.c
@@ -0,0 +1,722 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/atomic.h>
+#include <sys/door.h>
+#include <sys/proc.h>
+#include <sys/cred_impl.h>
+#include <sys/policy.h>
+#include <sys/priv.h>
+#include <sys/klpd.h>
+#include <sys/errno.h>
+#include <sys/kmem.h>
+#include <sys/project.h>
+#include <sys/systm.h>
+#include <sys/sysmacros.h>
+#include <sys/pathname.h>
+#include <sys/varargs.h>
+#include <sys/zone.h>
+#include <netinet/in.h>
+
+#define ROUNDUP(a, n) (((a) + ((n) - 1)) & ~((n) - 1))
+
+static kmutex_t klpd_mutex;
+
+typedef struct klpd_reg {
+ struct klpd_reg *klpd_next;
+ struct klpd_reg **klpd_refp;
+ door_handle_t klpd_door;
+ pid_t klpd_door_pid;
+ priv_set_t klpd_pset;
+ cred_t *klpd_cred;
+ int klpd_indel; /* Disabled */
+ uint32_t klpd_ref;
+} klpd_reg_t;
+
+
+/*
+ * This data structure hangs off the credential of a process; the
+ * credential is finalized and cannot be changed; but this structure
+ * can be changed when a new door server for the particular group
+ * needs to be registered. It is refcounted and shared between
+ * processes with common ancestry.
+ *
+ * The reference count is atomically updated.
+ *
+ * But the registration probably needs to be updated under a lock.
+ */
+typedef struct credklpd {
+ kmutex_t crkl_lock;
+ klpd_reg_t *crkl_reg;
+ uint32_t crkl_ref;
+} credklpd_t;
+
+klpd_reg_t *klpd_list;
+
+static void klpd_unlink(klpd_reg_t *);
+static int klpd_unreg_dh(door_handle_t);
+
+static credklpd_t *crklpd_alloc(void);
+
+void crklpd_setreg(credklpd_t *, klpd_reg_t *);
+
+extern size_t max_vnode_path;
+
+void
+klpd_rele(klpd_reg_t *p)
+{
+ if (atomic_add_32_nv(&p->klpd_ref, -1) == 0) {
+ if (p->klpd_refp != NULL)
+ klpd_unlink(p);
+ if (p->klpd_cred != NULL)
+ crfree(p->klpd_cred);
+ door_ki_rele(p->klpd_door);
+ kmem_free(p, sizeof (*p));
+ }
+}
+
+/*
+ * In order to be able to walk the lists, we can't unlink the entry
+ * until the reference count drops to 0. If we remove it too soon,
+ * list walkers will terminate when they happen to call a now orphaned
+ * entry.
+ */
+static klpd_reg_t *
+klpd_rele_next(klpd_reg_t *p)
+{
+ klpd_reg_t *r = p->klpd_next;
+
+ klpd_rele(p);
+ return (r);
+}
+
+
+static void
+klpd_hold(klpd_reg_t *p)
+{
+ atomic_add_32(&p->klpd_ref, 1);
+}
+
+/*
+ * Remove registration from where it is registered. Returns next in list.
+ */
+static void
+klpd_unlink(klpd_reg_t *p)
+{
+ ASSERT(p->klpd_refp == NULL || *p->klpd_refp == p);
+
+ if (p->klpd_refp != NULL)
+ *p->klpd_refp = p->klpd_next;
+
+ if (p->klpd_next != NULL)
+ p->klpd_next->klpd_refp = p->klpd_refp;
+ p->klpd_refp = NULL;
+}
+
+/*
+ * Remove the head of the klpd list and decrement its refcnt.
+ * The lock guarding the list should be held; this function is
+ * called when we are sure we want to remove the entry from the
+ * list but not so sure that the reference count has dropped back to
+ * 1 and is specifically intended to remove the non-list variants.
+ */
+void
+klpd_remove(klpd_reg_t **pp)
+{
+ klpd_reg_t *p = *pp;
+ if (p == NULL)
+ return;
+ ASSERT(p->klpd_next == NULL);
+ klpd_unlink(p);
+ klpd_rele(p);
+}
+
+/*
+ * Link new entry in list. The Boolean argument specifies whether this
+ * list can contain only a single item or multiple items.
+ * Returns the entry which needs to be released if single is B_TRUE.
+ */
+static klpd_reg_t *
+klpd_link(klpd_reg_t *p, klpd_reg_t **listp, boolean_t single)
+{
+ klpd_reg_t *old = *listp;
+
+ ASSERT(p->klpd_ref == 1);
+
+ ASSERT(old == NULL || *old->klpd_refp == old);
+ p->klpd_refp = listp;
+ p->klpd_next = single ? NULL : old;
+ *listp = p;
+ if (old != NULL) {
+ if (single) {
+ ASSERT(old->klpd_next == NULL);
+ old->klpd_refp = NULL;
+ return (old);
+ } else
+ old->klpd_refp = &p->klpd_next;
+ }
+ return (NULL);
+}
+
+/*
+ * The typical call consists of:
+ * - priv_set_t
+ * - some integer data (type, value)
+ * for now, it's just one bit.
+ */
+static klpd_head_t *
+klpd_marshall(klpd_reg_t *p, const priv_set_t *rq, va_list ap)
+{
+ char *comp;
+ uint_t type;
+ vnode_t *vp;
+ size_t len = sizeof (priv_set_t) + sizeof (klpd_head_t);
+ size_t plen, clen;
+ int proto;
+
+ klpd_arg_t *kap = NULL;
+ klpd_head_t *khp;
+
+ type = va_arg(ap, uint_t);
+ switch (type) {
+ case KLPDARG_NOMORE:
+ khp = kmem_zalloc(len, KM_SLEEP);
+ khp->klh_argoff = 0;
+ break;
+ case KLPDARG_VNODE:
+ len += offsetof(klpd_arg_t, kla_str);
+ vp = va_arg(ap, vnode_t *);
+ if (vp == NULL)
+ return (NULL);
+
+ comp = va_arg(ap, char *);
+
+ if (comp != NULL && *comp != '\0')
+ clen = strlen(comp) + 1;
+ else
+ clen = 0;
+
+ len += ROUNDUP(MAXPATHLEN, sizeof (uint_t));
+ khp = kmem_zalloc(len, KM_SLEEP);
+
+ khp->klh_argoff = sizeof (klpd_head_t) + sizeof (priv_set_t);
+ kap = KLH_ARG(khp);
+
+ if (vnodetopath(crgetzone(p->klpd_cred)->zone_rootvp,
+ vp, kap->kla_str, MAXPATHLEN, p->klpd_cred) != 0) {
+ kmem_free(khp, len);
+ return (NULL);
+ }
+ if (clen != 0) {
+ plen = strlen(kap->kla_str);
+ if (plen + clen + 1 >= MAXPATHLEN) {
+ kmem_free(khp, len);
+ return (NULL);
+ }
+ /* Don't make root into a double "/" */
+ if (plen <= 2)
+ plen = 0;
+ kap->kla_str[plen] = '/';
+ bcopy(comp, &kap->kla_str[plen + 1], clen);
+ }
+ break;
+ case KLPDARG_PORT:
+ proto = va_arg(ap, int);
+ switch (proto) {
+ case IPPROTO_TCP: type = KLPDARG_TCPPORT;
+ break;
+ case IPPROTO_UDP: type = KLPDARG_UDPPORT;
+ break;
+ case IPPROTO_SCTP: type = KLPDARG_SCTPPORT;
+ break;
+ case PROTO_SDP: type = KLPDARG_SDPPORT;
+ break;
+ }
+ /* FALLTHROUGH */
+ case KLPDARG_INT:
+ case KLPDARG_TCPPORT:
+ case KLPDARG_UDPPORT:
+ case KLPDARG_SCTPPORT:
+ case KLPDARG_SDPPORT:
+ len += sizeof (*kap);
+ khp = kmem_zalloc(len, KM_SLEEP);
+ khp->klh_argoff = sizeof (klpd_head_t) + sizeof (priv_set_t);
+ kap = KLH_ARG(khp);
+ kap->kla_int = va_arg(ap, int);
+ break;
+ default:
+ return (NULL);
+ }
+ khp->klh_vers = KLPDCALL_VERS;
+ khp->klh_len = len;
+ khp->klh_privoff = sizeof (*khp);
+ *KLH_PRIVSET(khp) = *rq;
+ if (kap != NULL) {
+ kap->kla_type = type;
+ kap->kla_dlen = len - khp->klh_argoff;
+ }
+ return (khp);
+}
+
+static int
+klpd_do_call(klpd_reg_t *p, const priv_set_t *req, va_list ap)
+{
+ door_arg_t da;
+ int res;
+ int dres;
+ klpd_head_t *klh;
+
+ if (p->klpd_door_pid == curproc->p_pid)
+ return (-1);
+
+ klh = klpd_marshall(p, req, ap);
+
+ if (klh == NULL)
+ return (-1);
+
+ da.data_ptr = (char *)klh;
+ da.data_size = klh->klh_len;
+ da.desc_ptr = NULL;
+ da.desc_num = 0;
+ da.rbuf = (char *)&res;
+ da.rsize = sizeof (res);
+
+ while ((dres = door_ki_upcall(p->klpd_door, &da)) != 0) {
+ switch (dres) {
+ case EAGAIN:
+ delay(1);
+ continue;
+ case EINVAL:
+ case EBADF:
+ /* Bad door, don't call it again. */
+ (void) klpd_unreg_dh(p->klpd_door);
+ /* FALLTHROUGH */
+ case EINTR:
+ /* Pending signal, nothing we can do. */
+ /* FALLTHROUGH */
+ default:
+ kmem_free(klh, klh->klh_len);
+ return (-1);
+ }
+ }
+ kmem_free(klh, klh->klh_len);
+ /* Bogus return value, must be a failure */
+ if (da.rbuf != (char *)&res) {
+ kmem_free(da.rbuf, da.rsize);
+ return (-1);
+ }
+ return (res);
+}
+
+uint32_t klpd_bad_locks;
+
+int
+klpd_call(const cred_t *cr, const priv_set_t *req, va_list ap)
+{
+ klpd_reg_t *p;
+ int rv = -1;
+ credklpd_t *ckp;
+ zone_t *ckzone;
+
+ /*
+ * These locks must not be held when this code is called;
+ * callbacks to userland with these locks held will result
+ * in issues. That said, the code at the call sides was
+ * restructured not to call with any of the locks held and
+ * no policies operate by default on most processes.
+ */
+ if (mutex_owned(&pidlock) || mutex_owned(&curproc->p_lock) ||
+ mutex_owned(&curproc->p_crlock)) {
+ atomic_add_32(&klpd_bad_locks, 1);
+ return (-1);
+ }
+
+ /*
+ * Enforce the limit set for the call process (still).
+ */
+ if (!priv_issubset(req, &CR_LPRIV(cr)))
+ return (-1);
+
+ /* Try 1: get the credential specific klpd */
+ if ((ckp = crgetcrklpd(cr)) != NULL) {
+ mutex_enter(&ckp->crkl_lock);
+ if ((p = ckp->crkl_reg) != NULL &&
+ p->klpd_indel == 0 &&
+ priv_issubset(req, &p->klpd_pset)) {
+ klpd_hold(p);
+ mutex_exit(&ckp->crkl_lock);
+ rv = klpd_do_call(p, req, ap);
+ mutex_enter(&ckp->crkl_lock);
+ klpd_rele(p);
+ mutex_exit(&ckp->crkl_lock);
+ if (rv != -1)
+ return (rv == 0 ? 0 : -1);
+ } else {
+ mutex_exit(&ckp->crkl_lock);
+ }
+ }
+
+ /* Try 2: get the project specific klpd */
+ mutex_enter(&klpd_mutex);
+
+ if ((p = curproj->kpj_klpd) != NULL) {
+ klpd_hold(p);
+ mutex_exit(&klpd_mutex);
+ if (p->klpd_indel == 0 &&
+ priv_issubset(req, &p->klpd_pset)) {
+ rv = klpd_do_call(p, req, ap);
+ }
+ mutex_enter(&klpd_mutex);
+ klpd_rele(p);
+ mutex_exit(&klpd_mutex);
+
+ if (rv != -1)
+ return (rv == 0 ? 0 : -1);
+ } else {
+ mutex_exit(&klpd_mutex);
+ }
+
+ /* Try 3: get the global klpd list */
+ ckzone = crgetzone(cr);
+ mutex_enter(&klpd_mutex);
+
+ for (p = klpd_list; p != NULL; ) {
+ zone_t *kkzone = crgetzone(p->klpd_cred);
+ if ((kkzone == &zone0 || kkzone == ckzone) &&
+ p->klpd_indel == 0 &&
+ priv_issubset(req, &p->klpd_pset)) {
+ klpd_hold(p);
+ mutex_exit(&klpd_mutex);
+ rv = klpd_do_call(p, req, ap);
+ mutex_enter(&klpd_mutex);
+
+ p = klpd_rele_next(p);
+
+ if (rv != -1)
+ break;
+ } else {
+ p = p->klpd_next;
+ }
+ }
+ mutex_exit(&klpd_mutex);
+ return (rv == 0 ? 0 : -1);
+}
+
+/*
+ * Register the klpd.
+ * If the pid_t passed in is positive, update the registration for
+ * the specific process; that is only possible if the process already
+ * has a registration on it. This change of registration will affect
+ * all processes which share common ancestry.
+ *
+ * MY_PID (pid 0) can be used to create or change the context for
+ * the current process, typically done after fork().
+ *
+ * A negative value can be used to register a klpd globally.
+ *
+ * The per-credential klpd needs to be cleaned up when entering
+ * a zone or unsetting the flag.
+ */
+int
+klpd_reg(int did, idtype_t type, id_t id, priv_set_t *psetbuf)
+{
+ cred_t *cr = CRED();
+ door_handle_t dh;
+ klpd_reg_t *kpd;
+ priv_set_t pset;
+ door_info_t di;
+ credklpd_t *ckp = NULL;
+ pid_t pid = -1;
+ projid_t proj = -1;
+ kproject_t *kpp = NULL;
+
+ if (CR_FLAGS(cr) & PRIV_XPOLICY)
+ return (set_errno(EINVAL));
+
+ if (copyin(psetbuf, &pset, sizeof (priv_set_t)))
+ return (set_errno(EFAULT));
+
+ if (!priv_issubset(&pset, &CR_OEPRIV(cr)))
+ return (set_errno(EPERM));
+
+ switch (type) {
+ case P_PID:
+ pid = (pid_t)id;
+ if (pid == P_MYPID)
+ pid = curproc->p_pid;
+ if (pid == curproc->p_pid)
+ ckp = crklpd_alloc();
+ break;
+ case P_PROJID:
+ proj = (projid_t)id;
+ kpp = project_hold_by_id(proj, crgetzone(cr),
+ PROJECT_HOLD_FIND);
+ if (kpp == NULL)
+ return (set_errno(ESRCH));
+ break;
+ default:
+ return (set_errno(ENOTSUP));
+ }
+
+
+ /*
+ * Verify the door passed in; it must be a door and we won't
+ * allow processes to be called on their own behalf.
+ */
+ dh = door_ki_lookup(did);
+ if (dh == NULL || door_ki_info(dh, &di) != 0) {
+ if (ckp != NULL)
+ crklpd_rele(ckp);
+ if (kpp != NULL)
+ project_rele(kpp);
+ return (set_errno(EBADF));
+ }
+ if (type == P_PID && pid == di.di_target) {
+ if (ckp != NULL)
+ crklpd_rele(ckp);
+ ASSERT(kpp == NULL);
+ return (set_errno(EINVAL));
+ }
+
+ kpd = kmem_zalloc(sizeof (*kpd), KM_SLEEP);
+ crhold(kpd->klpd_cred = cr);
+ kpd->klpd_door = dh;
+ kpd->klpd_door_pid = di.di_target;
+ kpd->klpd_ref = 1;
+ kpd->klpd_pset = pset;
+
+ if (kpp != NULL) {
+ mutex_enter(&klpd_mutex);
+ kpd = klpd_link(kpd, &kpp->kpj_klpd, B_TRUE);
+ mutex_exit(&klpd_mutex);
+ if (kpd != NULL)
+ klpd_rele(kpd);
+ project_rele(kpp);
+ } else if ((int)pid < 0) {
+ /* Global daemon */
+ mutex_enter(&klpd_mutex);
+ (void) klpd_link(kpd, &klpd_list, B_FALSE);
+ mutex_exit(&klpd_mutex);
+ } else if (pid == curproc->p_pid) {
+ proc_t *p = curproc;
+ cred_t *newcr = cralloc();
+
+ /* No need to lock, sole reference to ckp */
+ kpd = klpd_link(kpd, &ckp->crkl_reg, B_TRUE);
+
+ if (kpd != NULL)
+ klpd_rele(kpd);
+
+ mutex_enter(&p->p_crlock);
+ cr = p->p_cred;
+ crdup_to(cr, newcr);
+ crsetcrklpd(newcr, ckp);
+ p->p_cred = newcr; /* Already held for p_cred */
+
+ crhold(newcr); /* Hold once for the current thread */
+ mutex_exit(&p->p_crlock);
+ crfree(cr); /* One for the p_cred */
+ crset(p, newcr);
+ } else {
+ proc_t *p;
+ cred_t *pcr;
+ mutex_enter(&pidlock);
+ p = prfind(pid);
+ if (p == NULL || !prochasprocperm(p, curproc, CRED())) {
+ mutex_exit(&pidlock);
+ klpd_rele(kpd);
+ return (set_errno(p == NULL ? ESRCH : EPERM));
+ }
+ mutex_enter(&p->p_crlock);
+ crhold(pcr = p->p_cred);
+ mutex_exit(&pidlock);
+ mutex_exit(&p->p_crlock);
+ /*
+ * We're going to update the credential's ckp in place;
+ * this requires that it exists.
+ */
+ ckp = crgetcrklpd(pcr);
+ if (ckp == NULL) {
+ crfree(pcr);
+ klpd_rele(kpd);
+ return (set_errno(EINVAL));
+ }
+ crklpd_setreg(ckp, kpd);
+ crfree(pcr);
+ }
+
+ return (0);
+}
+
+static int
+klpd_unreg_dh(door_handle_t dh)
+{
+ klpd_reg_t *p;
+
+ mutex_enter(&klpd_mutex);
+ for (p = klpd_list; p != NULL; p = p->klpd_next) {
+ if (p->klpd_door == dh)
+ break;
+ }
+ if (p == NULL) {
+ mutex_exit(&klpd_mutex);
+ return (EINVAL);
+ }
+ if (p->klpd_indel != 0) {
+ mutex_exit(&klpd_mutex);
+ return (EAGAIN);
+ }
+ p->klpd_indel = 1;
+ klpd_rele(p);
+ mutex_exit(&klpd_mutex);
+ return (0);
+}
+
+int
+klpd_unreg(int did, idtype_t type, id_t id)
+{
+ door_handle_t dh;
+ int res = 0;
+ proc_t *p;
+ pid_t pid;
+ projid_t proj;
+ kproject_t *kpp = NULL;
+ credklpd_t *ckp;
+
+ switch (type) {
+ case P_PID:
+ pid = (pid_t)id;
+ break;
+ case P_PROJID:
+ proj = (projid_t)id;
+ kpp = project_hold_by_id(proj, crgetzone(CRED()),
+ PROJECT_HOLD_FIND);
+ if (kpp == NULL)
+ return (set_errno(ESRCH));
+ break;
+ default:
+ return (set_errno(ENOTSUP));
+ }
+
+ dh = door_ki_lookup(did);
+ if (dh == NULL) {
+ if (kpp != NULL)
+ project_rele(kpp);
+ return (set_errno(EINVAL));
+ }
+
+ if (kpp != NULL) {
+ mutex_enter(&klpd_mutex);
+ if (kpp->kpj_klpd == NULL)
+ res = ESRCH;
+ else
+ klpd_remove(&kpp->kpj_klpd);
+ mutex_exit(&klpd_mutex);
+ project_rele(kpp);
+ goto out;
+ } else if ((int)pid > 0) {
+ mutex_enter(&pidlock);
+ p = prfind(pid);
+ if (p == NULL) {
+ mutex_exit(&pidlock);
+ door_ki_rele(dh);
+ return (set_errno(ESRCH));
+ }
+ mutex_enter(&p->p_crlock);
+ mutex_exit(&pidlock);
+ } else if (pid == 0) {
+ p = curproc;
+ mutex_enter(&p->p_crlock);
+ } else {
+ res = klpd_unreg_dh(dh);
+ goto out;
+ }
+
+ ckp = crgetcrklpd(p->p_cred);
+ if (ckp != NULL) {
+ crklpd_setreg(ckp, NULL);
+ } else {
+ res = ESRCH;
+ }
+ mutex_exit(&p->p_crlock);
+
+out:
+ door_ki_rele(dh);
+
+ if (res != 0)
+ return (set_errno(res));
+ return (0);
+}
+
+void
+crklpd_hold(credklpd_t *crkpd)
+{
+ atomic_add_32(&crkpd->crkl_ref, 1);
+}
+
+void
+crklpd_rele(credklpd_t *crkpd)
+{
+ if (atomic_add_32_nv(&crkpd->crkl_ref, -1) == 0) {
+ if (crkpd->crkl_reg != NULL)
+ klpd_rele(crkpd->crkl_reg);
+ mutex_destroy(&crkpd->crkl_lock);
+ kmem_free(crkpd, sizeof (*crkpd));
+ }
+}
+
+static credklpd_t *
+crklpd_alloc(void)
+{
+ credklpd_t *res = kmem_alloc(sizeof (*res), KM_SLEEP);
+
+ mutex_init(&res->crkl_lock, NULL, MUTEX_DEFAULT, NULL);
+ res->crkl_ref = 1;
+ res->crkl_reg = NULL;
+
+ return (res);
+}
+
+void
+crklpd_setreg(credklpd_t *crk, klpd_reg_t *new)
+{
+ klpd_reg_t *old;
+
+ mutex_enter(&crk->crkl_lock);
+ if (new == NULL) {
+ old = crk->crkl_reg;
+ if (old != NULL)
+ klpd_unlink(old);
+ } else {
+ old = klpd_link(new, &crk->crkl_reg, B_TRUE);
+ }
+ mutex_exit(&crk->crkl_lock);
+
+ if (old != NULL)
+ klpd_rele(old);
+}