about summary refs log tree commit diff
path: root/Src/options.c
diff options
context:
space:
mode:
authorDaniel Shahaf <danielsh@apache.org>2020-03-07 21:36:46 +0000
committerDaniel Shahaf <danielsh@apache.org>2020-03-07 21:36:46 +0000
commit6fc8e8628f9c3da6e4b83c3de67e44376708cbcb (patch)
treec711b1d53564bcca757799e1d734e24859c5f32e /Src/options.c
parent0d3d07c56f75064310271bf4469c5c9c13145d58 (diff)
parentdd50f125b5eb65896642d2ff664adefd33f1004c (diff)
downloadzsh-6fc8e8628f9c3da6e4b83c3de67e44376708cbcb.tar.gz
zsh-6fc8e8628f9c3da6e4b83c3de67e44376708cbcb.tar.xz
zsh-6fc8e8628f9c3da6e4b83c3de67e44376708cbcb.zip
Merge remote-tracking branch 'origin/master' into 5.9
* Test/D02glob.ztst:
  On the "unreadable directories can be globbed (users/24619, users/24626)"
  test, resolve conflicts by removing the Cygwin-only skip that has been added
  in master, since the test is passing on this branch.  This effectively reverts
  workers/45492.  See discussion starting in workers/45504.

* origin/master:
  unposted: Remove 'sgi', as that OpenBSD port has been discontinued.
  45509: fix typos in B01cd.ztst
  45490 (+45495 and a test): refactor rlimits.c
  github #49: Fix typo: longson should be loongson
  users/24710: Fix job control problem with sudo.
  45492: skip test added by users/24633 on Cygwin
  45488: COMP_WORDS for bash need "$@"-style quoting
  45487: Missing mod_export declarations for AIX
  45447: Complete vcs_info_hookadd and vcs_info_hookdel. Expose _vcs_info_hooks as a top-level helper function.
  45463: test: kill: Document why we use SIGURG
  45453: builtins: kill: Do not signal current process group when pid is empty
  45452: builtins: kill: Add `kill ''` regression test with explicit sigspec
  45451: builtins: kill: Add basic test suite
  github #48/0002: vcs_info git: properly detect bare repositories
  github #48/0001: vcs_info git: avoid warnings in bare repositories
  unposted: Post-release version bump
  unposted: Release 5.8
  CVE-2019-20044: Update change log for preceding commits
  Update NEWS/README
  Add unsetopt/PRIVILEGED tests
  Clean up error-message white space
  Improve PRIVILEGED fixes (again)
  Improve PRIVILEGED fixes
  Drop privileges securely
  unposted: V01zmodload: Fix failing test from workers/45385
  45423: _su: Improve arg handling, shell look-ups
  unposted: _zip: Recognise '--'
  45385: Add a test for 'zmodload -Fa' preemptively disabling ("blacklisting"?) features.
  unposted: Test release: 5.7.1-test-3
  zsh/system: Fix infinite loop in sysread
  _diff_options: Restore -w completion lost in workers/43351
  unposted: Fix ChangeLog typo.
  45368: Add tests for workers/45367's issue about double slashes in 'cd -P' and /home/daniel/in/zsh.
  45373: Fix ERR_EXIT bug in else branch of if.
  45372: Record a symlink loop bug involving :P
  45365: _git: Fix __git_recent_branches for the case when a commit has an empty message
  45343: Queue signals around arithmetic evaluations
  45344: Document where third-party completion functions should be installed.
  45345: internal: ztst.vim: Fix highlighting of zsh comments in test payload
  unposted: internal: Add some comments and fix indentation.  No functional change.
  45340: internal: Document the difference between paramtab and realparamtab.
  45332: _git: add completion for git-version
  _brace_parameter: add missing \

Conflicts:
	ChangeLog
	Test/D02glob.ztst
	Test/V01zmodload.ztst
