Add runas_check_shell flag to require a runas user to have a valid shell.

Not enabled by default.
This commit is contained in:
Todd C. Miller
2019-12-09 19:29:45 -07:00
parent df8f06609c
commit b14d633ec6
16 changed files with 285 additions and 5 deletions

View File

@@ -129,6 +129,7 @@ lib/util/getgrouplist.c
lib/util/gethostname.c lib/util/gethostname.c
lib/util/getopt_long.c lib/util/getopt_long.c
lib/util/gettime.c lib/util/gettime.c
lib/util/getusershell.c
lib/util/gidlist.c lib/util/gidlist.c
lib/util/glob.c lib/util/glob.c
lib/util/host_port.c lib/util/host_port.c

View File

@@ -343,6 +343,9 @@
/* Define to 1 if you have the `getuserattr' function. */ /* Define to 1 if you have the `getuserattr' function. */
#undef HAVE_GETUSERATTR #undef HAVE_GETUSERATTR
/* Define to 1 if you have the `getusershell' function. */
#undef HAVE_GETUSERSHELL
/* Define to 1 if you have the `getutid' function. */ /* Define to 1 if you have the `getutid' function. */
#undef HAVE_GETUTID #undef HAVE_GETUTID

26
configure vendored
View File

@@ -19397,6 +19397,32 @@ esac
fi fi
done done
for ac_func in getusershell
do :
ac_fn_c_check_func "$LINENO" "getusershell" "ac_cv_func_getusershell"
if test "x$ac_cv_func_getusershell" = xyes; then :
cat >>confdefs.h <<_ACEOF
#define HAVE_GETUSERSHELL 1
_ACEOF
else
case " $LIBOBJS " in
*" getusershell.$ac_objext "* ) ;;
*) LIBOBJS="$LIBOBJS getusershell.$ac_objext"
;;
esac
for _sym in sudo_getusershell; do
COMPAT_EXP="${COMPAT_EXP}${_sym}
"
done
fi
done
for ac_func in reallocarray for ac_func in reallocarray
do : do :
ac_fn_c_check_func "$LINENO" "reallocarray" "ac_cv_func_reallocarray" ac_fn_c_check_func "$LINENO" "reallocarray" "ac_cv_func_reallocarray"

View File

