]> granicus.if.org Git - sudo/commitdiff
Better error messages, and added debugging throughout.
authorTodd C. Miller <Todd.Miller@courtesan.com>
Fri, 9 Mar 2012 22:07:41 +0000 (17:07 -0500)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Fri, 9 Mar 2012 22:07:41 +0000 (17:07 -0500)
Fixed seteuid() version of set_perms()/restore_perms().
Fixed logic bug in AIX version of restore_perms().
Added checks to avoid changing uid/gid when we don't have to.
Never set gid/uid state to -1, use the old value instead.

plugins/sudoers/set_perms.c

index 1f0a2ac695113046d2949cda4ce6f43ba8633947..4c5570edf495bed213df862479b955a706615b81 100644 (file)
@@ -96,6 +96,9 @@ rewind_perms(void)
 
 #if defined(HAVE_SETRESUID)
 
+#define UID_CHANGED (state->ruid != ostate->ruid || state->euid != ostate->euid || state->suid != ostate->suid)
+#define GID_CHANGED (state->rgid != ostate->rgid || state->egid != ostate->egid || state->sgid != ostate->sgid)
+
 /*
  * Set real and effective and saved uids and gids based on perm.
  * We always retain a saved uid of 0 unless we are headed for an exec().
@@ -106,7 +109,7 @@ int
 set_perms(int perm)
 {
     struct perm_state *state, *ostate = NULL;
-    const char *errstr;
+    char errbuf[1024];
     int noexit;
     debug_decl(set_perms, SUDO_DEBUG_PERMS)
 
@@ -114,7 +117,7 @@ set_perms(int perm)
     CLR(perm, PERM_MASK);
 
     if (perm_stack_depth == PERM_STACK_MAX) {
-       errstr = _("perm stack overflow");
+       strlcpy(errbuf, _("perm stack overflow"), sizeof(errbuf));
        errno = EINVAL;
        goto bad;
     }
@@ -122,7 +125,7 @@ set_perms(int perm)
     state = &perm_stack[perm_stack_depth];
     if (perm != PERM_INITIAL) {
        if (perm_stack_depth == 0) {
-           errstr = _("perm stack underflow");
+           strlcpy(errbuf, _("perm stack underflow"), sizeof(errbuf));
            errno = EINVAL;
            goto bad;
        }
@@ -136,12 +139,12 @@ set_perms(int perm)
        /* Stash initial state */
 #ifdef HAVE_GETRESUID
        if (getresuid(&state->ruid, &state->euid, &state->suid)) {
-           errstr = "getresuid";
+           strlcpy(errbuf, "PERM_INITIAL: getresuid", sizeof(errbuf));
            goto bad;
 
        }
        if (getresgid(&state->rgid, &state->egid, &state->sgid)) {
-           errstr = "getresgid";
+           strlcpy(errbuf, "PERM_INITIAL: getresgid", sizeof(errbuf));
            goto bad;
        }
 #else
@@ -155,44 +158,64 @@ set_perms(int perm)
 #endif
        state->grlist = user_group_list;
        grlist_addref(state->grlist);
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_INITIAL: "
+           "ruid: %d, euid: %d, suid: %d, rgid: %d, egid: %d, sgid: %d",
+           __func__, (int)state->ruid, (int)state->euid, (int)state->suid,
+           (int)state->rgid, (int)state->egid, (int)state->sgid);
        break;
 
     case PERM_ROOT:
        state->ruid = ROOT_UID;
        state->euid = ROOT_UID;
        state->suid = ROOT_UID;
-       if (setresuid(ID(ruid), ID(euid), ID(suid))) {
-           errstr = "setresuid(ROOT_UID, ROOT_UID, ROOT_UID)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_ROOT: uid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->ruid, (int)ostate->euid, (int)ostate->suid,
+           (int)state->ruid, (int)state->euid, (int)state->suid);
+       if (UID_CHANGED && setresuid(ID(ruid), ID(euid), ID(suid))) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_ROOT: setresuid(%d, %d, %d)",
+               ID(ruid), ID(euid), ID(suid));
            goto bad;
        }
-       state->rgid = -1;
-       state->egid = -1;
-       state->sgid = -1;
+       state->rgid = ostate->rgid;
+       state->egid = ostate->egid;
+       state->sgid = ostate->sgid;
        state->grlist = ostate->grlist;
        grlist_addref(state->grlist);
        break;
 
     case PERM_USER:
-       state->rgid = -1;
+       state->rgid = ostate->rgid;
        state->egid = user_gid;
-       state->sgid = -1;
-       if (setresgid(-1, ID(egid), -1)) {
-           errstr = "setresgid(-1, user_gid, -1)";
+       state->sgid = ostate->sgid;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_USER: gid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->rgid, (int)ostate->egid, (int)ostate->sgid,
+           (int)state->rgid, (int)state->egid, (int)state->sgid);
+       if (GID_CHANGED && setresgid(ID(rgid), ID(egid), ID(sgid))) {
+           snprintf(errbuf, sizeof(errbuf), "PERM_USER: setresgid(%d, %d, %d)",
+               ID(rgid), ID(egid), ID(sgid));
            goto bad;
        }
        state->grlist = user_group_list;
        grlist_addref(state->grlist);
        if (state->grlist != ostate->grlist) {
            if (sudo_setgroups(state->grlist->ngids, state->grlist->gids)) {
-               errstr = "setgroups()";
+               strlcpy(errbuf, "PERM_USER: setgroups", sizeof(errbuf));
                goto bad;
            }
        }
        state->ruid = user_uid;
        state->euid = user_uid;
        state->suid = ROOT_UID;
-       if (setresuid(ID(ruid), ID(euid), ID(suid))) {
-           errstr = "setresuid(user_uid, user_uid, ROOT_UID)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_USER: uid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->ruid, (int)ostate->euid, (int)ostate->suid,
+           (int)state->ruid, (int)state->euid, (int)state->suid);
+       if (UID_CHANGED && setresuid(ID(ruid), ID(euid), ID(suid))) {
+           snprintf(errbuf, sizeof(errbuf), "PERM_USER: setresuid(%d, %d, %d)",
+               ID(ruid), ID(euid), ID(suid));
            goto bad;
        }
        break;
@@ -202,41 +225,61 @@ set_perms(int perm)
        state->rgid = user_gid;
        state->egid = user_gid;
        state->sgid = user_gid;
