From 24e993db62cf146fb76ebcf677a4a7aa3766fc74 Mon Sep 17 00:00:00 2001 From: Sam Foxman Date: Sun, 22 Dec 2019 17:30:28 -0500 Subject: Drop privileges securely --- Src/options.c | 161 +++++++++++++++++++++++++++++++++++++++++++--------------- configure.ac | 4 +- 2 files changed, 124 insertions(+), 41 deletions(-) diff --git a/Src/options.c b/Src/options.c index 48c14c179..328cf318b 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,28 @@ 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)) - zwarnnam(nam, "can't change option: %s", *args); + retval = 1; + } else { + retval = !!dosetopt(optno, action, 0, opts); + if (retval) { + zwarnnam(nam, "can't change option: %s", *args); + } + } 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)) - zwarnnam(nam, "can't change option: -%c", **args); + retval = 1; + } else { + retval = !!dosetopt(optno, action, 0, opts); + if (retval) { + zwarnnam(nam, "can't change option: -%c", **args); + } + } } } args++; @@ -625,10 +636,15 @@ 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)) - zwarnnam(nam, "can't change option: %s", args[-1]); + retval = 1; + } else { + retval = !!dosetopt(optno, !isun, 0, opts); + if (retval) { + zwarnnam(nam, "can't change option: %s", args[-1]); + } + } } } else { /* Globbing option (-m) set. */ @@ -651,7 +667,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 +677,7 @@ bin_setopt(char *nam, char **args, UNUSED(Options ops), int isun) } } inittyptab(); - return 0; + return retval; } /* Identify an option name */ @@ -769,37 +786,101 @@ 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; - /* - * 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; + + int skip_setuid = 0; + int skip_setgid = 0; + +#if defined(HAVE_GETEGID) && defined(HAVE_SETGID) && defined(HAVE_GETUID) + int orig_egid = getegid(); +#endif + +#if defined(HAVE_GETEUID) && defined(HAVE_GETUID) + if (geteuid() == getuid()) { + skip_setuid = 1; + } +#endif + +#if defined(HAVE_GETEGID) && defined(HAVE_GETGID) + if (getegid() == getgid()) { + skip_setgid = 1; } +#endif + + if (!skip_setgid) { + int setgid_err; +#ifdef HAVE_SETRESGID + setgid_err = setresgid(getgid(), getgid(), getgid()); +#elif defined(HAVE_SETREGID) +#if defined(HAVE_GETEGID) && defined(HAVE_SETGID) && defined(HAVE_GETUID) + setgid_err = setregid(getgid(), getgid()); #else - zwarn("setuid not available"); - return -1; -#endif /* not HAVE_SETUID */ + zwarnnam("unsetopt", + "PRIVILEGED: can't drop privileges; setregid available, but cannot check if saved gid changed"); + return -1; +#endif +#else + zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; setresgid and setregid not available"); + return -1; +#endif + if (setgid_err) { + zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; failed to change group ID: %e", errno); + return -1; + } + } + + if (!skip_setuid) { +#if defined(HAVE_GETEUID) && defined(HAVE_SETUID) + int orig_euid = geteuid(); +#endif + int setuid_err; +#if defined(HAVE_GETEUID) && defined(HAVE_INITGROUPS) && defined(HAVE_GETPWUID) + if (geteuid() == 0) { + struct passwd *pw = getpwuid(getuid()); + if (pw == NULL) { + zwarnnam("unsetopt", "can't drop privileges; failed to get user information for uid %d: %e", + getuid(), errno); + return -1; + } + if (initgroups(pw->pw_name, pw->pw_gid)) { + zwarnnam("unsetopt", "can't drop privileges; failed to set supplementary group list: %e", errno); + return -1; + } + } +#endif + +#ifdef HAVE_SETRESUID + setuid_err = setresuid(getuid(), getuid(), getuid()); +#elif defined(HAVE_SETREUID) +#if defined(HAVE_GETEUID) && defined(HAVE_SETUID) && defined(HAVE_GETUID) + setuid_err = setreuid(getuid(), getuid()); +#else + zwarnnam("unsetopt", + "PRIVILEGED: can't drop privileges; setreuid available, but cannot check if saved uid changed"); + return -1; +#endif +#else + zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; setresuid and setreuid not available"); + return -1; +#endif + if (setuid_err) { + zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; failed to change user ID: %e", errno); + return -1; + } +#if defined(HAVE_GETEUID) && defined(HAVE_SETUID) && defined(HAVE_GETUID) + if (getuid() != 0 && !setuid(orig_euid)) { + zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; was able to restore the euid"); + return -1; + } +#endif + } + +#if defined(HAVE_GETEGID) && defined(HAVE_SETGID) && defined(HAVE_GETUID) + if (getuid() != 0 && !skip_setgid && !setgid(orig_egid)) { + zwarnnam("unsetopt", "PRIVILEGED: can't drop privileges; was able to restore the egid"); + return -1; + } +#endif + #ifdef JOB_CONTROL } else if (!force && optno == MONITOR && value) { if (new_opts[optno] == value) diff --git a/configure.ac b/configure.ac index c8885cac5..2474c270e 100644 --- a/configure.ac +++ b/configure.ac @@ -1310,7 +1310,9 @@ AC_CHECK_FUNCS(strftime strptime mktime timelocal \ inet_aton inet_pton inet_ntop \ getlogin getpwent getpwnam getpwuid getgrgid getgrnam \ initgroups nis_list \ - setuid seteuid setreuid setresuid setsid \ + getuid setuid seteuid setreuid setresuid setsid \ + getgid setgid setegid setregid setresgid \ + geteuid getegid \ memcpy memmove strstr strerror strtoul \ getrlimit getrusage \ setlocale \ -- cgit 1.4.1