- /*
- * There are five cases, and we attempt to emulate them in
- * the following fashion:
- * -1, -1: return 0. This is correct emulation.
- * -1, N: call seteuid(N). This is correct emulation.
- * N, -1: if we called setuid(N), our euid would be changed
- * to N as well. the theory is that we don't want to
- * revoke root access yet, so we call seteuid(N)
- * instead. This is incorrect emulation, but often
- * suffices enough for binary compatibility.
- * N, N: call setuid(N). This is correct emulation.
- * N, M: call setuid(N). This is close to correct emulation.
- */
- if (uap->ruid == (uid_t)-1) {
- if (uap->euid == (uid_t)-1)
- return (0); /* -1, -1 */
- seuidargs.euid = uap->euid; /* -1, N */
- return (seteuid(p, &seuidargs, retval));
+ DEBUG_CRED_ENTER("setregid %d %d\n", uap->rgid, uap->egid);
+
+ rgid = uap->rgid;
+ egid = uap->egid;
+
+ if (rgid == (uid_t)-1)
+ rgid = KAUTH_GID_NONE;
+ if (egid == (uid_t)-1)
+ egid = KAUTH_GID_NONE;
+ AUDIT_ARG(egid, egid);
+ AUDIT_ARG(rgid, rgid);
+
+ my_cred = kauth_cred_proc_ref(p);
+ my_pcred = posix_cred_get(my_cred);
+
+ if (((rgid != KAUTH_UID_NONE && /* allow no change of rgid */
+ rgid != my_pcred->cr_rgid && /* allow rgid = rgid */
+ rgid != my_pcred->cr_gid && /* allow rgid = egid */
+ rgid != my_pcred->cr_svgid) || /* allow rgid = svgid */
+ (egid != KAUTH_UID_NONE && /* allow no change of egid */
+ egid != my_pcred->cr_groups[0] && /* allow no change of egid */
+ egid != my_pcred->cr_gid && /* allow egid = egid */
+ egid != my_pcred->cr_rgid && /* allow egid = rgid */
+ egid != my_pcred->cr_svgid)) && /* allow egid = svgid */
+ (error = suser(my_cred, &p->p_acflag))) { /* allow root user any */
+ kauth_cred_unref(&my_cred);
+ return (error);
+ }
+
+ /* get current credential and take a reference while we muck with it */
+ for (;;) {
+ uid_t new_egid = my_pcred->cr_gid;
+ uid_t new_rgid = my_pcred->cr_rgid;
+ uid_t svgid = KAUTH_UID_NONE;
+
+
+ /*
+ * 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.
+ */
+ if (egid == KAUTH_UID_NONE && my_pcred->cr_gid != egid) {
+ /* changing the effective GID */
+ new_egid = egid;
+ OSBitOrAtomic(P_SUGID, &p->p_flag);
+ }
+ if (rgid != KAUTH_UID_NONE && my_pcred->cr_rgid != rgid) {
+ /* changing the real GID */
+ new_rgid = rgid;
+ OSBitOrAtomic(P_SUGID, &p->p_flag);
+ }
+ /*
+ * If the newly requested real gid or effective gid does
+ * not match the saved gid, then set the saved gid to the
+ * new effective gid. We are protected from escalation
+ * by the prechecking.
+ */
+ if (my_pcred->cr_svgid != uap->rgid &&
+ my_pcred->cr_svgid != uap->egid) {
+ svgid = new_egid;
+ OSBitOrAtomic(P_SUGID, &p->p_flag);
+ }
+
+ my_new_cred = kauth_cred_setresgid(my_cred, rgid, egid, svgid);
+ if (my_cred != my_new_cred) {
+
+ DEBUG_CRED_CHANGE("setregid(CH)%d: %p/0x%08x->%p/0x%08x\n", p->p_pid, my_cred, my_pcred->cr_flags, my_new_cred, posix_cred_get(my_new_cred)->cr_flags);
+
+ proc_lock(p);
+ /* 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);
+ /* try again */
+ my_cred = kauth_cred_proc_ref(p);
+ continue;
+ }
+ p->p_ucred = my_new_cred;
+ /* update cred on proc */
+ PROC_UPDATE_CREDS_ONPROC(p);
+ OSBitOrAtomic(P_SUGID, &p->p_flag); /* XXX redundant? */
+ proc_unlock(p);
+ }
+ break;