-       if (setresgid(ID(rgid), ID(egid), ID(sgid))) {
-           errstr = "setresgid(user_gid, user_gid, user_gid)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_FULL_USER: gid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->rgid, (int)ostate->egid, (int)ostate->sgid,
+           (int)state->rgid, (int)state->egid, (int)state->sgid);
+       if (GID_CHANGED && setresgid(ID(rgid), ID(egid), ID(sgid))) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_FULL_USER: setresgid(%d, %d, %d)",
+               ID(rgid), ID(egid), ID(sgid));
            goto bad;
        }
        state->grlist = user_group_list;
        grlist_addref(state->grlist);
        if (state->grlist != ostate->grlist) {
            if (sudo_setgroups(state->grlist->ngids, state->grlist->gids)) {
-               errstr = "setgroups()";
+               strlcpy(errbuf, "PERM_FULL_USER: setgroups", sizeof(errbuf));
                goto bad;
            }
        }
        state->ruid = user_uid;
        state->euid = user_uid;
        state->suid = user_uid;
-       if (setresuid(ID(ruid), ID(euid), ID(suid))) {
-           errstr = "setresuid(user_uid, user_uid, user_uid)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_FULL_USER: uid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->ruid, (int)ostate->euid, (int)ostate->suid,
+           (int)state->ruid, (int)state->euid, (int)state->suid);
+       if (UID_CHANGED && setresuid(ID(ruid), ID(euid), ID(suid))) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_FULL_USER: setresuid(%d, %d, %d)",
+               ID(ruid), ID(euid), ID(suid));
            goto bad;
        }
        break;
 
     case PERM_RUNAS:
-       state->rgid = -1;
+       state->rgid = ostate->rgid;
        state->egid = runas_gr ? runas_gr->gr_gid : runas_pw->pw_gid;
-       state->sgid = -1;
-       if (setresgid(-1, ID(egid), -1)) {
-           errstr = _("unable to change to runas gid");
+       state->sgid = ostate->sgid;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_RUNAS: gid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->rgid, (int)ostate->egid, (int)ostate->sgid,
+           (int)state->rgid, (int)state->egid, (int)state->sgid);
+       if (GID_CHANGED && setresgid(ID(rgid), ID(egid), ID(sgid))) {
+           strlcpy(errbuf, _("unable to change to runas gid"), sizeof(errbuf));
            goto bad;
        }
        state->grlist = runas_setgroups();
-       state->ruid = -1;
+       state->ruid = ostate->ruid;
        state->euid = runas_pw ? runas_pw->pw_uid : user_uid;
-       state->suid = -1;
-       if (setresuid(-1, ID(euid), -1)) {
-           errstr = _("unable to change to runas uid");
+       state->suid = ostate->suid;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_RUNAS: uid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->ruid, (int)ostate->euid, (int)ostate->suid,
+           (int)state->ruid, (int)state->euid, (int)state->suid);
+       if (UID_CHANGED && setresuid(ID(ruid), ID(euid), ID(suid))) {
+           strlcpy(errbuf, _("unable to change to runas uid"), sizeof(errbuf));
            goto bad;
        }
        break;
@@ -246,10 +289,14 @@ set_perms(int perm)
        grlist_addref(state->grlist);
 
        /* assumes euid == ROOT_UID, ruid == user */
-       state->rgid = -1;
+       state->rgid = ostate->rgid;
        state->egid = sudoers_gid;
-       state->sgid = -1;
-       if (setresgid(-1, ID(egid), -1))
+       state->sgid = ostate->sgid;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_SUDOERS: gid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->rgid, (int)ostate->egid, (int)ostate->sgid,
+           (int)state->rgid, (int)state->egid, (int)state->sgid);
+       if (GID_CHANGED && setresgid(ID(rgid), ID(egid), ID(sgid)))
            error(1, _("unable to change to sudoers gid"));
 
        state->ruid = ROOT_UID;
@@ -263,8 +310,14 @@ set_perms(int perm)
        else
            state->euid = sudoers_uid;
        state->suid = ROOT_UID;
-       if (setresuid(ID(ruid), ID(euid), ID(suid))) {
-           errstr = "setresuid(ROOT_UID, SUDOERS_UID, ROOT_UID)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_SUDOERS: uid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->ruid, (int)ostate->euid, (int)ostate->suid,
+           (int)state->ruid, (int)state->euid, (int)state->suid);
+       if (UID_CHANGED && setresuid(ID(ruid), ID(euid), ID(suid))) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_SUDOERS: setresuid(%d, %d, %d)",
+               ID(ruid), ID(euid), ID(suid));
            goto bad;
        }
        break;
@@ -272,14 +325,20 @@ set_perms(int perm)
     case PERM_TIMESTAMP:
        state->grlist = ostate->grlist;
        grlist_addref(state->grlist);
-       state->rgid = -1;
-       state->egid = -1;
-       state->sgid = -1;
+       state->rgid = ostate->rgid;
+       state->egid = ostate->egid;
+       state->sgid = ostate->sgid;
        state->ruid = ROOT_UID;
        state->euid = timestamp_uid;
        state->suid = ROOT_UID;
-       if (setresuid(ID(ruid), ID(euid), ID(suid))) {
-           errstr = "setresuid(ROOT_UID, timestamp_uid, ROOT_UID)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_TIMESTAMP: uid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->ruid, (int)ostate->euid, (int)ostate->suid,
+           (int)state->ruid, (int)state->euid, (int)state->suid);
+       if (UID_CHANGED && setresuid(ID(ruid), ID(euid), ID(suid))) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_TIMESTAMP: setresuid(%d, %d, %d)",
+               ID(ruid), ID(euid), ID(suid));
            goto bad;
        }
        break;
@@ -289,8 +348,7 @@ done:
     perm_stack_depth++;
     debug_return_bool(1);
 bad:
