+
+ /*
+ * Otherwise, we are reverting back to normal mode of operation where
+ * delayed binding of the process credential sets the credential in
+ * the thread (uu_ucred)
+ */
+ if ((uthread->uu_flag & UT_SETUID) == 0)
+ return (EPERM);
+
+ /* revert to delayed binding of process credential */
+ my_new_cred = kauth_cred_proc_ref(p);
+ kauth_cred_unref(&uthread->uu_ucred);
+ uthread->uu_ucred = my_new_cred;
+ uthread->uu_flag &= ~UT_SETUID;
+
+ return (0);
+}
+
+
+/*
+ * setgroups1
+ *
+ * Description: Internal implementation for both the setgroups and initgroups
+ * system calls
+ *
+ * Parameters: gidsetsize Number of groups in set
+ * gidset Pointer to group list
+ * gmuid Base gid (initgroups only!)
+ *
+ * Returns: 0 Success
+ * suser:EPERM Permision denied
+ * EINVAL Invalid gidsetsize value
+ * copyin:EFAULT Bad gidset or gidsetsize is
+ * too large
+ *
+ * Notes: When called from a thread running under an assumed per-thread
+ * identity, this function will operate against the per-thread
+ * credential, rather than against the process credential. In
+ * this specific case, the process credential is verified to
+ * still be privileged at the time of the call, rather than the
+ * per-thread credential for this operation to be permitted.
+ *
+ * This effectively means that setgroups/initigroups calls in
+ * a thread running a per-thread credential should occur *after*
+ * the settid call that created it, not before (unlike setuid,
+ * which must be called after, since it will result in privilege
+ * being dropped).
+ *
+ * When called normally (i.e. no per-thread assumed identity),
+ * the per process credential is updated per POSIX.
+ *
+ * If the credential is changed as a result of this call, then we
+ * flag the process as having set privilege since the last exec.
+ */
+static int
+setgroups1(proc_t p, u_int gidsetsize, user_addr_t gidset, uid_t gmuid, __unused int32_t *retval)
+{
+ u_int ngrp;
+ gid_t newgroups[NGROUPS] = { 0 };
+ int error;
+ kauth_cred_t my_cred, my_new_cred;
+ struct uthread *uthread = get_bsdthread_info(current_thread());
+
+ DEBUG_CRED_ENTER("setgroups1 (%d/%d): %d 0x%016x %d\n", p->p_pid, (p->p_pptr ? p->p_pptr->p_pid : 0), gidsetsize, gidset, gmuid);
+
+ ngrp = gidsetsize;
+ if (ngrp > NGROUPS)
+ return (EINVAL);
+
+ if ( ngrp < 1 ) {
+ ngrp = 1;
+ } else {
+ error = copyin(gidset,
+ (caddr_t)newgroups, ngrp * sizeof(gid_t));
+ if (error) {
+ return (error);
+ }
+ }
+
+ my_cred = kauth_cred_proc_ref(p);
+ if ((error = suser(my_cred, &p->p_acflag))) {
+ kauth_cred_unref(&my_cred);
+ return (error);
+ }
+
+ if ((uthread->uu_flag & UT_SETUID) != 0) {
+#if DEBUG_CRED
+ int my_cred_flags = uthread->uu_ucred->cr_flags;
+#endif /* DEBUG_CRED */
+ kauth_cred_unref(&my_cred);
+
+ /*
+ * If this thread is under an assumed identity, set the
+ * supplementary grouplist on the thread credential instead
+ * of the process one. If we were the only reference holder,
+ * the credential is updated in place, otherwise, our reference
+ * is dropped and we get back a different cred with a reference
+ * already held on it. Because this is per-thread, we don't
+ * need the referencing/locking/retry required for per-process.
+ */
+ my_cred = uthread->uu_ucred;
+ uthread->uu_ucred = kauth_cred_setgroups(my_cred, &newgroups[0], ngrp, gmuid);
+#if DEBUG_CRED
+ if (my_cred != uthread->uu_ucred) {
+ DEBUG_CRED_CHANGE("setgroups1(CH)%d: %p/0x%08x->%p/0x%08x\n", p->p_pid, my_cred, my_cred_flags, uthread->uu_ucred , uthread->uu_ucred ->cr_flags);
+ }
+#endif /* DEBUG_CRED */
+ } else {
+
+ /*
+ * get current credential and take a reference while we muck
+ * with it
+ */
+ for (;;) {
+ /*
+ * Set the credential with new info. If there is no
+ * change, we get back the same credential we passed
+ * in; if there is a change, we drop the reference on
+ * the credential we passed in. The subsequent
+ * compare is safe, because it is a pointer compare
+ * rather than a contents compare.
+ */
+ my_new_cred = kauth_cred_setgroups(my_cred, &newgroups[0], ngrp, gmuid);
+ if (my_cred != my_new_cred) {
+
+ DEBUG_CRED_CHANGE("setgroups1(CH)%d: %p/0x%08x->%p/0x%08x\n", p->p_pid, my_cred, my_cred->cr_flags, my_new_cred, my_new_cred->cr_flags);
+
+ proc_lock(p);
+ /*
+ * We need to protect for a race where another
+ * thread also changed the credential after we
+ * took our reference. If p_ucred has
+ * changed then we should restart this again
+ * with the new cred.
+ */
+ if (p->p_ucred != my_cred) {
+ proc_unlock(p);
+ kauth_cred_unref(&my_new_cred);
+ my_cred = kauth_cred_proc_ref(p);
+ /* try again */
+ continue;
+ }
+ p->p_ucred = my_new_cred;
+ /* update cred on proc */
+ PROC_UPDATE_CREDS_ONPROC(p);
+ OSBitOrAtomic(P_SUGID, &p->p_flag);
+ proc_unlock(p);
+ }
+ break;
+ }
+ /* Drop old proc reference or our extra reference */
+ AUDIT_ARG(groupset, posix_cred_get(my_cred)->cr_groups, ngrp);
+ kauth_cred_unref(&my_cred);
+
+
+ set_security_token(p);
+ }
+
+ return (0);
+}
+
+
+/*
+ * initgroups
+ *
+ * Description: Initialize the default supplementary groups list and set the
+ * gmuid for use by the external group resolver (if any)
+ *
+ * Parameters: uap->gidsetsize Number of groups in set
+ * uap->gidset Pointer to group list
+ * uap->gmuid Base gid
+ *
+ * Returns: 0 Success
+ * setgroups1:EPERM Permision denied
+ * setgroups1:EINVAL Invalid gidsetsize value
+ * setgroups1:EFAULT Bad gidset or gidsetsize is
+ *
+ * Notes: This function opts *IN* to memberd participation
+ *
+ * The normal purpose of this function is for a privileged
+ * process to indicate supplementary groups and identity for
+ * participation in extended group membership resolution prior
+ * to dropping privilege by assuming a specific user identity.
+ *
+ * It is the first half of the primary mechanism whereby user
+ * identity is established to the system by programs such as
+ * /usr/bin/login. The second half is the drop of uid privilege
+ * for a specific uid corresponding to the user.
+ *
+ * See also: setgroups1()
+ */
+int
+initgroups(proc_t p, struct initgroups_args *uap, __unused int32_t *retval)
+{
+ DEBUG_CRED_ENTER("initgroups\n");
+
+ return(setgroups1(p, uap->gidsetsize, uap->gidset, uap->gmuid, retval));
+}
+
+
+/*
+ * setgroups
+ *
+ * Description: Initialize the default supplementary groups list
+ *
+ * Parameters: gidsetsize Number of groups in set
+ * gidset Pointer to group list
+ *
+ * Returns: 0 Success
+ * setgroups1:EPERM Permision denied
+ * setgroups1:EINVAL Invalid gidsetsize value
+ * setgroups1:EFAULT Bad gidset or gidsetsize is
+ *
+ * Notes: This functions opts *OUT* of memberd participation.
+ *
+ * This function exists for compatibility with POSIX. Most user
+ * programs should use initgroups() instead to ensure correct
+ * participation in group membership resolution when utilizing
+ * a directory service for authentication.
+ *
+ * It is identical to an initgroups() call with a gmuid argument
+ * of KAUTH_UID_NONE.
+ *
+ * See also: setgroups1()
+ */
+int
+setgroups(proc_t p, struct setgroups_args *uap, __unused int32_t *retval)
+{
+ DEBUG_CRED_ENTER("setgroups\n");
+
+ return(setgroups1(p, uap->gidsetsize, uap->gidset, KAUTH_UID_NONE, retval));