Example #1
0
/*
 * 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);
}
Example #2
0
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_inc_32(&klpd_bad_locks);
		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);
}
Example #3
0
/*
 * setppriv (priv_op_t, priv_ptype_t, priv_set_t)
 */
static int
setppriv(priv_op_t op, priv_ptype_t type, priv_set_t *in_pset)
{
	priv_set_t	pset, *target;
	cred_t		*cr, *pcr;
	proc_t		*p;
	boolean_t	donocd = B_FALSE;

	if (!PRIV_VALIDSET(type) || !PRIV_VALIDOP(op))
		return (set_errno(EINVAL));

	if (copyin(in_pset, &pset, sizeof (priv_set_t)))
		return (set_errno(EFAULT));

	p = ttoproc(curthread);
	cr = cralloc();
	mutex_enter(&p->p_crlock);

retry:
	pcr = p->p_cred;

	if (AU_AUDITING())
		audit_setppriv(op, type, &pset, pcr);

	/*
	 * Filter out unallowed request (bad op and bad type)
	 */
	switch (op) {
	case PRIV_ON:
	case PRIV_SET:
		/*
		 * Turning on privileges; the limit set cannot grow,
		 * other sets can but only as long as they remain subsets
		 * of P.  Only immediately after exec holds that P <= L.
		 */
		if (type == PRIV_LIMIT &&
		    !priv_issubset(&pset, &CR_LPRIV(pcr))) {
			mutex_exit(&p->p_crlock);
			crfree(cr);
			return (set_errno(EPERM));
		}
		if (!priv_issubset(&pset, &CR_OPPRIV(pcr)) &&
		    !priv_issubset(&pset, priv_getset(pcr, type))) {
			mutex_exit(&p->p_crlock);
			/* Policy override should not grow beyond L either */
			if (type != PRIV_INHERITABLE ||
			    !priv_issubset(&pset, &CR_LPRIV(pcr)) ||
			    secpolicy_require_privs(CRED(), &pset) != 0) {
				crfree(cr);
				return (set_errno(EPERM));
			}
			mutex_enter(&p->p_crlock);
			if (pcr != p->p_cred)
				goto retry;
			donocd = B_TRUE;
		}
		break;

	case PRIV_OFF:
		/* PRIV_OFF is always allowed */
		break;
	}

	/*
	 * OK! everything is cool.
	 * Do cred COW.
	 */
	crcopy_to(pcr, cr);

	/*
	 * If we change the effective, permitted or limit set, we attain
	 * "privilege awareness".
	 */
	if (type != PRIV_INHERITABLE)
		priv_set_PA(cr);

	target = &(CR_PRIVS(cr)->crprivs[type]);

	switch (op) {
	case PRIV_ON:
		priv_union(&pset, target);
		break;
	case PRIV_OFF:
		priv_inverse(&pset);
		priv_intersect(target, &pset);

		/*
		 * Fall-thru to set target and change other process
		 * privilege sets.
		 */
		/*FALLTHRU*/

	case PRIV_SET:
		*target = pset;

		/*
		 * Take privileges no longer permitted out
		 * of other effective sets as well.
		 * Limit set is enforced at exec() time.
		 */
		if (type == PRIV_PERMITTED)
			priv_intersect(&pset, &CR_EPRIV(cr));
		break;
	}

	/*
	 * When we give up privileges not in the inheritable set,
	 * set SNOCD if not already set; first we compute the
	 * privileges removed from P using Diff = (~P') & P
	 * and then we check whether the removed privileges are
	 * a subset of I.  If we retain uid 0, all privileges
	 * are required anyway so don't set SNOCD.
	 */
	if (type == PRIV_PERMITTED && (p->p_flag & SNOCD) == 0 &&
	    cr->cr_uid != 0 && cr->cr_ruid != 0 && cr->cr_suid != 0) {
		priv_set_t diff = CR_OPPRIV(cr);
		priv_inverse(&diff);
		priv_intersect(&CR_OPPRIV(pcr), &diff);
		donocd = !priv_issubset(&diff, &CR_IPRIV(cr));
	}

	p->p_cred = cr;
	mutex_exit(&p->p_crlock);

	if (donocd) {
		mutex_enter(&p->p_lock);
		p->p_flag |= SNOCD;
		mutex_exit(&p->p_lock);
	}

	/*
	 * The basic_test privilege should not be removed from E;
	 * if that has happened, then some programmer typically set the E/P to
	 * empty. That is not portable.
	 */
	if ((type == PRIV_EFFECTIVE || type == PRIV_PERMITTED) &&
	    priv_basic_test >= 0 && !PRIV_ISASSERT(target, priv_basic_test)) {
		proc_t *p = curproc;
		pid_t pid = p->p_pid;
		char *fn = PTOU(p)->u_comm;

		cmn_err(CE_WARN, "%s[%d]: setppriv: basic_test privilege "
		    "removed from E/P", fn, pid);
	}

	crset(p, cr);		/* broadcast to process threads */

	return (0);
}
Example #4
0
/* Specify what privileges an suid root binary needs.  */
int __init_suid_priv (int flags, ...)
{
  int res = 0;
  priv_set_t *permit = NULL, *inherit = NULL, *scratch = NULL;

  /* Check flags.  */
  if (flags != PU_LIMITPRIVS && flags != PU_CLEARLIMITSET)
    return -1;

  /* We can only initialize once.  */
  if (__suidset)
    return -1;

  /* Do nothing if we are running as root but not setuid root.  */
  uid_t uid = getuid ();
  uid_t euid = geteuid ();
  if (uid == 0 && euid == 0)
    return 0;

  /* Allocate a scratch set.  */
  scratch = priv_allocset ();
  if (!scratch)
    goto error;

  /* Get the basic set.  */
  const priv_data_t *pd = __priv_parse_data_cached ();
  if (!pd)
    goto error;
  priv_set_t *basic = pd->pd_basicprivs;

  /* Get the inherited set.  */
  inherit = priv_allocset ();
  if (!inherit)
    goto error;
  if (getppriv (PRIV_INHERITABLE, inherit) != 0)
    goto error;

  /* Get the permitted set.  */
  permit = priv_allocset ();
  if (!permit)
    goto error;
  if (getppriv (PRIV_PERMITTED, permit) != 0)
    goto error;

  /* Get passed privileges.  */
  __suidset = priv_allocset ();
  if (!__suidset)
    goto error;
  priv_emptyset (__suidset);
  va_list ap;
  va_start (ap, flags);
  const char *priv;
  while ((priv = va_arg (ap, const char *)))
    if (priv_addset (__suidset, priv) != 0)
      goto error;

  /* Make sure that the passed privileges are a subset of the current
     permitted privileges.  */
  if (priv_issubset (__suidset, permit) != _B_TRUE)
    goto error;

  /* Set the effective privileges to the inherited ones.  */
  if (setppriv (PRIV_SET, PRIV_EFFECTIVE, inherit) != 0)
    goto error;

  /* Set the permitted privileges to those currently permitted privileges in
     set of the ones passed in, the inherited ones, and the basic set.  */
  priv_copyset (__suidset, scratch);
  priv_union (inherit, scratch);
  if (basic)
    priv_union (basic, scratch);
  priv_intersect (permit, scratch);
  if (setppriv (PRIV_SET, PRIV_PERMITTED, scratch) != 0)
    goto error;

  /* Check if we need to set the limit set.  */
  if (flags & PU_CLEARLIMITSET)
    {
      priv_emptyset (scratch);
      if (setppriv (PRIV_SET, PRIV_LIMIT, scratch) != 0)
        goto error;
    }
  else if (flags & PU_LIMITPRIVS)
    {
      if (setppriv (PRIV_SET, PRIV_LIMIT, scratch) != 0)
        goto error;
    }

  /* Change the uid to the caller's uid if we're setuid root.  */
  if (euid == 0 && setreuid (uid, uid) != 0)
    goto error;

  goto out;

error:
  res = -1;
  if (__suidset)
    {
      priv_freeset (__suidset);
      __suidset = NULL;
    }
  if (euid == 0)
    setreuid (uid, uid);

out:
  priv_freeset (permit);
  priv_freeset (inherit);
  priv_freeset (scratch);

  return res;
}
Example #5
0
/*
 * Privilege sanity checking when setting: E <= P.
 */