-    /* XXX - better warnings inline */
-    warningx("%s: %s", errstr,
+    warningx("%s: %s", errbuf,
        errno == EAGAIN ? _("too many processes") : strerror(errno));
     if (noexit)
        debug_return_bool(0);
@@ -310,6 +368,13 @@ restore_perms(void)
     ostate = &perm_stack[perm_stack_depth - 2];
     perm_stack_depth--;
 
+    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: uid: [%d, %d, %d] -> [%d, %d, %d]",
+       __func__, (int)state->ruid, (int)state->euid, (int)state->suid,
+       (int)ostate->ruid, (int)ostate->euid, (int)ostate->suid);
+    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: gid: [%d, %d, %d] -> [%d, %d, %d]",
+       __func__, (int)state->rgid, (int)state->egid, (int)state->sgid,
+       (int)ostate->rgid, (int)ostate->egid, (int)ostate->sgid);
+
     /* XXX - more cases here where euid != ruid */
     if (OID(euid) == ROOT_UID && state->euid != ROOT_UID) {
        if (setresuid(-1, ROOT_UID, -1)) {
@@ -346,6 +411,9 @@ bad:
 
 #elif defined(_AIX) && defined(ID_SAVED)
 
+#define UID_CHANGED (state->ruid != ostate->ruid || state->euid != ostate->euid || state->suid != ostate->suid)
+#define GID_CHANGED (state->rgid != ostate->rgid || state->egid != ostate->egid || state->sgid != ostate->sgid)
+
 /*
  * Set real and effective and saved uids and gids based on perm.
  * We always retain a saved uid of 0 unless we are headed for an exec().
@@ -356,7 +424,7 @@ int
 set_perms(int perm)
 {
     struct perm_state *state, *ostate = NULL;
-    const char *errstr;
+    char errbuf[1024];
     int noexit;
     debug_decl(set_perms, SUDO_DEBUG_PERMS)
 
@@ -364,7 +432,7 @@ set_perms(int perm)
     CLR(perm, PERM_MASK);
 
     if (perm_stack_depth == PERM_STACK_MAX) {
-       errstr = _("perm stack overflow");
+       strlcpy(errbuf, _("perm stack overflow"), sizeof(errbuf));
        errno = EINVAL;
        goto bad;
     }
@@ -372,7 +440,7 @@ set_perms(int perm)
     state = &perm_stack[perm_stack_depth];
     if (perm != PERM_INITIAL) {
        if (perm_stack_depth == 0) {
-           errstr = _("perm stack underflow");
+           strlcpy(errbuf, _("perm stack underflow"), sizeof(errbuf));
            errno = EINVAL;
            goto bad;
        }
@@ -392,50 +460,73 @@ set_perms(int perm)
        state->sgid = getgidx(ID_SAVED);
        state->grlist = user_group_list;
        grlist_addref(state->grlist);
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_INITIAL: "
+           "ruid: %d, euid: %d, suid: %d, rgid: %d, egid: %d, sgid: %d",
+           __func__, (unsigned int)state->ruid, (unsigned int)state->euid,
+           (unsigned int)state->suid, (unsigned int)state->rgid,
+           (unsigned int)state->egid, (unsigned int)state->sgid);
        break;
 
     case PERM_ROOT:
-       if (setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, ROOT_UID)) {
-           errstr = "setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, ROOT_UID)";
-           goto bad;
-       }
        state->ruid = ROOT_UID;
        state->euid = ROOT_UID;
        state->suid = ROOT_UID;
-       state->rgid = -1;
-       state->egid = -1;
-       state->sgid = -1;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_ROOT: uid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->ruid, (int)ostate->euid, (int)ostate->suid,
+           (int)state->ruid, (int)state->euid, (int)state->suid);
+       if (UID_CHANGED && setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, ROOT_UID)) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_ROOT: setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, %d)",
+               ROOT_UID);
+           goto bad;
+       }
+       state->rgid = ostate->rgid;
+       state->egid = ostate->egid;
+       state->sgid = ostate->sgid;
        state->grlist = ostate->grlist;
        grlist_addref(state->grlist);
        break;
 
     case PERM_USER:
-       state->rgid = -1;
+       state->rgid = ostate->rgid;
        state->egid = user_gid;
-       state->sgid = -1;
-       if (setgidx(ID_EFFECTIVE, user_gid)) {
-           errstr = "setgidx(ID_EFFECTIVE, user_gid)";
+       state->sgid = ostate->sgid;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_USER: gid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->rgid, (int)ostate->egid, (int)ostate->sgid,
+           (int)state->rgid, (int)state->egid, (int)state->sgid);
+       if (GID_CHANGED && setgidx(ID_EFFECTIVE, user_gid)) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_USER: setgidx(ID_EFFECTIVE, %d)", user_gid);
            goto bad;
        }
        state->grlist = user_group_list;
        grlist_addref(state->grlist);
        if (state->grlist != ostate->grlist) {
            if (sudo_setgroups(state->grlist->ngids, state->grlist->gids)) {
-               errstr = "setgroups()";
+               strlcpy(errbuf, "PERM_USER: setgroups", sizeof(errbuf));
                goto bad;
            }
        }
-       if (ID(ruid) != ROOT_UID || ID(euid) != ROOT_UID || ID(suid) != ROOT_UID) {
+       state->ruid = user_uid;
+       state->euid = user_uid;
+       state->suid = ROOT_UID;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_USER: uid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->ruid, (int)ostate->euid, (int)ostate->suid,
+           (int)state->ruid, (int)state->euid, (int)state->suid);
+       if (ostate->euid != ROOT_UID || ostate->suid != ROOT_UID) {
            if (setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, ROOT_UID)) {
-               errstr = "setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, ROOT_UID)";
+               snprintf(errbuf, sizeof(errbuf),
+                   "PERM_USER: setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, %d)",
+                   ROOT_UID);
                goto bad;
            }
        }
-       state->ruid = user_uid;
-       state->euid = user_uid;
-       state->suid = ROOT_UID;
        if (setuidx(ID_EFFECTIVE|ID_REAL, user_uid)) {
-           errstr = "setuidx(ID_EFFECTIVE|ID_REAL, user_uid)";
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_USER: setuidx(ID_EFFECTIVE|ID_REAL, %d)", user_uid);
            goto bad;
        }
        break;
@@ -445,41 +536,61 @@ set_perms(int perm)
        state->rgid = user_gid;
        state->egid = user_gid;
        state->sgid = user_gid;
-       if (setgidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, user_gid)) {
-           errstr = "setgidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, user_gid)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_FULL_USER: gid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->rgid, (int)ostate->egid, (int)ostate->sgid,
+           (int)state->rgid, (int)state->egid, (int)state->sgid);
+       if (GID_CHANGED && setgidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, user_gid)) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_FULL_USER: setgidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, %d)",
+               user_gid);
            goto bad;
        }
        state->grlist = user_group_list;
        grlist_addref(state->grlist);
        if (state->grlist != ostate->grlist) {
            if (sudo_setgroups(state->grlist->ngids, state->grlist->gids)) {
-               errstr = "setgroups()";
+               strlcpy(errbuf, "PERM_FULL_USER: setgroups", sizeof(errbuf));
                goto bad;
            }
        }
        state->ruid = user_uid;
        state->euid = user_uid;
        state->suid = user_uid;
-       if (setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, user_uid)) {
-           errstr = "setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, user_uid)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_FULL_USER: uid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->ruid, (int)ostate->euid, (int)ostate->suid,
+           (int)state->ruid, (int)state->euid, (int)state->suid);
+       if (UID_CHANGED && setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, user_uid)) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_FULL_USER: setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, %d)",
+               user_uid);
            goto bad;
        }
        break;
 
     case PERM_RUNAS:
-       state->rgid = -1;
+       state->rgid = ostate->rgid;
        state->egid = runas_gr ? runas_gr->gr_gid : runas_pw->pw_gid;
-       state->sgid = -1;
-       if (setgidx(ID_EFFECTIVE, state->egid)) {
-           errstr = _("unable to change to runas gid");
+       state->sgid = ostate->sgid;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_RUNAS: gid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->rgid, (int)ostate->egid, (int)ostate->sgid,
+           (int)state->rgid, (int)state->egid, (int)state->sgid);
+       if (GID_CHANGED && setgidx(ID_EFFECTIVE, state->egid)) {
+           strlcpy(errbuf, _("unable to change to runas gid"), sizeof(errbuf));
            goto bad;
        }
        state->grlist = runas_setgroups();
-       state->ruid = -1;
+       state->ruid = ostate->ruid;
        state->euid = runas_pw ? runas_pw->pw_uid : user_uid;
-       state->suid = -1;
-       if (setuidx(ID_EFFECTIVE, state->euid)) {
-           errstr = _("unable to change to runas uid");
+       state->suid = ostate->suid;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_RUNAS: uid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->ruid, (int)ostate->euid, (int)ostate->suid,
+           (int)state->ruid, (int)state->euid, (int)state->suid);
+       if (UID_CHANGED && setuidx(ID_EFFECTIVE, state->euid)) {
+           strlcpy(errbuf, _("unable to change to runas uid"), sizeof(errbuf));
            goto bad;
        }
        break;
@@ -489,10 +600,14 @@ set_perms(int perm)
        grlist_addref(state->grlist);
 
        /* assume euid == ROOT_UID, ruid == user */
-       state->rgid = -1;
+       state->rgid = ostate->rgid;
        state->egid = sudoers_gid;
-       state->sgid = -1;
-       if (setgidx(ID_EFFECTIVE, sudoers_gid))
+       state->sgid = ostate->sgid;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_SUDOERS: gid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->rgid, (int)ostate->egid, (int)ostate->sgid,
+           (int)state->rgid, (int)state->egid, (int)state->sgid);
+       if (GID_CHANGED && setgidx(ID_EFFECTIVE, sudoers_gid))
            error(1, _("unable to change to sudoers gid"));
 
        state->ruid = ROOT_UID;
@@ -506,37 +621,55 @@ set_perms(int perm)
        else
            state->euid = sudoers_uid;
        state->suid = ROOT_UID;
-       if (ID(ruid) != ROOT_UID || ID(suid) != ROOT_UID) {
-           if (setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, ROOT_UID)) {
-               errstr = "setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, ROOT_UID)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_SUDOERS: uid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->ruid, (int)ostate->euid, (int)ostate->suid,
+           (int)state->ruid, (int)state->euid, (int)state->suid);
+       if (UID_CHANGED) {
+           if (ostate->ruid != ROOT_UID || ostate->suid != ROOT_UID) {
+               if (setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, ROOT_UID)) {
+                   snprintf(errbuf, sizeof(errbuf),
+                       "PERM_SUDOERS: setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, %d)",
+                       ROOT_UID);
+                   goto bad;
+               }
+           }
+           if (setuidx(ID_EFFECTIVE, state->euid)) {
+               snprintf(errbuf, sizeof(errbuf),
+                   "PERM_SUDOERS: setuidx(ID_EFFECTIVE, %d)", sudoers_uid);
                goto bad;
            }
        }