@@ -3,7 +3,7 @@ dnl Use the top-level autogen.sh script to generate configure and config.h.in
dnl dnl
dnl SPDX-License-Identifier: ISC dnl SPDX-License-Identifier: ISC
dnl dnl
dnl Copyright (c) 1994-1996, 1998-2018 Todd C. Miller <Todd.Miller@sudo.ws> dnl Copyright (c) 1994-1996, 1998-2019 Todd C. Miller <Todd.Miller@sudo.ws>
dnl dnl
dnl Permission to use, copy, modify, and distribute this software for any dnl Permission to use, copy, modify, and distribute this software for any
dnl purpose with or without fee is hereby granted, provided that the above dnl purpose with or without fee is hereby granted, provided that the above
@@ -2565,6 +2565,10 @@ AC_CHECK_FUNCS([getdelim], [], [
SUDO_APPEND_COMPAT_EXP(sudo_getdelim) SUDO_APPEND_COMPAT_EXP(sudo_getdelim)
COMPAT_TEST_PROGS="${COMPAT_TEST_PROGS}${COMPAT_TEST_PROGS+ }getdelim_test" COMPAT_TEST_PROGS="${COMPAT_TEST_PROGS}${COMPAT_TEST_PROGS+ }getdelim_test"
]) ])
AC_CHECK_FUNCS([getusershell], [], [
AC_LIBOBJ(getusershell)
SUDO_APPEND_COMPAT_EXP(sudo_getusershell)
])
AC_CHECK_FUNCS([reallocarray], [], [ AC_CHECK_FUNCS([reallocarray], [], [
AC_LIBOBJ(reallocarray) AC_LIBOBJ(reallocarray)
SUDO_APPEND_COMPAT_EXP(sudo_reallocarray) SUDO_APPEND_COMPAT_EXP(sudo_reallocarray)

View File

@@ -25,7 +25,7 @@
.nr BA @BAMAN@ .nr BA @BAMAN@
.nr LC @LCMAN@ .nr LC @LCMAN@
.nr PS @PSMAN@ .nr PS @PSMAN@
.TH "SUDOERS" "@mansectform@" "December 8, 2019" "Sudo @PACKAGE_VERSION@" "File Formats Manual" .TH "SUDOERS" "@mansectform@" "December 9, 2019" "Sudo @PACKAGE_VERSION@" "File Formats Manual"
.nh .nh
.if n .ad l .if n .ad l
.SH "NAME" .SH "NAME"
@@ -2969,6 +2969,28 @@ Older versions of
\fBsudo\fR \fBsudo\fR
always allowed matching of unknown user and group IDs. always allowed matching of unknown user and group IDs.
.TP 18n .TP 18n
runas_check_shell
.br
If enabled,
\fBsudo\fR
will only run commands as a user whose shell appears in the
\fI/etc/shells\fR
file, even if the invoking user's
\fRRunas_List\fR
would otherwise permit it.
If no
\fI/etc/shells\fR
file is present, a system-dependent list of built-in default shells is used.
On many operating systems, system users such as
\(lqbin\(rq,
do not have a valid shell and this flag can be used to prevent
commands from being run as those users.
This flag is
\fIoff\fR
by default.
.sp
This setting is only supported by version 1.8.30 or higher.
.TP 18n
runaspw runaspw
If set, If set,
\fBsudo\fR \fBsudo\fR

View File

@@ -24,7 +24,7 @@
.nr BA @BAMAN@ .nr BA @BAMAN@
.nr LC @LCMAN@ .nr LC @LCMAN@
.nr PS @PSMAN@ .nr PS @PSMAN@
.Dd December 8, 2019 .Dd December 9, 2019
.Dt SUDOERS @mansectform@ .Dt SUDOERS @mansectform@
.Os Sudo @PACKAGE_VERSION@ .Os Sudo @PACKAGE_VERSION@
.Sh NAME .Sh NAME
@@ -2794,6 +2794,26 @@ This setting is only supported by version 1.8.30 or higher.
Older versions of Older versions of
.Nm sudo .Nm sudo
always allowed matching of unknown user and group IDs. always allowed matching of unknown user and group IDs.
.It runas_check_shell
If enabled,
.Nm sudo
will only run commands as a user whose shell appears in the
.Pa /etc/shells
file, even if the invoking user's
.Li Runas_List
would otherwise permit it.
If no
.Pa /etc/shells
file is present, a system-dependent list of built-in default shells is used.
On many operating systems, system users such as
.Dq bin ,
do not have a valid shell and this flag can be used to prevent
commands from being run as those users.
This flag is
.Em off
by default.
.Pp
This setting is only supported by version 1.8.30 or higher.
.It runaspw .It runaspw
If set, If set,
.Nm sudo .Nm sudo

View File

@@ -413,6 +413,17 @@ __dso_public ssize_t sudo_getdelim(char **bufp, size_t *bufsizep, int delim, FIL
# undef getdelim # undef getdelim
# define getdelim(_a, _b, _c, _d) sudo_getdelim((_a), (_b), (_c), (_d)) # define getdelim(_a, _b, _c, _d) sudo_getdelim((_a), (_b), (_c), (_d))
#endif /* HAVE_GETDELIM */ #endif /* HAVE_GETDELIM */
#ifndef HAVE_GETUSERSHELL
__dso_public char *sudo_getusershell(void);
# undef getusershell
# define getusershell() sudo_getusershell()
__dso_public void sudo_setusershell(void);
# undef setusershell
# define setusershell() sudo_setusershell()
__dso_public void sudo_endusershell(void);
# undef endusershell
# define endusershell() sudo_endusershell()
#endif /* HAVE_GETUSERSHELL */
#ifndef HAVE_UTIMENSAT #ifndef HAVE_UTIMENSAT
__dso_public int sudo_utimensat(int fd, const char *file, const struct timespec *times, int flag); __dso_public int sudo_utimensat(int fd, const char *file, const struct timespec *times, int flag);
# undef utimensat # undef utimensat

View File

@@ -706,6 +706,18 @@ gettime.i: $(srcdir)/gettime.c $(incdir)/compat/stdbool.h \
$(CC) -E -o $@ $(CPPFLAGS) $< $(CC) -E -o $@ $(CPPFLAGS) $<
gettime.plog: gettime.i gettime.plog: gettime.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/gettime.c --i-file $< --output-file $@ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/gettime.c --i-file $< --output-file $@
getusershell.lo: $(srcdir)/getusershell.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \
$(incdir)/sudo_util.h $(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/getusershell.c
getusershell.i: $(srcdir)/getusershell.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \
$(incdir)/sudo_util.h $(top_builddir)/config.h
$(CC) -E -o $@ $(CPPFLAGS) $<
getusershell.plog: getusershell.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getusershell.c --i-file $< --output-file $@
gidlist.lo: $(srcdir)/gidlist.c $(incdir)/compat/stdbool.h \ gidlist.lo: $(srcdir)/gidlist.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \

138
lib/util/getusershell.c Normal file
View File

@@ -0,0 +1,138 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2019 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
*/
#include <config.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#define DEFAULT_TEXT_DOMAIN "sudo"
#include "sudo_gettext.h" /* must be included before sudo_compat.h */
#include "sudo_compat.h"
#include "sudo_debug.h"
#include "sudo_util.h"
static char **allowed_shells, **current_shell;
static char *default_shells[] = {
"/bin/sh",
"/bin/ksh",
"/bin/ksh93",
"/bin/bash",
"/bin/dash",
"/bin/zsh",
"/bin/csh",
"/bin/tcsh",
NULL
};
static char **
read_shells(void)
{
size_t maxshells = 16, nshells = 0;
size_t linesize = 0;
char *line = NULL;
FILE *fp;
debug_decl(read_shells, SUDO_DEBUG_UTIL)
if ((fp = fopen("/etc/shells", "r")) == NULL)
goto bad;
free(allowed_shells);
allowed_shells = reallocarray(NULL, maxshells, sizeof(char *));
if (allowed_shells == NULL)
goto bad;
while (sudo_parseln(&line, &linesize, NULL, fp, PARSELN_CONT_IGN) != -1) {
if (nshells + 1 >= maxshells) {
char **new_shells;
new_shells = reallocarray(NULL, maxshells + 16, sizeof(char *));
if (new_shells == NULL)
goto bad;
allowed_shells = new_shells;
maxshells += 16;
}
if ((allowed_shells[nshells] = strdup(line)) == NULL)
goto bad;
nshells++;
}
allowed_shells[nshells] = NULL;
free(line);
fclose(fp);
debug_return_ptr(allowed_shells);
bad:
free(line);
if (fp != NULL)
fclose(fp);
while (nshells != 0)
free(allowed_shells[--nshells]);
free(allowed_shells);
allowed_shells = NULL;
debug_return_ptr(default_shells);
}
void
sudo_setusershell(void)
{
debug_decl(setusershell, SUDO_DEBUG_UTIL)
current_shell = read_shells();
debug_return;
}
void
sudo_endusershell(void)
{
debug_decl(endusershell, SUDO_DEBUG_UTIL)
if (allowed_shells != NULL) {
char **shell;
for (shell = allowed_shells; *shell != NULL; shell++)
free(*shell);
free(allowed_shells);
allowed_shells = NULL;
}
current_shell = NULL;
debug_return;
}
char *
sudo_getusershell(void)
{
debug_decl(getusershell, SUDO_DEBUG_UTIL)
if (current_shell == NULL)
current_shell = read_shells();
debug_return_str(*current_shell++);
}

View File

@@ -116,7 +116,7 @@ sub mkdep {
# XXX - fill in AUTH_OBJS from contents of the auth dir instead # XXX - fill in AUTH_OBJS from contents of the auth dir instead
$makefile =~ s:\@AUTH_OBJS\@:afs.lo aix_auth.lo bsdauth.lo dce.lo fwtk.lo getspwuid.lo kerb5.lo pam.lo passwd.lo rfc1938.lo secureware.lo securid5.lo sia.lo:; $makefile =~ s:\@AUTH_OBJS\@:afs.lo aix_auth.lo bsdauth.lo dce.lo fwtk.lo getspwuid.lo kerb5.lo pam.lo passwd.lo rfc1938.lo secureware.lo securid5.lo sia.lo:;
$makefile =~ s:\@DIGEST\@:digest.lo digest_openssl.lo digest_gcrypt.lo:; $makefile =~ s:\@DIGEST\@:digest.lo digest_openssl.lo digest_gcrypt.lo:;
$makefile =~ s:\@LTLIBOBJS\@:arc4random.lo arc4random_uniform.lo closefrom.lo dup3.lo fchmodat.lo fstatat.lo fnmatch.lo getaddrinfo.lo getcwd.lo getentropy.lo getgrouplist.lo getdelim.lo getopt_long.lo glob.lo inet_ntop_lo inet_pton.lo isblank.lo memrchr.lo memset_s.lo mksiglist.lo mksigname.lo mktemp.lo nanosleep.lo openat.lo pipe2.lo pw_dup.lo reallocarray.lo sha2.lo sig2str.lo siglist.lo signame.lo snprintf.lo str2sig.lo strlcat.lo strlcpy.lo strndup.lo strnlen.lo strsignal.lo unlinkat.lo utimens.lo vsyslog.lo:; $makefile =~ s:\@LTLIBOBJS\@:arc4random.lo arc4random_uniform.lo closefrom.lo dup3.lo fchmodat.lo fstatat.lo fnmatch.lo getaddrinfo.lo getcwd.lo getentropy.lo getgrouplist.lo getdelim.lo getopt_long.lo getusershell.lo glob.lo inet_ntop_lo inet_pton.lo isblank.lo memrchr.lo memset_s.lo mksiglist.lo mksigname.lo mktemp.lo nanosleep.lo openat.lo pipe2.lo pw_dup.lo reallocarray.lo sha2.lo sig2str.lo siglist.lo signame.lo snprintf.lo str2sig.lo strlcat.lo strlcpy.lo strndup.lo strnlen.lo strsignal.lo unlinkat.lo utimens.lo vsyslog.lo:;
# Parse OBJS lines # Parse OBJS lines
my %objs; my %objs;

View File

@@ -328,3 +328,28 @@ get_authpw(int mode)
debug_return_ptr(pw); debug_return_ptr(pw);
} }
/*
* Returns true if the specified shell is allowed by /etc/shells, else false.
*/
bool
check_user_shell(const struct passwd *pw)
{
const char *shell;
debug_decl(check_user_shell, SUDOERS_DEBUG_AUTH)
if (!def_runas_check_shell)
debug_return_bool(true);
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s: checking /etc/shells for %s", __func__, pw->pw_shell);
setusershell();
while ((shell = getusershell()) != NULL) {
if (strcmp(shell, pw->pw_shell) == 0)
debug_return_bool(true);
}
endusershell();
debug_return_bool(false);
}

View File

@@ -529,6 +529,10 @@ struct sudo_defs_types sudo_defs_table[] = {
"runas_allow_unknown_id", T_FLAG, "runas_allow_unknown_id", T_FLAG,
N_("Allow the use of unknown runas user and/or group ID"), N_("Allow the use of unknown runas user and/or group ID"),
NULL, NULL,
}, {
"runas_check_shell", T_FLAG,
N_("Only permit running commands as a user with a valid shell"),
NULL,
}, { }, {
NULL, 0, NULL NULL, 0, NULL
} }

View File

@@ -244,6 +244,8 @@
#define def_log_server_peer_key (sudo_defs_table[I_LOG_SERVER_PEER_KEY].sd_un.str) #define def_log_server_peer_key (sudo_defs_table[I_LOG_SERVER_PEER_KEY].sd_un.str)
#define I_RUNAS_ALLOW_UNKNOWN_ID 122 #define I_RUNAS_ALLOW_UNKNOWN_ID 122
#define def_runas_allow_unknown_id (sudo_defs_table[I_RUNAS_ALLOW_UNKNOWN_ID].sd_un.flag) #define def_runas_allow_unknown_id (sudo_defs_table[I_RUNAS_ALLOW_UNKNOWN_ID].sd_un.flag)
#define I_RUNAS_CHECK_SHELL 123
#define def_runas_check_shell (sudo_defs_table[I_RUNAS_CHECK_SHELL].sd_un.flag)
enum def_tuple { enum def_tuple {
never, never,

View File

@@ -384,3 +384,7 @@ log_server_peer_key
runas_allow_unknown_id runas_allow_unknown_id
T_FLAG T_FLAG
"Allow the use of unknown runas user and/or group ID" "Allow the use of unknown runas user and/or group ID"
runas_check_shell
T_FLAG
"Only permit running commands as a user with a valid shell"

View File

@@ -434,6 +434,13 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
goto bad; goto bad;
} }
/* Check runas user's shell. */
if (!check_user_shell(runas_pw)) {
log_warningx(SLOG_RAW_MSG, N_("invalid shell for user %s: %s"),
runas_pw->pw_name, runas_pw->pw_shell);
goto bad;
}
/* /*
* We don't reset the environment for sudoedit or if the user * We don't reset the environment for sudoedit or if the user
* specified the -E command line flag and they have setenv privs. * specified the -E command line flag and they have setenv privs.

View File

@@ -256,6 +256,7 @@ int find_path(const char *infile, char **outfile, struct stat *sbp,
/* check.c */ /* check.c */
int check_user(int validate, int mode); int check_user(int validate, int mode);
bool check_user_shell(const struct passwd *pw);
bool user_is_exempt(void); bool user_is_exempt(void);
/* prompt.c */ /* prompt.c */