static boolean_t
priv_valid(const cred_t *cr)
{
	return (priv_issubset(&CR_EPRIV(cr), &CR_PPRIV(cr)));
}
Example #6
0
/*
 * Guts of pr_spriv:
 *
 * Set the privileges of a process.
 *
 * In order to set the privileges, the setting process will need to
 * have those privileges in its effective set in order to prevent
 * specially privileged processes to easily gain additional privileges.
 * Pre-existing privileges can be retained.  To change any privileges,
 * PRIV_PROC_OWNER needs to be asserted.
 *
 * In formula:
 *
 *	S' <= S || S' <= S + Ea
 *
 * the new set must either be subset of the old set or a subset of
 * the oldset merged with the effective set of the acting process; or just:
 *
 *	S' <= S + Ea
 *
 * It's not legal to grow the limit set this way.
 *
 */
int
priv_pr_spriv(proc_t *p, prpriv_t *prpriv, const cred_t *cr)
{
	cred_t *oldcred;
	cred_t *newcred;
	int i;
	int err = EPERM;
	cred_priv_t *cp, *ocp;
	priv_set_t eset;

	ASSERT(MUTEX_HELD(&p->p_lock));

	/*
	 * Set must have proper dimension; infosize must be absent
	 * or properly sized.
	 */
	if (prpriv->pr_nsets != PRIV_NSET ||
	    prpriv->pr_setsize != PRIV_SETSIZE ||
	    (prpriv->pr_infosize & (sizeof (uint32_t) - 1)) != 0 ||
	    prpriv->pr_infosize > priv_info->priv_infosize ||
	    prpriv->pr_infosize < 0)
		return (EINVAL);

	mutex_exit(&p->p_lock);

	if (priv_proc_cred_perm(cr, p, &oldcred, VWRITE) != 0) {
		mutex_enter(&p->p_lock);
		return (EPERM);
	}

	newcred = crdup(oldcred);

	/* Copy the privilege sets from prpriv to newcred */
	bcopy(prpriv->pr_sets, CR_PRIVSETS(newcred), PRIV_SETBYTES);

	cp = &newcred->cr_priv;
	ocp = &oldcred->cr_priv;
	eset = CR_OEPRIV(cr);

	priv_intersect(&CR_LPRIV(oldcred), &eset);

	/*
	 * Verify the constraints laid out:
	 * for the limit set, we require that the new set is a subset
	 * of the old limit set.
	 * for all other sets, we require that the new set is either a
	 * subset of the old set or a subset of the intersection of
	 * the old limit set and the effective set of the acting process.
	 */
	for (i = 0; i < PRIV_NSET; i++)
		if (!priv_issubset(&cp->crprivs[i], &ocp->crprivs[i]) &&
		    (i == PRIV_LIMIT || !priv_issubset(&cp->crprivs[i], &eset)))
			break;

	crfree(oldcred);

	if (i < PRIV_NSET || !priv_valid(newcred))
		goto err;

	/* Load the settable privilege information */
	if (prpriv->pr_infosize > 0) {
		char *x = (char *)prpriv + PRIV_PRPRIV_INFO_OFFSET(prpriv);
		char *lastx = x + prpriv->pr_infosize;

		while (x < lastx) {
			priv_info_t *pi = (priv_info_t *)x;
			priv_info_uint_t *pii;

			switch (pi->priv_info_type) {
			case PRIV_INFO_FLAGS:
				pii = (priv_info_uint_t *)x;
				if (pii->info.priv_info_size != sizeof (*pii)) {
					err = EINVAL;
					goto err;
				}
				CR_FLAGS(newcred) &= ~PRIV_USER;
				CR_FLAGS(newcred) |= (pii->val & PRIV_USER);
				break;
			default:
				err = EINVAL;
				goto err;
			}
			/* Guarantee alignment and forward progress */
			if ((pi->priv_info_size & (sizeof (uint32_t) - 1)) ||
			    pi->priv_info_size < sizeof (*pi) ||
			    lastx - x > pi->priv_info_size) {
				err = EINVAL;
				goto err;
			}

			x += pi->priv_info_size;
		}
	}

	/*
	 * We'll try to copy the privilege aware flag; but since the
	 * privileges sets are all individually set, they are set
	 * as if we're privilege aware.  If PRIV_AWARE wasn't set
	 * or was explicitely unset, we need to set the flag and then
	 * try to get rid of it.
	 */
	if ((CR_FLAGS(newcred) & PRIV_AWARE) == 0) {
		CR_FLAGS(newcred) |= PRIV_AWARE;
		priv_adjust_PA(newcred);
	}

	mutex_enter(&p->p_crlock);
	oldcred = p->p_cred;
	p->p_cred = newcred;
	mutex_exit(&p->p_crlock);
	crfree(oldcred);

	mutex_enter(&p->p_lock);
	return (0);

err:
	crfree(newcred);
	mutex_enter(&p->p_lock);
	return (err);
}