-       if (setuidx(ID_EFFECTIVE, state->euid)) {
-           errstr = "setuidx(ID_EFFECTIVE, SUDOERS_UID)";
-           goto bad;
-       }
        break;
 
     case PERM_TIMESTAMP:
        state->grlist = ostate->grlist;
        grlist_addref(state->grlist);
-       state->rgid = -1;
-       state->egid = -1;
-       state->sgid = -1;
+       state->rgid = ostate->rgid;
+       state->egid = ostate->egid;
+       state->sgid = ostate->sgid;
        state->ruid = ROOT_UID;
        state->euid = timestamp_uid;
        state->suid = ROOT_UID;
-       if (ID(ruid) != ROOT_UID || ID(suid) != ROOT_UID) {
-           if (setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, ROOT_UID)) {
-               errstr = "setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, ROOT_UID)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_TIMESTAMP: uid: "
+           "[%d, %d, %d] -> [%d, %d, %d]", __func__,
+           (int)ostate->ruid, (int)ostate->euid, (int)ostate->suid,
+           (int)state->ruid, (int)state->euid, (int)state->suid);
+       if (UID_CHANGED) {
+           if (ostate->ruid != ROOT_UID || ostate->suid != ROOT_UID) {
+               if (setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, ROOT_UID)) {
+                   snprintf(errbuf, sizeof(errbuf),
+                       "PERM_TIMESTAMP: setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, %d)",
+                       ROOT_UID);
+                   goto bad;
+               }
+           }
+           if (setuidx(ID_EFFECTIVE, timestamp_uid)) {
+               snprintf(errbuf, sizeof(errbuf),
+                   "PERM_TIMESTAMP: setuidx(ID_EFFECTIVE, %d)", timestamp_uid);
                goto bad;
            }
        }
-       if (setuidx(ID_EFFECTIVE, timestamp_uid)) {
-           errstr = "setuidx(ID_EFFECTIVE, timestamp_uid)";
-           goto bad;
-       }
        break;
     }
 
@@ -544,8 +677,7 @@ done:
     perm_stack_depth++;
     debug_return_bool(1);
 bad:
-    /* XXX - better warnings inline */
-    warningx("%s: %s", errstr,
+    warningx("%s: %s", errbuf,
        errno == EAGAIN ? _("too many processes") : strerror(errno));
     if (noexit)
        debug_return_bool(0);
@@ -565,6 +697,13 @@ restore_perms(void)
     ostate = &perm_stack[perm_stack_depth - 2];
     perm_stack_depth--;
 
+    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: uid: [%d, %d, %d] -> [%d, %d, %d]",
+       __func__, (int)state->ruid, (int)state->euid, (int)state->suid,
+       (int)ostate->ruid, (int)ostate->euid, (int)ostate->suid);
+    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: gid: [%d, %d, %d] -> [%d, %d, %d]",
+       __func__, (int)state->rgid, (int)state->egid, (int)state->sgid,
+       (int)ostate->rgid, (int)ostate->egid, (int)ostate->sgid);
+
     if (OID(ruid) != -1 && OID(euid) != -1 && OID(suid) != -1) {
        /* XXX - more cases here where euid != ruid */
        if (OID(euid) == ROOT_UID && state->euid != ROOT_UID) {
@@ -575,7 +714,7 @@ restore_perms(void)
                goto bad;
            }
        }
