summaryrefslogtreecommitdiff
path: root/Src/options.c
diff options
context:
space:
mode:
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)