Diffstat (limited to 'Src/options.c')
-rw-r--r--Src/options.c141
1 files changed, 107 insertions, 34 deletions
diff --git a/Src/options.c b/Src/options.c
index 48c14c179..08ba71917 100644
--- a/Src/options.c
+++ b/Src/options.c
@@ -577,6 +577,7 @@ int
 bin_setopt(char *nam, char **args, UNUSED(Options ops), int isun)
 {
     int action, optno, match = 0;
+    int retval = 0;
 
     /* With no arguments or options, display options. */
     if (!*args) {
@@ -604,18 +605,24 @@ bin_setopt(char *nam, char **args, UNUSED(Options ops), int isun)
 		    inittyptab();
 		    return 1;
 		}
-		if(!(optno = optlookup(*args)))
+		if(!(optno = optlookup(*args))) {
 		    zwarnnam(nam, "no such option: %s", *args);
-		else if(dosetopt(optno, action, 0, opts))
+		    retval |= 1;
+		} else if (dosetopt(optno, action, 0, opts)) {
 		    zwarnnam(nam, "can't change option: %s", *args);
+		    retval |= 1;
+		}
 		break;
 	    } else if(**args == 'm') {
 		match = 1;
 	    } else {
-	    	if (!(optno = optlookupc(**args)))
+		if (!(optno = optlookupc(**args))) {
 		    zwarnnam(nam, "bad option: -%c", **args);
-		else if(dosetopt(optno, action, 0, opts))
+		    retval |= 1;
+		} else if (dosetopt(optno, action, 0, opts)) {
 		    zwarnnam(nam, "can't change option: -%c", **args);
+		    retval |= 1;
+		}
 	    }
 	}
 	args++;
@@ -625,10 +632,13 @@ bin_setopt(char *nam, char **args, UNUSED(Options ops), int isun)
     if (!match) {
 	/* Not globbing the arguments -- arguments are simply option names. */
 	while (*args) {
-	    if(!(optno = optlookup(*args++)))
+	    if(!(optno = optlookup(*args++))) {
 		zwarnnam(nam, "no such option: %s", args[-1]);
-	    else if(dosetopt(optno, !isun, 0, opts))
+		retval |= 1;
+	    } else if (dosetopt(optno, !isun, 0, opts)) {
 		zwarnnam(nam, "can't change option: %s", args[-1]);
+		retval |= 1;
+	    }
 	}
     } else {
 	/* Globbing option (-m) set. */
@@ -651,7 +661,8 @@ bin_setopt(char *nam, char **args, UNUSED(Options ops), int isun)
 	    tokenize(s);
 	    if (!(pprog = patcompile(s, PAT_HEAPDUP, NULL))) {
 		zwarnnam(nam, "bad pattern: %s", *args);
-		continue;
+		retval |= 1;
+		break;
 	    }
 	    /* Loop over expansions. */
 	    scanmatchtable(optiontab, pprog, 0, 0, OPT_ALIAS,
@@ -660,7 +671,7 @@ bin_setopt(char *nam, char **args, UNUSED(Options ops), int isun)
 	}
     }
     inittyptab();
-    return 0;
+    return retval;
 }
 
 /* Identify an option name */
@@ -769,37 +780,99 @@ dosetopt(int optno, int value, int force, char *new_opts)
 	    return -1;
     } else if(optno == PRIVILEGED && !value) {
 	/* unsetting PRIVILEGED causes the shell to make itself unprivileged */
-#ifdef HAVE_SETUID
-	int ignore_err;
-	errno = 0;
+
+/* 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;
+	const int orig_euid = geteuid();
+	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.
-	 *
-	 * Some OSes (possibly no longer around) have been known to
-	 * fail silently the first time, so we attempt the change twice.
-	 * If it fails we are guaranteed to pick this up the second
-	 * time, so ignore the first time.
-	 *
-	 * Some versions of gcc make it hard to ignore the results the
-	 * first time, hence the following.  (These are probably not
-	 * systems that require the doubled calls.)
 	 */
-	ignore_err = setgid(getgid());
-	(void)ignore_err;
-	ignore_err = setuid(getuid());
-	(void)ignore_err;
-	if (setgid(getgid())) {
-            zwarn("failed to change group ID: %e", errno);
-            return -1;
-        } else if (setuid(getuid())) {
-            zwarn("failed to change user ID: %e", errno);
-            return -1;
+	if (setresgid(getgid(), getgid(), getgid())) {
+	    zwarnnam("unsetopt",
+		"PRIVILEGED: can't drop privileges; failed to change group ID: %e",
+		errno);
+	    return -1;
 	}
-#else
-        zwarn("setuid not available");
-        return -1;
-#endif /* not HAVE_SETUID */
+
+# ifdef HAVE_INITGROUPS
+	/* 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. */
+# 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;
+	}
+
+	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;
+	}
+
+	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;
+	}
+
+	if (failed) {
+	    /* A warning message has been printed. */
+	    return -1;
+	}
+#endif /* HAVE_SETRESGID && HAVE_SETRESUID */
+
 #ifdef JOB_CONTROL
     } else if (!force && optno == MONITOR && value) {
 	if (new_opts[optno] == value)