-       if (OID(ruid) == OID(euid) == OID(suid)) {
+       if (OID(ruid) == OID(euid) && OID(euid) == OID(suid)) {
            if (setuidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, OID(ruid))) {
                warning("setuidx() [%d, %d, %d] -> [%d, %d, %d]",
                    (int)state->ruid, (int)state->euid, (int)state->suid,
@@ -599,7 +738,7 @@ restore_perms(void)
        }
     }
     if (OID(rgid) != -1 && OID(egid) != -1 && OID(sgid) != -1) {
-       if (OID(rgid) == OID(egid) == OID(sgid)) {
+       if (OID(rgid) == OID(egid) && OID(egid) == OID(sgid)) {
            if (setgidx(ID_EFFECTIVE|ID_REAL|ID_SAVED, OID(rgid))) {
                warning("setgidx() [%d, %d, %d] -> [%d, %d, %d]",
                    (int)state->rgid, (int)state->egid, (int)state->sgid,
@@ -637,6 +776,9 @@ bad:
 
 #elif defined(HAVE_SETREUID)
 
+#define UID_CHANGED (state->ruid != ostate->ruid || state->euid != ostate->euid)
+#define GID_CHANGED (state->rgid != ostate->rgid || state->egid != ostate->egid)
+
 /*
  * Set real and effective and saved uids and gids based on perm.
  * We always retain a saved uid of 0 unless we are headed for an exec().
@@ -647,7 +789,7 @@ int
 set_perms(int perm)
 {
     struct perm_state *state, *ostate = NULL;
-    const char *errstr;
+    char errbuf[1024];
     int noexit;
     debug_decl(set_perms, SUDO_DEBUG_PERMS)
 
@@ -655,7 +797,7 @@ set_perms(int perm)
     CLR(perm, PERM_MASK);
 
     if (perm_stack_depth == PERM_STACK_MAX) {
-       errstr = _("perm stack overflow");
+       strlcpy(errbuf, _("perm stack overflow"), sizeof(errbuf));
        errno = EINVAL;
        goto bad;
     }
@@ -663,7 +805,7 @@ set_perms(int perm)
     state = &perm_stack[perm_stack_depth];
     if (perm != PERM_INITIAL) {
        if (perm_stack_depth == 0) {
-           errstr = _("perm stack underflow");
+           strlcpy(errbuf, _("perm stack underflow"), sizeof(errbuf));
            errno = EINVAL;
            goto bad;
        }
@@ -681,49 +823,68 @@ set_perms(int perm)
        state->egid = getegid();
        state->grlist = user_group_list;
        grlist_addref(state->grlist);
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_INITIAL: "
+           "ruid: %d, euid: %d, rgid: %d, egid: %d", __func__,
+           (int)state->ruid, (int)state->euid,
+           (int)state->rgid, (int)state->egid);
        break;
 
     case PERM_ROOT:
+       state->ruid = ROOT_UID;
+       state->euid = ROOT_UID;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_ROOT: uid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->ruid,
+           (int)ostate->euid, (int)state->ruid, (int)state->euid);
        /*
         * setreuid(0, 0) may fail on some systems if euid is not already 0.
         */
        if (ostate->euid != ROOT_UID) {
            if (setreuid(-1, ROOT_UID)) {
-               errstr = "setreuid(-1, ROOT_UID)";
+               snprintf(errbuf, sizeof(errbuf),
+                   "PERM_ROOT: setreuid(-1, %d)", PERM_ROOT);
                goto bad;
            }
        }
-       state->euid = ROOT_UID;
-       if (setreuid(ID(ruid), -1)) {
-           errstr = "setreuid(ROOT_UID, -1)";
-           goto bad;
+       if (ostate->ruid != ROOT_UID) {
+           if (setreuid(ROOT_UID, -1)) {
+               snprintf(errbuf, sizeof(errbuf),
+                   "PERM_ROOT: setreuid(%d, -1)", ROOT_UID);
+               goto bad;
+           }
        }
-       state->ruid = ROOT_UID;
-       state->rgid = -1;
-       state->egid = -1;
+       state->rgid = ostate->rgid;
+       state->egid = ostate->rgid;
        state->grlist = ostate->grlist;
        grlist_addref(state->grlist);
        break;
 
     case PERM_USER:
-       state->rgid = -1;
+       state->rgid = ostate->rgid;
        state->egid = user_gid;
-       if (setregid(-1, ID(egid))) {
-           errstr = "setregid(-1, user_gid)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_USER: gid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->rgid,
+           (int)ostate->egid, (int)state->rgid, (int)state->egid);
+       if (GID_CHANGED && setregid(ID(rgid), ID(egid))) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_USER: setregid(%d, %d)", ID(rgid), ID(egid));
            goto bad;
        }
        state->grlist = user_group_list;
        grlist_addref(state->grlist);
        if (state->grlist != ostate->grlist) {
            if (sudo_setgroups(state->grlist->ngids, state->grlist->gids)) {
-               errstr = "setgroups()";
+               strlcpy(errbuf, "PERM_USER: setgroups", sizeof(errbuf));
                goto bad;
            }
        }
        state->ruid = ROOT_UID;
        state->euid = user_uid;
-       if (setreuid(ID(ruid), ID(euid))) {
-           errstr = "setreuid(ROOT_UID, user_uid)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_USER: uid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->ruid,
+           (int)ostate->euid, (int)state->ruid, (int)state->euid);
+       if (UID_CHANGED && setreuid(ID(ruid), ID(euid))) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_USER: setreuid(%d, %d)", ID(ruid), ID(euid));
            goto bad;
        }
        break;
@@ -732,38 +893,52 @@ set_perms(int perm)
        /* headed for exec() */
        state->rgid = user_gid;
        state->egid = user_gid;
-       if (setregid(ID(rgid), ID(egid))) {
-           errstr = "setregid(user_gid, user_gid)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_FULL_USER: gid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->rgid,
+           (int)ostate->egid, (int)state->rgid, (int)state->egid);
+       if (GID_CHANGED && setregid(ID(rgid), ID(egid))) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_FULL_USER: setregid(%d, %d)", ID(rgid), ID(egid));
            goto bad;
        }
        state->grlist = user_group_list;
        grlist_addref(state->grlist);
        if (state->grlist != ostate->grlist) {
            if (sudo_setgroups(state->grlist->ngids, state->grlist->gids)) {
-               errstr = "setgroups()";
+               strlcpy(errbuf, "PERM_FULL_USER: setgroups", sizeof(errbuf));
                goto bad;
            }
        }
        state->ruid = user_uid;
        state->euid = user_uid;
-       if (setreuid(ID(ruid), ID(euid))) {
-           errstr = "setreuid(user_uid, user_uid)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_FULL_USER: uid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->ruid,
+           (int)ostate->euid, (int)state->ruid, (int)state->euid);
+       if (UID_CHANGED && setreuid(ID(ruid), ID(euid))) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_FULL_USER: setreuid(%d, %d)", ID(ruid), ID(euid));
            goto bad;
        }
        break;
 
     case PERM_RUNAS:
-       state->rgid = -1;
+       state->rgid = ostate->rgid;
        state->egid = runas_gr ? runas_gr->gr_gid : runas_pw->pw_gid;
-       if (setregid(ID(rgid), ID(egid))) {
-           errstr = _("unable to change to runas gid");
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_RUNAS: gid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->rgid,
+           (int)ostate->egid, (int)state->rgid, (int)state->egid);
+       if (GID_CHANGED && setregid(ID(rgid), ID(egid))) {
+           strlcpy(errbuf, _("unable to change to runas gid"), sizeof(errbuf));
            goto bad;
        }
        state->grlist = runas_setgroups();
        state->ruid = ROOT_UID;
        state->euid = runas_pw ? runas_pw->pw_uid : user_uid;
-       if (setreuid(ID(ruid), ID(euid))) {
-           errstr = _("unable to change to runas uid");
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_RUNAS: uid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->ruid,
+           (int)ostate->euid, (int)state->ruid, (int)state->euid);
+       if (UID_CHANGED && setreuid(ID(ruid), ID(euid))) {
+           strlcpy(errbuf, _("unable to change to runas uid"), sizeof(errbuf));
            goto bad;
        }
        break;
@@ -773,9 +948,12 @@ set_perms(int perm)
        grlist_addref(state->grlist);
 
        /* assume euid == ROOT_UID, ruid == user */
-       state->rgid = -1;
+       state->rgid = ostate->rgid;
        state->egid = sudoers_gid;
-       if (setregid(-1, ID(egid)))
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_SUDOERS: gid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->rgid,
+           (int)ostate->egid, (int)state->rgid, (int)state->egid);
+       if (GID_CHANGED && setregid(ID(rgid), ID(egid)))
            error(1, _("unable to change to sudoers gid"));
 
        state->ruid = ROOT_UID;
@@ -788,8 +966,12 @@ set_perms(int perm)
            state->euid = 1;
        else
            state->euid = sudoers_uid;
-       if (setreuid(ID(ruid), ID(euid))) {
-           errstr = "setreuid(ROOT_UID, SUDOERS_UID)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_SUDOERS: uid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->ruid,
+           (int)ostate->euid, (int)state->ruid, (int)state->euid);
+       if (UID_CHANGED && setreuid(ID(ruid), ID(euid))) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_SUDOERS: setreuid(%d, %d)", ID(ruid), ID(euid));
            goto bad;
        }
        break;
@@ -797,12 +979,16 @@ set_perms(int perm)
     case PERM_TIMESTAMP:
        state->grlist = ostate->grlist;
        grlist_addref(state->grlist);
-       state->rgid = -1;
-       state->egid = -1;
+       state->rgid = ostate->rgid;
+       state->egid = ostate->egid;
        state->ruid = ROOT_UID;
        state->euid = timestamp_uid;
-       if (setreuid(ID(ruid), ID(euid))) {
-           errstr = "setreuid(ROOT_UID, timestamp_uid)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_TIMESTAMP: uid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->ruid,
+           (int)ostate->euid, (int)state->ruid, (int)state->euid);
+       if (UID_CHANGED && setreuid(ID(ruid), ID(euid))) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_TIMESTAMP: setreuid(%d, %d)", ID(ruid), ID(euid));
            goto bad;
        }
        break;
