This patch will upgrade Sudo version 1.8.31 patchlevel 1 to Sudo version 1.8.31 patchlevel 2. To apply: $ cd sudo-1.8.31p1 $ patch -p1 < sudo-1.8.31p2.patch diff -urNa sudo-1.8.31p1/ChangeLog sudo-1.8.31p2/ChangeLog --- sudo-1.8.31p1/ChangeLog Sat Mar 14 11:25:43 2020 +++ sudo-1.8.31p2/ChangeLog Fri Jun 12 09:59:35 2020 @@ -1,8 +1,98 @@ +2020-06-12 Todd C. Miller + + * .hgtags: + Added tag SUDO_1_8_31p2 for changeset e7b18fb9bded + [bef3b405b502] [tip] <1.8> + +2020-06-11 Todd C. Miller + + * NEWS, configure, configure.ac, src/sudo.c: + sudo 1.8.31p2 + [e7b18fb9bded] [SUDO_1_8_31p2] <1.8> + +2020-06-06 Todd C. Miller + + * config.h.in, configure, configure.ac, include/sudo_compat.h: + Declare getdelim(3) if it exists in libc but is not prototyped in + stdio.h. This can happen on systems with a gcc packages that was + built on and older versions of the OS where getdelim(3) was not + present. + [8a9ae71f2f0b] <1.8> + +2020-06-04 Todd C. Miller + + * plugins/sudoers/ldap.c, plugins/sudoers/parse.c, + plugins/sudoers/sssd.c: + Avoid passing NULL to printf in match debug code for LDAP/SSSD. The + file name in struct userspec was not set for the LDAP and SSSD + backends. There is no actual file in this case so set the name to + LDAP/SSSD. Also add a guard to make sure we don't try to print NULL + in sudoers_lookup_check() if name is left unset. + [783c8193ca63] <1.8> + +2020-06-03 Todd C. Miller + + * plugins/sudoers/ldap.c, plugins/sudoers/sssd.c: + When converting LDAP to sudoers, ignore entries with no sudoHost + attribute. Otherwise, sudo_ldap_role_to_priv() will treat a NULL + host list as as the "ALL" wildcard. This regression was introduced + in sudo 1.8.23, which was the first version to convert LDAP sudoRole + objects to sudoers internal data structures. Thanks to Andreas + Mueller for reporting and debugging this problem. + [c49fb26a6435] <1.8> + +2020-06-02 Todd C. Miller + + * src/exec_pty.c: + If event loop fails due to ENXIO, remove /dev/tty events and + recover. This fixes an issue on Solaris 11.4 (and probably others) + with "sudo reboot" when I/O logging is enabled. Previously, sudo + would kill the command if it was still running after the event loop + terminated, leaving the system in a half-dead state. + [b9b95c6778b8] <1.8> + +2020-06-01 Todd C. Miller + + * src/exec_pty.c: + Don't try to suspend sudo if the user's tty has gone away. Fixes a + problem on Solaris 11.4 (and possibly others) where sudo continually + tries to put itself in the background after the user's terminal has + been revoked. + [ed49c3e168ff] <1.8> + +2020-05-06 Todd C. Miller + + * doc/sudo.man.in, doc/sudo.mdoc.in, src/parse_args.c: + Don't allow duplicate values for command line options that take an + argument. Previously, if multiple instances of the same command line + option were specified, the last one would be used. This meant that, + for example, "sudo -u someuser -u otheruser id" would run the + command as "otheruser". This has the potential to cause problems for + programs that run sudo with a user-specified command that do not use + the "--" option to indicate that no more options should be + processed. While this is a bug in the calling program, there is + little downside to erroring out when multiple options of the same + type are specified on the command line. Bug #924 + [e9ecdf650c0a] <1.8> + +2020-03-31 Todd C. Miller + + * src/exec_pty.c: + Don't kill the command just because the loop exited unexpectedly. We + currently have no good way to distinguish between an error executing + the command and an error while the command is running. + + In the future, we should have additional status codes so we can tell + what type of condition caused the loop to exit. + + For now, only kill the command if cstat is left uninitialized. + [f02ac319b458] <1.8> + 2020-03-14 Todd C. Miller * .hgtags: Added tag SUDO_1_8_31p1 for changeset c80864e62917 - [ec1850cd11a2] [tip] <1.8> + [ec1850cd11a2] <1.8> * NEWS, configure, configure.ac: Sudo 1.8.31p1 @@ -14,6 +104,13 @@ if we set the limit to zero, even for root. This is not a problem outside the container. [314df33eea97] <1.8> + +2020-02-08 Todd C. Miller + + * src/parse_args.c, src/sudo.h: + Mark main sudo usage() function __noreturn__. This splits the usage + printing out into display_usage(). + [8b0f8828c019] <1.8> 2020-01-30 Todd C. Miller diff -urNa sudo-1.8.31p1/NEWS sudo-1.8.31p2/NEWS --- sudo-1.8.31p1/NEWS Sat Mar 14 11:22:49 2020 +++ sudo-1.8.31p2/NEWS Fri Jun 12 07:14:53 2020 @@ -1,3 +1,29 @@ +What's new in Sudo 1.8.31p2 + + * Sudo command line options that take a value may only be specified + once. This is to help guard against problems caused by poorly + written scripts that invoke sudo with user-controlled input. + Bug #924. + + * When running a command in a pty, sudo will no longer try to + suspend itself if the user's tty has been revoked (for instance + when the parent ssh daemon is killed). This fixes a bug where + sudo would continuously suspend the command (which would succeed), + then suspend itself (which would fail due to the missing tty) + and then resume the command. + + * If sudo's event loop fails due to the tty being revoked, remove + the user's tty events and restart the event loop (once). This + fixes a problem when running "sudo reboot" in a pty on some + systems. When the event loop exited unexpectedly, sudo would + kill the command running in the pty, which in the case of "reboot", + could lead to the system being in a half-rebooted state. + + * Fixed a regression introduced in sudo 1.8.23 in the LDAP and + SSSD back-ends where a missing sudoHost attribute was treated + as an "ALL" wildcard value. A sudoRole with no sudoHost attribute + is now ignored as it was prior to version 1.8.23. + What's new in Sudo 1.8.31p1 * Sudo once again ignores a failure to restore the RLIMIT_CORE diff -urNa sudo-1.8.31p1/config.h.in sudo-1.8.31p2/config.h.in --- sudo-1.8.31p1/config.h.in Sat Mar 14 11:20:34 2020 +++ sudo-1.8.31p2/config.h.in Fri Jun 12 07:15:12 2020 @@ -98,6 +98,10 @@ */ #undef HAVE_DECL_ERRNO +/* Define to 1 if you have the declaration of `getdelim', and to 0 if you + don't. */ +#undef HAVE_DECL_GETDELIM + /* Define to 1 if you have the declaration of `getdomainname', and to 0 if you don't. */ #undef HAVE_DECL_GETDOMAINNAME diff -urNa sudo-1.8.31p1/configure sudo-1.8.31p2/configure --- sudo-1.8.31p1/configure Sat Mar 14 11:20:30 2020 +++ sudo-1.8.31p2/configure Fri Jun 12 07:15:11 2020 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for sudo 1.8.31p1. +# Generated by GNU Autoconf 2.69 for sudo 1.8.31p2. # # Report bugs to . # @@ -590,8 +590,8 @@ # Identity of this package. PACKAGE_NAME='sudo' PACKAGE_TARNAME='sudo' -PACKAGE_VERSION='1.8.31p1' -PACKAGE_STRING='sudo 1.8.31p1' +PACKAGE_VERSION='1.8.31p2' +PACKAGE_STRING='sudo 1.8.31p2' PACKAGE_BUGREPORT='https://bugzilla.sudo.ws/' PACKAGE_URL='' @@ -825,6 +825,7 @@ docdir oldincludedir includedir +runstatedir localstatedir sharedstatedir sysconfdir @@ -1042,6 +1043,7 @@ sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' @@ -1294,6 +1296,15 @@ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ @@ -1431,7 +1442,7 @@ for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ - libdir localedir mandir + libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. @@ -1544,7 +1555,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures sudo 1.8.31p1 to adapt to many kinds of systems. +\`configure' configures sudo 1.8.31p2 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1584,6 +1595,7 @@ --sysconfdir=DIR read-only single-machine data [/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] @@ -1609,7 +1621,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sudo 1.8.31p1:";; + short | recursive ) echo "Configuration of sudo 1.8.31p2:";; esac cat <<\_ACEOF @@ -1875,7 +1887,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sudo configure 1.8.31p1 +sudo configure 1.8.31p2 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2584,7 +2596,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by sudo $as_me 1.8.31p1, which was +It was created by sudo $as_me 1.8.31p2, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -19380,8 +19392,21 @@ #define HAVE_GETDELIM 1 _ACEOF + # Out of date gcc fixed includes may result in missing getdelim() prototype + ac_fn_c_check_decl "$LINENO" "getdelim" "ac_cv_have_decl_getdelim" "$ac_includes_default" +if test "x$ac_cv_have_decl_getdelim" = xyes; then : + ac_have_decl=1 else + ac_have_decl=0 +fi +cat >>confdefs.h <<_ACEOF +#define HAVE_DECL_GETDELIM $ac_have_decl +_ACEOF + + +else + case " $LIBOBJS " in *" getdelim.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS getdelim.$ac_objext" @@ -27543,7 +27568,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by sudo $as_me 1.8.31p1, which was +This file was extended by sudo $as_me 1.8.31p2, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -27609,7 +27634,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sudo config.status 1.8.31p1 +sudo config.status 1.8.31p2 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff -urNa sudo-1.8.31p1/configure.ac sudo-1.8.31p2/configure.ac --- sudo-1.8.31p1/configure.ac Sat Mar 14 11:20:07 2020 +++ sudo-1.8.31p2/configure.ac Fri Jun 12 07:14:53 2020 @@ -18,7 +18,7 @@ dnl OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. dnl AC_PREREQ([2.59]) -AC_INIT([sudo], [1.8.31p1], [https://bugzilla.sudo.ws/], [sudo]) +AC_INIT([sudo], [1.8.31p2], [https://bugzilla.sudo.ws/], [sudo]) AC_CONFIG_HEADER([config.h pathnames.h]) AC_CONFIG_SRCDIR([src/sudo.c]) dnl @@ -2560,7 +2560,10 @@ esac SUDO_APPEND_COMPAT_EXP(sudo_getgrouplist) ]) -AC_CHECK_FUNCS([getdelim], [], [ +AC_CHECK_FUNCS([getdelim], [ + # Out of date gcc fixed includes may result in missing getdelim() prototype + AC_CHECK_DECLS([getdelim]) +], [ AC_LIBOBJ(getdelim) SUDO_APPEND_COMPAT_EXP(sudo_getdelim) COMPAT_TEST_PROGS="${COMPAT_TEST_PROGS}${COMPAT_TEST_PROGS+ }getdelim_test" diff -urNa sudo-1.8.31p1/doc/sudo.man.in sudo-1.8.31p2/doc/sudo.man.in --- sudo-1.8.31p1/doc/sudo.man.in Sun Nov 3 07:03:06 2019 +++ sudo-1.8.31p2/doc/sudo.man.in Thu Jun 11 21:24:50 2020 @@ -25,7 +25,7 @@ .nr BA @BAMAN@ .nr LC @LCMAN@ .nr PS @PSMAN@ -.TH "SUDO" "@mansectsu@" "October 20, 2019" "Sudo @PACKAGE_VERSION@" "System Manager's Manual" +.TH "SUDO" "@mansectsu@" "May 6, 2020" "Sudo @PACKAGE_VERSION@" "System Manager's Manual" .nh .if n .ad l .SH "NAME" @@ -685,6 +685,12 @@ option indicates that \fBsudo\fR should stop processing command line arguments. +.PP +Options that take a value may only be specified once. +This is to help guard against problems caused by poorly written +scripts that invoke +\fBsudo\fR +with user-controlled input. .PP Environment variables to be set for the command may also be passed on the command line in the form of diff -urNa sudo-1.8.31p1/doc/sudo.mdoc.in sudo-1.8.31p2/doc/sudo.mdoc.in --- sudo-1.8.31p1/doc/sudo.mdoc.in Sun Nov 3 07:03:06 2019 +++ sudo-1.8.31p2/doc/sudo.mdoc.in Thu Jun 11 21:24:24 2020 @@ -24,7 +24,7 @@ .nr BA @BAMAN@ .nr LC @LCMAN@ .nr PS @PSMAN@ -.Dd October 20, 2019 +.Dd May 6, 2020 .Dt SUDO @mansectsu@ .Os Sudo @PACKAGE_VERSION@ .Sh NAME @@ -637,6 +637,12 @@ .Nm should stop processing command line arguments. .El +.Pp +Options that take a value may only be specified once. +This is to help guard against problems caused by poorly written +scripts that invoke +.Nm sudo +with user-controlled input. .Pp Environment variables to be set for the command may also be passed on the command line in the form of diff -urNa sudo-1.8.31p1/include/sudo_compat.h sudo-1.8.31p2/include/sudo_compat.h --- sudo-1.8.31p1/include/sudo_compat.h Wed Dec 25 12:19:11 2019 +++ sudo-1.8.31p2/include/sudo_compat.h Fri Jun 12 07:14:46 2020 @@ -408,10 +408,13 @@ # undef getgrouplist # define getgrouplist(_a, _b, _c, _d) sudo_getgrouplist((_a), (_b), (_c), (_d)) #endif /* GETGROUPLIST */ -#ifndef HAVE_GETDELIM +#if !defined(HAVE_GETDELIM) __dso_public ssize_t sudo_getdelim(char **bufp, size_t *bufsizep, int delim, FILE *fp); # undef getdelim # define getdelim(_a, _b, _c, _d) sudo_getdelim((_a), (_b), (_c), (_d)) +#elif defined(HAVE_DECL_GETDELIM) && !HAVE_DECL_GETDELIM +/* getdelim present in libc but missing prototype (old gcc fixed includes?) */ +ssize_t getdelim(char **bufp, size_t *bufsizep, int delim, FILE *fp); #endif /* HAVE_GETDELIM */ #ifndef HAVE_GETUSERSHELL __dso_public char *sudo_getusershell(void); diff -urNa sudo-1.8.31p1/plugins/sudoers/ldap.c sudo-1.8.31p2/plugins/sudoers/ldap.c --- sudo-1.8.31p1/plugins/sudoers/ldap.c Mon Oct 28 06:28:52 2019 +++ sudo-1.8.31p2/plugins/sudoers/ldap.c Thu Jun 11 21:25:37 2020 @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: ISC * - * Copyright (c) 2003-2019 Todd C. Miller + * Copyright (c) 2003-2020 Todd C. Miller * * This code is derived from software contributed by Aaron Spangler. * @@ -363,16 +363,21 @@ * Extract the dn from an entry and return the first rdn from it. */ static char * -sudo_ldap_get_first_rdn(LDAP *ld, LDAPMessage *entry) +sudo_ldap_get_first_rdn(LDAP *ld, LDAPMessage *entry, int *rc) { #ifdef HAVE_LDAP_STR2DN char *dn, *rdn = NULL; LDAPDN tmpDN; debug_decl(sudo_ldap_get_first_rdn, SUDOERS_DEBUG_LDAP) - if ((dn = ldap_get_dn(ld, entry)) == NULL) + if ((dn = ldap_get_dn(ld, entry)) == NULL) { + int optrc = ldap_get_option(ld, LDAP_OPT_RESULT_CODE, rc); + if (optrc != LDAP_OPT_SUCCESS) + *rc = optrc; debug_return_str(NULL); - if (ldap_str2dn(dn, &tmpDN, LDAP_DN_FORMAT_LDAP) == LDAP_SUCCESS) { + } + *rc = ldap_str2dn(dn, &tmpDN, LDAP_DN_FORMAT_LDAP); + if (*rc == LDAP_SUCCESS) { ldap_rdn2str(tmpDN[0], &rdn, LDAP_DN_FORMAT_UFN); ldap_dnfree(tmpDN); } @@ -382,11 +387,20 @@ char *dn, **edn; debug_decl(sudo_ldap_get_first_rdn, SUDOERS_DEBUG_LDAP) - if ((dn = ldap_get_dn(ld, entry)) == NULL) + if ((dn = ldap_get_dn(ld, entry)) == NULL) { + int optrc = ldap_get_option(ld, LDAP_OPT_RESULT_CODE, rc); + if (optrc != LDAP_OPT_SUCCESS) + *rc = optrc; debug_return_str(NULL); + } edn = ldap_explode_dn(dn, 1); ldap_memfree(dn); - debug_return_str(edn ? edn[0] : NULL); + if (edn == NULL) { + *rc = LDAP_NO_MEMORY; + debug_return_str(NULL); + } + *rc = LDAP_SUCCESS; + debug_return_str(edn[0]); #endif } @@ -405,13 +419,21 @@ bv = sudo_ldap_get_values_len(ld, entry, "sudoOption", &rc); if (bv == NULL) { - if (rc == LDAP_NO_MEMORY) + if (rc == LDAP_NO_MEMORY) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); debug_return_bool(false); + } debug_return_bool(true); } /* Use sudoRole in place of file name in defaults. */ - cn = sudo_ldap_get_first_rdn(ld, entry); + cn = sudo_ldap_get_first_rdn(ld, entry, &rc); + if (cn == NULL) { + if (rc == LDAP_NO_MEMORY) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + goto done; + } + } if (asprintf(&cp, "sudoRole %s", cn ? cn : "UNKNOWN") == -1) { sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); goto done; @@ -1133,6 +1155,88 @@ return *bv ? (*bv)->bv_val : NULL; } +/* + * Wrapper for sudo_ldap_role_to_priv() that takes an LDAPMessage. + * Returns a struct privilege on success or NULL on failure. + */ +static struct privilege * +ldap_entry_to_priv(LDAP *ld, LDAPMessage *entry, int *rc_out) +{ + struct berval **cmnds = NULL, **hosts = NULL; + struct berval **runasusers = NULL, **runasgroups = NULL; + struct berval **opts = NULL, **notbefore = NULL, **notafter = NULL; + struct privilege *priv = NULL; + char *cn = NULL; + int rc; + debug_decl(ldap_entry_to_priv, SUDOERS_DEBUG_LDAP); + + /* Ignore sudoRole without sudoCommand or sudoHost. */ + cmnds = sudo_ldap_get_values_len(ld, entry, "sudoCommand", &rc); + if (cmnds == NULL) + goto cleanup; + hosts = sudo_ldap_get_values_len(ld, entry, "sudoHost", &rc); + if (hosts == NULL) + goto cleanup; + + /* Get the entry's dn for long format printing. */ + if ((cn = sudo_ldap_get_first_rdn(ld, entry, &rc)) == NULL) + goto cleanup; + + /* Get sudoRunAsUser / sudoRunAsGroup */ + runasusers = sudo_ldap_get_values_len(ld, entry, "sudoRunAsUser", &rc); + if (runasusers == NULL) { + if (rc != LDAP_NO_MEMORY) + runasusers = sudo_ldap_get_values_len(ld, entry, "sudoRunAs", &rc); + if (rc == LDAP_NO_MEMORY) + goto cleanup; + } + runasgroups = sudo_ldap_get_values_len(ld, entry, "sudoRunAsGroup", &rc); + if (rc == LDAP_NO_MEMORY) + goto cleanup; + + /* Get sudoNotBefore / sudoNotAfter */ + notbefore = sudo_ldap_get_values_len(ld, entry, "sudoNotBefore", &rc); + if (rc == LDAP_NO_MEMORY) + goto cleanup; + notafter = sudo_ldap_get_values_len(ld, entry, "sudoNotAfter", &rc); + if (rc == LDAP_NO_MEMORY) + goto cleanup; + + /* Parse sudoOptions. */ + opts = sudo_ldap_get_values_len(ld, entry, "sudoOption", &rc); + if (rc == LDAP_NO_MEMORY) + goto cleanup; + + priv = sudo_ldap_role_to_priv(cn, hosts, runasusers, runasgroups, + cmnds, opts, notbefore ? notbefore[0]->bv_val : NULL, + notafter ? notafter[0]->bv_val : NULL, false, true, berval_iter); + if (priv == NULL) { + rc = LDAP_NO_MEMORY; + goto cleanup; + } + +cleanup: + if (cn != NULL) + ldap_memfree(cn); + if (cmnds != NULL) + ldap_value_free_len(cmnds); + if (hosts != NULL) + ldap_value_free_len(hosts); + if (runasusers != NULL) + ldap_value_free_len(runasusers); + if (runasgroups != NULL) + ldap_value_free_len(runasgroups); + if (opts != NULL) + ldap_value_free_len(opts); + if (notbefore != NULL) + ldap_value_free_len(notbefore); + if (notafter != NULL) + ldap_value_free_len(notafter); + + *rc_out = rc; + debug_return_ptr(priv); +} + static bool ldap_to_sudoers(LDAP *ld, struct ldap_result *lres, struct userspec_list *ldap_userspecs) @@ -1146,6 +1250,7 @@ /* We only have a single userspec */ if ((us = calloc(1, sizeof(*us))) == NULL) goto oom; + us->file = rcstr_dup("LDAP"); TAILQ_INIT(&us->users); TAILQ_INIT(&us->privileges); STAILQ_INIT(&us->comments); @@ -1157,81 +1262,16 @@ m->type = ALL; TAILQ_INSERT_TAIL(&us->users, m, entries); - /* Treat each sudoRole as a separate privilege. */ + /* Treat each entry as a separate privilege. */ for (i = 0; i < lres->nentries; i++) { - LDAPMessage *entry = lres->entries[i].entry; - struct berval **cmnds = NULL, **hosts = NULL; - struct berval **runasusers = NULL, **runasgroups = NULL; - struct berval **opts = NULL, **notbefore = NULL, **notafter = NULL; - struct privilege *priv = NULL; - char *cn = NULL; + struct privilege *priv; - /* Ignore sudoRole without sudoCommand. */ - cmnds = sudo_ldap_get_values_len(ld, entry, "sudoCommand", &rc); - if (cmnds == NULL) { + priv = ldap_entry_to_priv(ld, lres->entries[i].entry, &rc); + if (priv == NULL) { if (rc == LDAP_NO_MEMORY) - goto cleanup; + goto oom; continue; } - - /* Get the entry's dn for long format printing. */ - if ((cn = sudo_ldap_get_first_rdn(ld, entry)) == NULL) - goto cleanup; - - /* Get sudoHost */ - hosts = sudo_ldap_get_values_len(ld, entry, "sudoHost", &rc); - if (rc == LDAP_NO_MEMORY) - goto cleanup; - - /* Get sudoRunAsUser / sudoRunAsGroup */ - runasusers = sudo_ldap_get_values_len(ld, entry, "sudoRunAsUser", &rc); - if (runasusers == NULL) { - if (rc != LDAP_NO_MEMORY) - runasusers = sudo_ldap_get_values_len(ld, entry, "sudoRunAs", &rc); - if (rc == LDAP_NO_MEMORY) - goto cleanup; - } - runasgroups = sudo_ldap_get_values_len(ld, entry, "sudoRunAsGroup", &rc); - if (rc == LDAP_NO_MEMORY) - goto cleanup; - - /* Get sudoNotBefore / sudoNotAfter */ - notbefore = sudo_ldap_get_values_len(ld, entry, "sudoNotBefore", &rc); - if (rc == LDAP_NO_MEMORY) - goto cleanup; - notafter = sudo_ldap_get_values_len(ld, entry, "sudoNotAfter", &rc); - if (rc == LDAP_NO_MEMORY) - goto cleanup; - - /* Parse sudoOptions. */ - opts = sudo_ldap_get_values_len(ld, entry, "sudoOption", &rc); - if (rc == LDAP_NO_MEMORY) - goto cleanup; - - priv = sudo_ldap_role_to_priv(cn, hosts, runasusers, runasgroups, - cmnds, opts, notbefore ? notbefore[0]->bv_val : NULL, - notafter ? notafter[0]->bv_val : NULL, false, true, berval_iter); - - cleanup: - if (cn != NULL) - ldap_memfree(cn); - if (cmnds != NULL) - ldap_value_free_len(cmnds); - if (hosts != NULL) - ldap_value_free_len(hosts); - if (runasusers != NULL) - ldap_value_free_len(runasusers); - if (runasgroups != NULL) - ldap_value_free_len(runasgroups); - if (opts != NULL) - ldap_value_free_len(opts); - if (notbefore != NULL) - ldap_value_free_len(notbefore); - if (notafter != NULL) - ldap_value_free_len(notafter); - - if (priv == NULL) - goto oom; TAILQ_INSERT_TAIL(&us->privileges, priv, entries); } diff -urNa sudo-1.8.31p1/plugins/sudoers/parse.c sudo-1.8.31p2/plugins/sudoers/parse.c --- sudo-1.8.31p1/plugins/sudoers/parse.c Mon Oct 28 06:27:38 2019 +++ sudo-1.8.31p2/plugins/sudoers/parse.c Thu Jun 11 21:25:37 2020 @@ -169,7 +169,8 @@ *matching_cs = cs; *defs = &priv->defaults; sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, - "userspec matched @ %s:%d %s", us->file, us->lineno, + "userspec matched @ %s:%d %s", + us->file ? us->file : "???", us->lineno, cmnd_match ? "allowed" : "denied"); debug_return_int(cmnd_match); } diff -urNa sudo-1.8.31p1/plugins/sudoers/sssd.c sudo-1.8.31p2/plugins/sudoers/sssd.c --- sudo-1.8.31p1/plugins/sudoers/sssd.c Wed Dec 4 12:41:47 2019 +++ sudo-1.8.31p2/plugins/sudoers/sssd.c Thu Jun 11 21:25:37 2020 @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: ISC * - * Copyright (c) 2003-2018 Todd C. Miller + * Copyright (c) 2003-2020 Todd C. Miller * Copyright (c) 2011 Daniel Kopecek * * This code is derived from software contributed by Aaron Spangler. @@ -240,6 +240,126 @@ return *val_array; } +/* + * Wrapper for sudo_ldap_role_to_priv() that takes an sss rule.. + * Returns a struct privilege on success or NULL on failure. + */ +static struct privilege * +sss_rule_to_priv(struct sudo_sss_handle *handle, struct sss_sudo_rule *rule, + int *rc_out) +{ + char **cmnds, **runasusers = NULL, **runasgroups = NULL; + char **opts = NULL, **notbefore = NULL, **notafter = NULL; + char **hosts = NULL, **cn_array = NULL, *cn = NULL; + struct privilege *priv = NULL; + int rc; + debug_decl(sss_rule_to_priv, SUDOERS_DEBUG_SSSD); + + /* Ignore sudoRole without sudoCommand or sudoHost. */ + rc = handle->fn_get_values(rule, "sudoCommand", &cmnds); + if (rc != 0) + goto cleanup; + rc = handle->fn_get_values(rule, "sudoHost", &hosts); + if (rc != 0) + goto cleanup; + + /* Get the entry's dn for long format printing. */ + rc = handle->fn_get_values(rule, "cn", &cn_array); + if (rc != 0) + goto cleanup; + cn = cn_array[0]; + + /* Get sudoRunAsUser / sudoRunAs */ + rc = handle->fn_get_values(rule, "sudoRunAsUser", &runasusers); + switch (rc) { + case 0: + break; + case ENOENT: + rc = handle->fn_get_values(rule, "sudoRunAs", &runasusers); + switch (rc) { + case 0: + case ENOENT: + break; + default: + goto cleanup; + } + break; + default: + goto cleanup; + } + + /* Get sudoRunAsGroup */ + rc = handle->fn_get_values(rule, "sudoRunAsGroup", &runasgroups); + switch (rc) { + case 0: + case ENOENT: + break; + default: + goto cleanup; + } + + /* Get sudoNotBefore */ + rc = handle->fn_get_values(rule, "sudoNotBefore", ¬before); + switch (rc) { + case 0: + case ENOENT: + break; + default: + goto cleanup; + } + + /* Get sudoNotAfter */ + rc = handle->fn_get_values(rule, "sudoNotAfter", ¬after); + switch (rc) { + case 0: + case ENOENT: + break; + default: + goto cleanup; + } + + /* Parse sudoOptions. */ + rc = handle->fn_get_values(rule, "sudoOption", &opts); + switch (rc) { + case 0: + case ENOENT: + break; + default: + goto cleanup; + } + + priv = sudo_ldap_role_to_priv(cn, hosts, runasusers, runasgroups, + cmnds, opts, notbefore ? notbefore[0] : NULL, + notafter ? notafter[0] : NULL, false, true, val_array_iter); + if (priv == NULL) { + rc = ENOMEM; + goto cleanup; + } + rc = 0; + +cleanup: + if (cn_array != NULL) + handle->fn_free_values(cn_array); + if (cmnds != NULL) + handle->fn_free_values(cmnds); + if (hosts != NULL) + handle->fn_free_values(hosts); + if (runasusers != NULL) + handle->fn_free_values(runasusers); + if (runasgroups != NULL) + handle->fn_free_values(runasgroups); + if (opts != NULL) + handle->fn_free_values(opts); + if (notbefore != NULL) + handle->fn_free_values(notbefore); + if (notafter != NULL) + handle->fn_free_values(notafter); + + *rc_out = rc; + + debug_return_ptr(priv); +} + static bool sss_to_sudoers(struct sudo_sss_handle *handle, struct sss_sudo_result *sss_result) @@ -252,6 +372,7 @@ /* We only have a single userspec */ if ((us = calloc(1, sizeof(*us))) == NULL) goto oom; + us->file = rcstr_dup("SSSD"); TAILQ_INIT(&us->users); TAILQ_INIT(&us->privileges); STAILQ_INIT(&us->comments); @@ -264,7 +385,7 @@ TAILQ_INSERT_TAIL(&us->users, m, entries); /* - * Treat each sudoRole as a separate privilege. + * Treat each rule as a separate privilege. * * Sssd has already sorted the rules in descending order. * The conversion to a sudoers parse tree requires that entries be @@ -272,10 +393,8 @@ */ for (i = sss_result->num_rules; i-- > 0; ) { struct sss_sudo_rule *rule = sss_result->rules + i; - char **cmnds, **runasusers = NULL, **runasgroups = NULL; - char **opts = NULL, **notbefore = NULL, **notafter = NULL; - char **hosts = NULL, **cn_array = NULL, *cn = NULL; - struct privilege *priv = NULL; + struct privilege *priv; + int rc; /* * We don't know whether a rule was included due to a user/group @@ -284,113 +403,11 @@ if (!sudo_sss_check_user(handle, rule)) continue; - switch (handle->fn_get_values(rule, "sudoCommand", &cmnds)) { - case 0: - break; - case ENOENT: - /* Ignore sudoRole without sudoCommand. */ + if ((priv = sss_rule_to_priv(handle, rule, &rc)) == NULL) { + if (rc == ENOMEM) + goto oom; continue; - default: - goto cleanup; } - - /* Get the entry's dn for long format printing. */ - switch (handle->fn_get_values(rule, "cn", &cn_array)) { - case 0: - cn = cn_array[0]; - break; - case ENOENT: - break; - default: - goto cleanup; - } - - /* Get sudoHost */ - switch (handle->fn_get_values(rule, "sudoHost", &hosts)) { - case 0: - case ENOENT: - break; - default: - goto cleanup; - } - - /* Get sudoRunAsUser / sudoRunAs */ - switch (handle->fn_get_values(rule, "sudoRunAsUser", &runasusers)) { - case 0: - break; - case ENOENT: - switch (handle->fn_get_values(rule, "sudoRunAs", &runasusers)) { - case 0: - case ENOENT: - break; - default: - goto cleanup; - } - break; - default: - goto cleanup; - } - - /* Get sudoRunAsGroup */ - switch (handle->fn_get_values(rule, "sudoRunAsGroup", &runasgroups)) { - case 0: - case ENOENT: - break; - default: - goto cleanup; - } - - /* Get sudoNotBefore */ - switch (handle->fn_get_values(rule, "sudoNotBefore", ¬before)) { - case 0: - case ENOENT: - break; - default: - goto cleanup; - } - - /* Get sudoNotAfter */ - switch (handle->fn_get_values(rule, "sudoNotAfter", ¬after)) { - case 0: - case ENOENT: - break; - default: - goto cleanup; - } - - /* Parse sudoOptions. */ - switch (handle->fn_get_values(rule, "sudoOption", &opts)) { - case 0: - case ENOENT: - break; - default: - goto cleanup; - } - - priv = sudo_ldap_role_to_priv(cn, hosts, runasusers, runasgroups, - cmnds, opts, notbefore ? notbefore[0] : NULL, - notafter ? notafter[0] : NULL, false, true, val_array_iter); - - cleanup: - if (cn_array != NULL) - handle->fn_free_values(cn_array); - if (cmnds != NULL) - handle->fn_free_values(cmnds); - if (hosts != NULL) - handle->fn_free_values(hosts); - if (runasusers != NULL) - handle->fn_free_values(runasusers); - if (runasgroups != NULL) - handle->fn_free_values(runasgroups); - if (opts != NULL) - handle->fn_free_values(opts); - if (notbefore != NULL) - handle->fn_free_values(notbefore); - if (notafter != NULL) - handle->fn_free_values(notafter); - - if (priv == NULL) - goto oom; TAILQ_INSERT_TAIL(&us->privileges, priv, entries); } diff -urNa sudo-1.8.31p1/src/exec_pty.c sudo-1.8.31p2/src/exec_pty.c --- sudo-1.8.31p1/src/exec_pty.c Sun Nov 3 06:48:45 2019 +++ sudo-1.8.31p2/src/exec_pty.c Thu Jun 11 21:25:28 2020 @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: ISC * - * Copyright (c) 2009-2019 Todd C. Miller + * Copyright (c) 2009-2020 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -116,7 +116,7 @@ static void sync_ttysize(struct exec_closure_pty *ec); static int safe_close(int fd); static void ev_free_by_fd(struct sudo_event_base *evbase, int fd); -static void check_foreground(struct exec_closure_pty *ec); +static pid_t check_foreground(struct exec_closure_pty *ec); static void add_io_events(struct sudo_event_base *evbase); static void schedule_signal(struct exec_closure_pty *ec, int signo); @@ -456,22 +456,25 @@ /* * Check whether we are running in the foregroup. - * Updates the foreground global and does lazy init of the - * the pty slave as needed. + * Updates the foreground global and updates the window size. + * Returns 0 if there is no tty, the foreground process group ID + * on success, or -1 on failure (tty revoked). */ -static void +static pid_t check_foreground(struct exec_closure_pty *ec) { + int ret = 0; debug_decl(check_foreground, SUDO_DEBUG_EXEC); if (io_fds[SFD_USERTTY] != -1) { - foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ec->ppgrp; + if ((ret = tcgetpgrp(io_fds[SFD_USERTTY])) != -1) { + foreground = ret == ec->ppgrp; - /* Also check for window size changes. */ - sync_ttysize(ec); + /* Also check for window size changes. */ + sync_ttysize(ec); + } } - - debug_return; + debug_return_int(ret); } /* @@ -494,8 +497,12 @@ * If sudo is already the foreground process, just resume the command * in the foreground. If not, we'll suspend sudo and resume later. */ - if (!foreground) - check_foreground(ec); + if (!foreground) { + if (check_foreground(ec) == -1) { + /* User's tty was revoked. */ + break; + } + } if (foreground) { if (ttymode != TERM_RAW) { if (sudo_term_raw(io_fds[SFD_USERTTY], 0)) @@ -537,7 +544,10 @@ log_suspend(SIGCONT); /* Check foreground/background status on resume. */ - check_foreground(ec); + if (check_foreground(ec) == -1) { + /* User's tty was revoked. */ + break; + } /* * We always resume the command in the foreground if sudo itself @@ -872,6 +882,9 @@ char signame[SIG2STR_MAX]; debug_decl(schedule_signal, SUDO_DEBUG_EXEC) + if (signo == 0) + debug_return; + if (signo == SIGCONT_FG) strlcpy(signame, "CONT_FG", sizeof(signame)); else if (signo == SIGCONT_BG) @@ -1159,6 +1172,10 @@ ec->cols = user_details.ts_cols; TAILQ_INIT(&ec->monitor_messages); + /* Reset cstat for running the command. */ + cstat->type = CMD_INVALID; + cstat->val = 0; + /* Setup event base and events. */ ec->evbase = sudo_ev_base_alloc(); if (ec->evbase == NULL) @@ -1300,6 +1317,7 @@ bool interpose[3] = { false, false, false }; struct exec_closure_pty ec = { 0 }; struct plugin_container *plugin; + int evloop_retries = -1; sigset_t set, oset; struct sigaction sa; struct stat sb; @@ -1576,15 +1594,38 @@ /* * In the event loop we pass input from user tty to master * and pass output from master to stdout and IO plugin. + * Try to recover on ENXIO, it means the tty was revoked. */ add_io_events(ec.evbase); - if (sudo_ev_dispatch(ec.evbase) == -1) - sudo_warn(U_("error in event loop")); - if (sudo_ev_got_break(ec.evbase)) { - /* error from callback or monitor died */ - sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely"); - /* XXX - may need to terminate command if cmnd_pid != -1 */ - } + do { + if (sudo_ev_dispatch(ec.evbase) == -1) + sudo_warn(U_("error in event loop")); + if (sudo_ev_got_break(ec.evbase)) { + /* error from callback or monitor died */ + sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely"); + /* XXX: no good way to know if we should terminate the command. */ + if (cstat->val == CMD_INVALID && ec.cmnd_pid != -1) { + /* no status message, kill command */ + terminate_command(ec.cmnd_pid, true); + ec.cmnd_pid = -1; + /* TODO: need way to pass an error to the sudo front end */ + cstat->type = CMD_WSTATUS; + cstat->val = W_EXITCODE(1, SIGKILL); + } + } else if (!sudo_ev_got_exit(ec.evbase)) { + switch (errno) { + case ENXIO: + case EIO: + case EBADF: + /* /dev/tty was revoked, remove tty events and retry (once) */ + if (evloop_retries == -1 && io_fds[SFD_USERTTY] != -1) { + ev_free_by_fd(ec.evbase, io_fds[SFD_USERTTY]); + evloop_retries = 1; + } + break; + } + } + } while (evloop_retries-- > 0); /* Flush any remaining output, free I/O bufs and events, do logout. */ pty_finish(cstat); diff -urNa sudo-1.8.31p1/src/parse_args.c sudo-1.8.31p2/src/parse_args.c --- sudo-1.8.31p1/src/parse_args.c Mon Oct 28 06:28:54 2019 +++ sudo-1.8.31p2/src/parse_args.c Thu Jun 11 21:24:01 2020 @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: ISC * - * Copyright (c) 1993-1996, 1998-2017 Todd C. Miller + * Copyright (c) 1993-1996, 1998-2020 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -59,7 +59,7 @@ * Local functions. */ static void help(void) __attribute__((__noreturn__)); -static void usage_excl(int); +static void usage_excl(void) __attribute__((__noreturn__)); /* * Mapping of command line flags to name/value settings. @@ -226,7 +226,7 @@ (cp = strtok_r(NULL, ",", &last))) { if (strchr(cp, '=') != NULL) { sudo_warnx(U_("invalid environment variable name: %s"), cp); - usage(1); + usage(); } if ((val = getenv(cp)) != NULL) env_set(e, cp, val); @@ -249,15 +249,13 @@ int valid_flags = DEFAULT_VALID_FLAGS; int ch, i; char *cp; - const char *runas_user = NULL; - const char *runas_group = NULL; const char *progname; int proglen; debug_decl(parse_args, SUDO_DEBUG_ARGS) /* Is someone trying something funny? */ if (argc <= 0) - usage(1); + usage(); /* Pass progname to plugin so it can call initprogname() */ progname = getprogname(); @@ -313,7 +311,9 @@ case 'a': assert(optarg != NULL); if (*optarg == '\0') - usage(1); + usage(); + if (sudo_settings[ARG_BSDAUTH_TYPE].value != NULL) + usage(); sudo_settings[ARG_BSDAUTH_TYPE].value = optarg; break; #endif @@ -327,15 +327,19 @@ assert(optarg != NULL); if (sudo_strtonum(optarg, 3, INT_MAX, NULL) == 0) { sudo_warnx(U_("the argument to -C must be a number greater than or equal to 3")); - usage(1); + usage(); } + if (sudo_settings[ARG_CLOSEFROM].value != NULL) + usage(); sudo_settings[ARG_CLOSEFROM].value = optarg; break; #ifdef HAVE_LOGIN_CAP_H case 'c': assert(optarg != NULL); if (*optarg == '\0') - usage(1); + usage(); + if (sudo_settings[ARG_LOGIN_CLASS].value != NULL) + usage(); sudo_settings[ARG_LOGIN_CLASS].value = optarg; break; #endif @@ -352,12 +356,14 @@ sudo_settings[ARG_PRESERVE_ENVIRONMENT].value = "true"; SET(flags, MODE_PRESERVE_ENV); } else { + if (extra_env.env_len != 0) + usage(); parse_env_list(&extra_env, optarg); } break; case 'e': if (mode && mode != MODE_EDIT) - usage_excl(1); + usage_excl(); mode = MODE_EDIT; sudo_settings[ARG_SUDOEDIT].value = "true"; valid_flags = MODE_NONINTERACTIVE; @@ -365,8 +371,9 @@ case 'g': assert(optarg != NULL); if (*optarg == '\0') - usage(1); - runas_group = optarg; + usage(); + if (sudo_settings[ARG_RUNAS_GROUP].value != NULL) + usage(); sudo_settings[ARG_RUNAS_GROUP].value = optarg; break; case 'H': @@ -381,12 +388,14 @@ */ if (got_host_flag && !is_envar && argv[optind] != NULL && argv[optind][0] != '-') { + if (sudo_settings[ARG_REMOTE_HOST].value != NULL) + usage(); sudo_settings[ARG_REMOTE_HOST].value = argv[optind++]; continue; } if (mode && mode != MODE_HELP) { if (strcmp(progname, "sudoedit") != 0) - usage_excl(1); + usage_excl(); } mode = MODE_HELP; valid_flags = 0; @@ -396,7 +405,9 @@ case OPT_HOSTNAME: assert(optarg != NULL); if (*optarg == '\0') - usage(1); + usage(); + if (sudo_settings[ARG_REMOTE_HOST].value != NULL) + usage(); sudo_settings[ARG_REMOTE_HOST].value = optarg; break; case 'i': @@ -409,7 +420,7 @@ case 'K': sudo_settings[ARG_IGNORE_TICKET].value = "true"; if (mode && mode != MODE_KILL) - usage_excl(1); + usage_excl(); mode = MODE_KILL; valid_flags = 0; break; @@ -418,7 +429,7 @@ if (mode == MODE_LIST) SET(flags, MODE_LONG_LIST); else - usage_excl(1); + usage_excl(); } mode = MODE_LIST; valid_flags = MODE_NONINTERACTIVE|MODE_LONG_LIST; @@ -433,25 +444,33 @@ case 'p': /* An empty prompt is allowed. */ assert(optarg != NULL); + if (sudo_settings[ARG_PROMPT].value != NULL) + usage(); sudo_settings[ARG_PROMPT].value = optarg; break; #ifdef HAVE_SELINUX case 'r': assert(optarg != NULL); if (*optarg == '\0') - usage(1); + usage(); + if (sudo_settings[ARG_SELINUX_ROLE].value != NULL) + usage(); sudo_settings[ARG_SELINUX_ROLE].value = optarg; break; case 't': assert(optarg != NULL); if (*optarg == '\0') - usage(1); + usage(); + if (sudo_settings[ARG_SELINUX_TYPE].value != NULL) + usage(); sudo_settings[ARG_SELINUX_TYPE].value = optarg; break; #endif case 'T': /* Plugin determines whether empty timeout is allowed. */ assert(optarg != NULL); + if (sudo_settings[ARG_TIMEOUT].value != NULL) + usage(); sudo_settings[ARG_TIMEOUT].value = optarg; break; case 'S': @@ -463,31 +482,32 @@ break; case 'U': assert(optarg != NULL); - if (*optarg == '\0') - usage(1); + if (list_user != NULL || *optarg == '\0') + usage(); list_user = optarg; break; case 'u': assert(optarg != NULL); if (*optarg == '\0') - usage(1); - runas_user = optarg; + usage(); + if (sudo_settings[ARG_RUNAS_USER].value != NULL) + usage(); sudo_settings[ARG_RUNAS_USER].value = optarg; break; case 'v': if (mode && mode != MODE_VALIDATE) - usage_excl(1); + usage_excl(); mode = MODE_VALIDATE; valid_flags = MODE_NONINTERACTIVE; break; case 'V': if (mode && mode != MODE_VERSION) - usage_excl(1); + usage_excl(); mode = MODE_VERSION; valid_flags = 0; break; default: - usage(1); + usage(); } } else if (!got_end_of_args && is_envar) { /* Insert key=value pair, crank optind and resume getopt. */ @@ -521,39 +541,40 @@ if (ISSET(flags, MODE_LOGIN_SHELL)) { if (ISSET(flags, MODE_SHELL)) { sudo_warnx(U_("you may not specify both the `-i' and `-s' options")); - usage(1); + usage(); } if (ISSET(flags, MODE_PRESERVE_ENV)) { sudo_warnx(U_("you may not specify both the `-i' and `-E' options")); - usage(1); + usage(); } SET(flags, MODE_SHELL); } if ((flags & valid_flags) != flags) - usage(1); + usage(); if (mode == MODE_EDIT && (ISSET(flags, MODE_PRESERVE_ENV) || extra_env.env_len != 0)) { if (ISSET(mode, MODE_PRESERVE_ENV)) sudo_warnx(U_("the `-E' option is not valid in edit mode")); if (extra_env.env_len != 0) sudo_warnx(U_("you may not specify environment variables in edit mode")); - usage(1); + usage(); } - if ((runas_user != NULL || runas_group != NULL) && + if ((sudo_settings[ARG_RUNAS_USER].value != NULL || + sudo_settings[ARG_RUNAS_GROUP].value != NULL) && !ISSET(mode, MODE_EDIT | MODE_RUN | MODE_CHECK | MODE_VALIDATE)) { - usage(1); + usage(); } if (list_user != NULL && mode != MODE_LIST && mode != MODE_CHECK) { sudo_warnx(U_("the `-U' option may only be used with the `-l' option")); - usage(1); + usage(); } if (ISSET(tgetpass_flags, TGP_STDIN) && ISSET(tgetpass_flags, TGP_ASKPASS)) { sudo_warnx(U_("the `-A' and `-S' options may not be used together")); - usage(1); + usage(); } if ((argc == 0 && mode == MODE_EDIT) || (argc > 0 && !ISSET(mode, MODE_RUN | MODE_EDIT | MODE_CHECK))) - usage(1); + usage(); if (argc == 0 && mode == MODE_RUN && !ISSET(flags, MODE_SHELL)) { SET(flags, (MODE_IMPLIED_SHELL | MODE_SHELL)); sudo_settings[ARG_IMPLIED_SHELL].value = "true"; @@ -648,11 +669,11 @@ } /* - * Give usage message and exit. + * Display usage message. * The actual usage strings are in sudo_usage.h for configure substitution. */ -void -usage(int fatal) +static void +display_usage(int (*output)(const char *)) { struct sudo_lbuf lbuf; char *uvec[6]; @@ -678,27 +699,35 @@ * tty width. */ ulen = (int)strlen(getprogname()) + 8; - sudo_lbuf_init(&lbuf, fatal ? usage_err : usage_out, ulen, NULL, + sudo_lbuf_init(&lbuf, output, ulen, NULL, user_details.ts_cols); for (i = 0; uvec[i] != NULL; i++) { sudo_lbuf_append(&lbuf, "usage: %s%s", getprogname(), uvec[i]); sudo_lbuf_print(&lbuf); } sudo_lbuf_destroy(&lbuf); - if (fatal) - exit(1); } /* + * Display usage message and exit. + */ +void +usage(void) +{ + display_usage(usage_err); + exit(1); +} + +/* * Tell which options are mutually exclusive and exit. */ static void -usage_excl(int fatal) +usage_excl(void) { debug_decl(usage_excl, SUDO_DEBUG_ARGS) sudo_warnx(U_("Only one of the -e, -h, -i, -K, -l, -s, -v or -V options may be specified")); - usage(fatal); + usage(); } static void @@ -716,7 +745,7 @@ sudo_lbuf_append(&lbuf, _("%s - execute a command as another user\n\n"), pname); sudo_lbuf_print(&lbuf); - usage(0); + display_usage(usage_out); sudo_lbuf_append(&lbuf, _("\nOptions:\n")); sudo_lbuf_append(&lbuf, " -A, --askpass %s\n", diff -urNa sudo-1.8.31p1/src/sudo.c sudo-1.8.31p2/src/sudo.c --- sudo-1.8.31p1/src/sudo.c Mon Oct 28 06:28:54 2019 +++ sudo-1.8.31p2/src/sudo.c Fri Jun 12 07:14:53 2020 @@ -217,7 +217,7 @@ ok = policy_open(&policy_plugin, settings, user_info, envp); if (ok != 1) { if (ok == -2) - usage(1); + usage(); else sudo_fatalx(U_("unable to initialize policy plugin")); } @@ -255,7 +255,7 @@ sudo_debug_printf(SUDO_DEBUG_INFO, "policy plugin returns %d", ok); if (ok != 1) { if (ok == -2) - usage(1); + usage(); exit(EXIT_FAILURE); /* plugin printed error message */ } /* Reset nargv/nargc based on argv_out. */ @@ -276,7 +276,7 @@ iolog_unlink(plugin); break; case -2: - usage(1); + usage(); break; default: sudo_fatalx(U_("error initializing I/O plugin %s"), diff -urNa sudo-1.8.31p1/src/sudo.h sudo-1.8.31p2/src/sudo.h --- sudo-1.8.31p1/src/sudo.h Mon Oct 28 06:28:52 2019 +++ sudo-1.8.31p2/src/sudo.h Thu Jun 11 21:22:31 2020 @@ -232,7 +232,7 @@ int sudo_edit(struct command_details *details); /* parse_args.c */ -void usage(int); +void usage(void) __attribute__((__noreturn__)); /* openbsd.c */ int os_init_openbsd(int argc, char *argv[], char *envp[]);