about summary refs log tree commit diff
diff options
context:
space:
mode:
authordana <dana@dana.is>2019-12-29 02:41:11 +0000
committerdana <dana@dana.is>2020-02-14 16:06:57 -0600
commit26d02efa7a9b0a6b32e1a8bbc6aca6c544b94211 (patch)
treeba46014197050acde6022dece7743a38450bbb9a
parent8250c5c168f07549ed646e6848e6dda118271e23 (diff)
downloadzsh-26d02efa7a9b0a6b32e1a8bbc6aca6c544b94211.tar.gz
zsh-26d02efa7a9b0a6b32e1a8bbc6aca6c544b94211.tar.xz
zsh-26d02efa7a9b0a6b32e1a8bbc6aca6c544b94211.zip
Improve PRIVILEGED fixes (again)
* Pass RGID instead of passwd GID to initgroups()

* Clean up #ifdefs, avoid unnecessary checks

* Flatten conditions
-rw-r--r--Src/options.c92
1 files changed, 43 insertions, 49 deletions
diff --git a/Src/options.c b/Src/options.c
index 425e27dc2..e9577c863 100644
--- a/Src/options.c
+++ b/Src/options.c
@@ -781,91 +781,85 @@ dosetopt(int optno, int value, int force, char *new_opts)
     } else if(optno == PRIVILEGED && !value) {
 	/* unsetting PRIVILEGED causes the shell to make itself unprivileged */
 
+/* For simplicity's sake, require both setresgid() and setresuid() up-front. */
+#if !defined(HAVE_SETRESGID)
+	zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; setresgid() and friends not available");
+	return -1;
+#elif !defined(HAVE_SETRESUID)
+	zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; setresuid() and friends not available");
+	return -1;
+#else
 	/* If set, return -1 so lastval will be non-zero. */
 	int failed = 0;
-
-#ifdef HAVE_SETUID
 	const int orig_euid = geteuid();
-#endif
 	const int orig_egid = getegid();
 
 	/*
 	 * Set the GID first as if we set the UID to non-privileged it
 	 * might be impossible to restore the GID.
 	 */
-	{
-#ifndef HAVE_SETRESGID
-	    zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; setresgid() and friends not available");
+	if (setresgid(getgid(), getgid(), getgid())) {
+	    zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; failed to change group ID: %e", errno);
 	    return -1;
-#else
-	    int setgid_err;
-	    setgid_err = setresgid(getgid(), getgid(), getgid());
-	    if (setgid_err) {
-		zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; failed to change group ID: %e", errno);
-		return -1;
-	    }
-#endif
 	}
 
-	/* Set the UID second. */
-	{
-#ifndef HAVE_SETRESUID
-	    zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; setresuid() and friends not available");
-	    return -1;
-#else
-	    int setuid_err;
-
 # ifdef HAVE_INITGROUPS
-	    /* Set the supplementary groups list. */
-	    if (geteuid() == 0) {
-		struct passwd *pw = getpwuid(getuid());
-		if (pw == NULL) {
-		    zwarnnam("unsetopt", "can't drop privileges; failed to get user information for uid %L: %e",
-			    (long)getuid(), errno);
-		    failed = 1;
-		} else if (initgroups(pw->pw_name, pw->pw_gid)) {
-		    zwarnnam("unsetopt", "can't drop privileges; failed to set supplementary group list: %e", errno);
-		    return -1;
-		}
-	    } else if (getuid() != 0 &&
-		    (geteuid() != getuid() || orig_egid != getegid())) {
-		zwarnnam("unsetopt", "PRIVILEGED: supplementary group list not changed due to lack of permissions: EUID=%L",
-			(long)geteuid());
+	/* Set the supplementary groups list.
+	 *
+	 * Note that on macOS, FreeBSD, and possibly some other platforms,
+	 * initgroups() resets the EGID to its second argument (see setgroups(2) for
+	 * details). This has the potential to leave the EGID in an unexpected
+	 * state. However, it seems common in other projects that do this dance to
+	 * simply re-use the same GID that's going to become the EGID anyway, in
+	 * which case it doesn't matter. That's what we do here. It's therefore
+	 * possible, in some probably uncommon cases, that the shell ends up not
+	 * having the privileges of the RUID user's primary/passwd group. */
+	if (geteuid() == 0) {
+	    struct passwd *pw = getpwuid(getuid());
+	    if (pw == NULL) {
+		zwarnnam("unsetopt", "can't drop privileges; failed to get user information for uid %L: %e",
+		    (long)getuid(), errno);
 		failed = 1;
+	    /* This may behave strangely in the unlikely event that the same user
+	     * name appears with multiple UIDs in the passwd database */
+	    } else if (initgroups(pw->pw_name, getgid())) {
+		zwarnnam("unsetopt", "can't drop privileges; failed to set supplementary group list: %e", errno);
+		return -1;
 	    }
+	} else if (getuid() != 0 &&
+	    (geteuid() != getuid() || orig_egid != getegid())) {
+	    zwarnnam("unsetopt", "PRIVILEGED: supplementary group list not changed due to lack of permissions: EUID=%L",
+		(long)geteuid());
+	    failed = 1;
+	}
 # else
-	    /* initgroups() isn't in POSIX.  If it's not available on the system,
-	     * we silently skip it. */
+	/* initgroups() isn't in POSIX.  If it's not available on the system,
+	 * we silently skip it. */
 # endif
 
-	    setuid_err = setresuid(getuid(), getuid(), getuid());
-	    if (setuid_err) {
-		zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; failed to change user ID: %e", errno);
-		return -1;
-	    }
-#endif
+	/* Set the UID second. */
+	if (setresuid(getuid(), getuid(), getuid())) {
+	    zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; failed to change user ID: %e", errno);
+	    return -1;
 	}
 
-#ifdef HAVE_SETGID
 	if (getuid() != 0 && orig_egid != getegid() &&
 		(setgid(orig_egid) != -1 || setegid(orig_egid) != -1)) {
 	    zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; was able to restore the egid");
 	    return -1;
 	}
-#endif
 
-#ifdef HAVE_SETUID
 	if (getuid() != 0 && orig_euid != geteuid() &&
 		(setuid(orig_euid) != -1 || seteuid(orig_euid) != -1)) {
 	    zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; was able to restore the euid");
 	    return -1;
 	}
-#endif
 
 	if (failed) {
 	    /* A warning message has been printed. */
 	    return -1;
 	}
+#endif /* HAVE_SETRESGID && HAVE_SETRESUID */
 
 #ifdef JOB_CONTROL
     } else if (!force && optno == MONITOR && value) {