@@ -812,8 +998,7 @@ done:
     perm_stack_depth++;
     debug_return_bool(1);
 bad:
-    /* XXX - better warnings inline */
-    warningx("%s: %s", errstr,
+    warningx("%s: %s", errbuf,
        errno == EAGAIN ? _("too many processes") : strerror(errno));
     if (noexit)
        debug_return_bool(0);
@@ -833,6 +1018,13 @@ restore_perms(void)
     ostate = &perm_stack[perm_stack_depth - 2];
     perm_stack_depth--;
 
+    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: uid: [%d, %d] -> [%d, %d]",
+       __func__, (int)state->ruid, (int)state->euid,
+       (int)ostate->ruid, (int)ostate->euid);
+    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: gid: [%d, %d] -> [%d, %d]",
+       __func__, (int)state->rgid, (int)state->egid,
+       (int)ostate->rgid, (int)ostate->egid);
+
     /*
      * When changing euid to ROOT_UID, setreuid() may fail even if
      * the ruid is ROOT_UID so call setuid() first.
@@ -872,6 +1064,8 @@ bad:
 
 #elif defined(HAVE_SETEUID)
 
+#define GID_CHANGED (state->rgid != ostate->rgid || state->egid != ostate->egid)
+
 /*
  * Set real and effective uids and gids based on perm.
  * We always retain a real or effective uid of ROOT_UID unless
@@ -882,7 +1076,7 @@ int
 set_perms(int perm)
 {
     struct perm_state *state, *ostate = NULL;
-    const char *errstr;
+    char errbuf[1024];
     int noexit;
     debug_decl(set_perms, SUDO_DEBUG_PERMS)
 
@@ -890,7 +1084,7 @@ set_perms(int perm)
     CLR(perm, PERM_MASK);
 
     if (perm_stack_depth == PERM_STACK_MAX) {
-       errstr = _("perm stack overflow");
+       strlcpy(errbuf, _("perm stack overflow"), sizeof(errbuf));
        errno = EINVAL;
        goto bad;
     }
@@ -898,7 +1092,7 @@ set_perms(int perm)
     state = &perm_stack[perm_stack_depth];
     if (perm != PERM_INITIAL) {
        if (perm_stack_depth == 0) {
-           errstr = _("perm stack underflow");
+           strlcpy(errbuf, _("perm stack underflow"), sizeof(errbuf));
            errno = EINVAL;
            goto bad;
        }
@@ -913,12 +1107,12 @@ set_perms(int perm)
      * real and effective uids to ROOT_UID initially to be safe.
      */
     if (perm != PERM_INITIAL) {
-       if (seteuid(ROOT_UID)) {
-           errstr = "seteuid(ROOT_UID)";
+       if (ostate->euid != ROOT_UID && seteuid(ROOT_UID)) {
+           snprintf(errbuf, sizeof(errbuf), "set_perms: seteuid(%d)", ROOT_UID);
            goto bad;
        }
-       if (setuid(ROOT_UID)) {
-           errstr = "setuid(ROOT_UID)";
+       if (ostate->ruid != ROOT_UID && setuid(ROOT_UID)) {
+           snprintf(errbuf, sizeof(errbuf), "set_perms: setuid(%d)", ROOT_UID);
            goto bad;
        }
     }
@@ -932,37 +1126,52 @@ set_perms(int perm)
        state->egid = getegid();
        state->grlist = user_group_list;
        grlist_addref(state->grlist);
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_INITIAL: "
+           "ruid: %d, euid: %d, rgid: %d, egid: %d", __func__,
+           (int)state->ruid, (int)state->euid,
+           (int)state->rgid, (int)state->egid);
        break;
 
     case PERM_ROOT:
        /* We already set ruid/euid above. */
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_ROOT: uid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->ruid,
+           (int)ostate->euid, ROOT_UID, ROOT_UID);
        state->ruid = ROOT_UID;
        state->euid = ROOT_UID;
