diff options
Diffstat (limited to 'Src/options.c')
-rw-r--r-- | Src/options.c | 141 |
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) |