-       state->rgid = -1;
-       state->egid = -1;
+       state->rgid = ostate->rgid;
+       state->egid = ostate->egid;
        state->grlist = ostate->grlist;
        grlist_addref(state->grlist);
        break;
 
     case PERM_USER:
        state->egid = user_gid;
-       if (setegid(ID(egid))) {
-           errstr = "setegid(user_gid)";
+       state->rgid = ostate->rgid;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_USER: gid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->rgid,
+           (int)ostate->egid, (int)state->rgid, (int)state->egid);
+       if (GID_CHANGED && setegid(user_gid)) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_USER: setegid(%d)", user_gid);
            goto bad;
        }
        state->grlist = user_group_list;
        grlist_addref(state->grlist);
        if (state->grlist != ostate->grlist) {
            if (sudo_setgroups(state->grlist->ngids, state->grlist->gids)) {
-               errstr = "setgroups()";
+               strlcpy(errbuf, "PERM_USER: setgroups", sizeof(errbuf));
                goto bad;
            }
        }
-       state->rgid = -1;
        state->ruid = ROOT_UID;
        state->euid = user_uid;
-       if (seteuid(ID(euid))) {
-           errstr = "seteuid(user_uid)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_USER: uid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->ruid,
+           (int)ostate->euid, (int)state->ruid, (int)state->euid);
+       if (seteuid(user_uid)) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_USER: seteuid(%d)", user_uid);
            goto bad;
        }
        break;
@@ -971,38 +1180,52 @@ set_perms(int perm)
        /* headed for exec() */
        state->rgid = user_gid;
        state->egid = user_gid;
-       if (setgid(user_gid)) {
-           errstr = "setgid(user_gid)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_FULL_USER: gid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->rgid,
+           (int)ostate->egid, (int)state->rgid, (int)state->egid);
+       if (GID_CHANGED && setgid(user_gid)) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_FULL_USER: setgid(%d)", user_gid);
            goto bad;
        }
        state->grlist = user_group_list;
        grlist_addref(state->grlist);
        if (state->grlist != ostate->grlist) {
            if (sudo_setgroups(state->grlist->ngids, state->grlist->gids)) {
-               errstr = "setgroups()";
+               strlcpy(errbuf, "PERM_FULL_USER: setgroups", sizeof(errbuf));
                goto bad;
            }
        }
        state->ruid = user_uid;
        state->euid = user_uid;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_FULL_USER: uid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->ruid,
+           (int)ostate->euid, (int)state->ruid, (int)state->euid);
        if (setuid(user_uid)) {
-           errstr = "setuid(user_uid)";
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_FULL_USER: setuid(%d)", user_uid);
            goto bad;
        }
        break;
 
     case PERM_RUNAS:
-       state->rgid = -1;
+       state->rgid = ostate->rgid;
        state->egid = runas_gr ? runas_gr->gr_gid : runas_pw->pw_gid;
-       if (setegid(ID(egid))) {
-           errstr = _("unable to change to runas gid");
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_RUNAS: gid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->rgid,
+           (int)ostate->egid, (int)state->rgid, (int)state->egid);
+       if (GID_CHANGED && setegid(state->egid)) {
+           strlcpy(errbuf, _("unable to change to runas gid"), sizeof(errbuf));
            goto bad;
        }
        state->grlist = runas_setgroups();
-       state->ruid = -1;
+       state->ruid = ostate->ruid;
        state->euid = runas_pw ? runas_pw->pw_uid : user_uid;
-       if (seteuid(ID(euid))) {
-           errstr = _("unable to change to runas uid");
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_RUNAS: uid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->ruid,
+           (int)ostate->euid, (int)state->ruid, (int)state->euid);
+       if (seteuid(state->euid)) {
+           strlcpy(errbuf, _("unable to change to runas uid"), sizeof(errbuf));
            goto bad;
        }
        break;
@@ -1012,9 +1235,12 @@ set_perms(int perm)
        grlist_addref(state->grlist);
 
        /* assume euid == ROOT_UID, ruid == user */
-       state->rgid = -1;
+       state->rgid = ostate->rgid;
        state->egid = sudoers_gid;
-       if (setegid(ID(egid)))
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_SUDOERS: gid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->rgid,
+           (int)ostate->egid, (int)state->rgid, (int)state->egid);
+       if (GID_CHANGED && setegid(sudoers_gid))
            error(1, _("unable to change to sudoers gid"));
 
        state->ruid = ROOT_UID;
@@ -1027,8 +1253,12 @@ set_perms(int perm)
            state->euid = 1;
        else
            state->euid = sudoers_uid;
-       if (seteuid(ID(euid))) {
-           errstr = "seteuid(SUDOERS_UID)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_SUDOERS: uid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->ruid,
+           (int)ostate->euid, (int)state->ruid, (int)state->euid);
+       if (seteuid(state->euid)) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_SUDOERS: seteuid(%d)", state->euid);
            goto bad;
        }
        break;
@@ -1036,12 +1266,16 @@ set_perms(int perm)
     case PERM_TIMESTAMP:
        state->grlist = ostate->grlist;
        grlist_addref(state->grlist);
-       state->rgid = -1;
-       state->egid = -1;
+       state->rgid = ostate->rgid;
+       state->egid = ostate->egid;
        state->ruid = ROOT_UID;
        state->euid = timestamp_uid;
-       if (seteuid(ID(euid))) {
-           errstr = "seteuid(timestamp_uid)";
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_TIMESTAMP: uid: "
+           "[%d, %d] -> [%d, %d]", __func__, (int)ostate->ruid,
+           (int)ostate->euid, (int)state->ruid, (int)state->euid);
+       if (seteuid(timestamp_uid)) {
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_TIMESTAMP: seteuid(%d)", timestamp_uid);
            goto bad;
        }
        break;
@@ -1051,8 +1285,7 @@ done:
     perm_stack_depth++;
     debug_return_bool(1);
 bad:
-    /* XXX - better warnings inline */
-    warningx("%s: %s", errstr,
+    warningx("%s: %s", errbuf,
        errno == EAGAIN ? _("too many processes") : strerror(errno));
     if (noexit)
        debug_return_bool(0);
@@ -1072,22 +1305,30 @@ restore_perms(void)
     ostate = &perm_stack[perm_stack_depth - 2];
     perm_stack_depth--;
 
+    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: uid: [%d, %d] -> [%d, %d]",
+       __func__, (int)state->ruid, (int)state->euid,
+       (int)ostate->ruid, (int)ostate->euid);
+    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: gid: [%d, %d] -> [%d, %d]",
+       __func__, (int)state->rgid, (int)state->egid,
+       (int)ostate->rgid, (int)ostate->egid);
+
     /*
      * Since we only have setuid() and seteuid() and semantics
      * for these calls differ on various systems, we set
      * real and effective uids to ROOT_UID initially to be safe.
      */
     if (seteuid(ROOT_UID)) {
-       errstr = "seteuid(ROOT_UID)";
+       warningx("seteuid() [%d] -> [%d]", (int)state->euid, ROOT_UID);
        goto bad;
     }
     if (setuid(ROOT_UID)) {
-       errstr = "setuid(ROOT_UID)";
+       warningx("setuid() [%d, %d] -> [%d, %d]", (int)state->ruid, ROOT_UID,
+           ROOT_UID, ROOT_UID);
        goto bad;
     }
 
-    if (setegid(OID(egid))) {
-       warning("setegid(%d)", (int)OID(egid));
+    if (OID(egid) != -1 && setegid(ostate->egid)) {
+       warning("setegid(%d)", (int)ostate->egid);
        goto bad;
     }
     if (state->grlist != ostate->grlist) {
@@ -1096,8 +1337,8 @@ restore_perms(void)
            goto bad;
        }
     }
-    if (seteuid(OID(euid))) {
-       warning("seteuid(%d)", (int)OID(euid));
+    if (OID(euid) != -1 && seteuid(ostate->euid)) {
+       warning("seteuid(%d)", ostate->euid);
        goto bad;
     }
     grlist_delref(state->grlist);
@@ -1118,7 +1359,7 @@ int
 set_perms(int perm)
 {
     struct perm_state *state, *ostate = NULL;
-    const char *errstr;
+    char errbuf[1024];
     int noexit;
     debug_decl(set_perms, SUDO_DEBUG_PERMS)
 
@@ -1126,7 +1367,7 @@ set_perms(int perm)
     CLR(perm, PERM_MASK);
 
     if (perm_stack_depth == PERM_STACK_MAX) {
-       errstr = _("perm stack overflow");
+       strlcpy(errbuf, _("perm stack overflow"), sizeof(errbuf));
        errno = EINVAL;
        goto bad;
     }
@@ -1134,7 +1375,7 @@ set_perms(int perm)
     state = &perm_stack[perm_stack_depth];
     if (perm != PERM_INITIAL) {
        if (perm_stack_depth == 0) {
-           errstr = _("perm stack underflow");
+           strlcpy(errbuf, _("perm stack underflow"), sizeof(errbuf));
            errno = EINVAL;
            goto bad;
        }
@@ -1150,33 +1391,42 @@ set_perms(int perm)
        state->rgid = getgid();
        state->grlist = user_group_list;
        grlist_addref(state->grlist);
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_INITIAL: "
+           "ruid: %d, rgid: %d", __func__, (int)state->ruid, (int)state->rgid);
        break;
 
     case PERM_ROOT:
        state->ruid = ROOT_UID;
-       state->rgid = -1;
+       state->rgid = ostate->rgid;
        state->grlist = ostate->grlist;
        grlist_addref(state->grlist);
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_ROOT: uid: "
+           "[%d] -> [%d]", __func__, (int)ostate->ruid, (int)state->ruid);
        if (setuid(ROOT_UID)) {
-           errstr = "setuid(ROOT_UID)";
+           snprintf(errbuf, sizeof(errbuf), "PERM_ROOT: setuid(%d)", ROOT_UID);
            goto bad;
        }
        break;
 
     case PERM_FULL_USER:
        state->rgid = user_gid;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_ROOT: gid: "
+           "[%d] -> [%d]", __func__, (int)ostate->rgid, (int)state->rgid);
        (void) setgid(user_gid);
        state->grlist = user_group_list;
        grlist_addref(state->grlist);
        if (state->grlist != ostate->grlist) {
            if (sudo_setgroups(state->grlist->ngids, state->grlist->gids)) {
-               errstr = "setgroups()";
+               strlcpy(errbuf, "PERM_FULL_USER: setgroups", sizeof(errbuf))
                goto bad;
            }
        }
        state->ruid = user_uid;
+       sudo_debug_printf(SUDO_DEBUG_INFO, "%s: PERM_ROOT: uid: "
+           "[%d] -> [%d]", __func__, (int)ostate->ruid, (int)state->ruid);
        if (setuid(user_uid)) {
-           errstr = "setuid(user_uid)";
+           snprintf(errbuf, sizeof(errbuf),
+               "PERM_FULL_USER: setuid(%d)", user_uid);
            goto bad;
        }
        break;
@@ -1193,8 +1443,7 @@ done:
     perm_stack_depth++;
     debug_return_bool(1);
 bad:
-    /* XXX - better warnings inline */
-    warningx("%s: %s", errstr,
+    warningx("%s: %s", errbuf,
        errno == EAGAIN ? _("too many processes") : strerror(errno));
     if (noexit)
        debug_return_bool(0);
@@ -1214,6 +1463,11 @@ restore_perms(void)
     ostate = &perm_stack[perm_stack_depth - 2];
     perm_stack_depth--;
 
+    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: uid: [%d] -> [%d]",
+       __func__, (int)state->ruid, (int)ostate->ruid)
+    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: gid: [%d] -> [%d]",
+       __func__, (int)state->rgid, (int)ostate->rgid);
+
     if (OID(rgid) != -1 && setgid(ostate->rgid)) {
        warning("setgid(%d)", (int)ostate->rgid);
        goto bad;