Add support for command timeouts in sudoers. After the timeout,

the command will be terminated.
This commit is contained in:
Todd C. Miller
2017-02-14 15:56:34 -07:00
parent 4f9dcd7264
commit 3980f1531b
31 changed files with 2367 additions and 1749 deletions

View File

@@ -407,6 +407,14 @@ plugins/sudoers/regress/sudoers/test16.in
plugins/sudoers/regress/sudoers/test16.json.ok
plugins/sudoers/regress/sudoers/test16.out.ok
plugins/sudoers/regress/sudoers/test16.toke.ok
plugins/sudoers/regress/sudoers/test17.in
plugins/sudoers/regress/sudoers/test17.json.ok
plugins/sudoers/regress/sudoers/test17.out.ok
plugins/sudoers/regress/sudoers/test17.toke.ok
plugins/sudoers/regress/sudoers/test18.in
plugins/sudoers/regress/sudoers/test18.json.ok
plugins/sudoers/regress/sudoers/test18.out.ok
plugins/sudoers/regress/sudoers/test18.toke.ok
plugins/sudoers/regress/sudoers/test2.in
plugins/sudoers/regress/sudoers/test2.json.ok
plugins/sudoers/regress/sudoers/test2.out.ok
@@ -493,6 +501,7 @@ plugins/sudoers/sudoers_debug.h
plugins/sudoers/sudoers_version.h
plugins/sudoers/sudoreplay.c
plugins/sudoers/testsudoers.c
plugins/sudoers/timeout.c
plugins/sudoers/timestamp.c
plugins/sudoers/timestr.c
plugins/sudoers/toke.c

View File

@@ -95,9 +95,9 @@ DDEESSCCRRIIPPTTIIOONN
was used to authenticate, the terminal session ID, and a time stamp
(using a monotonic clock if one is available). The user may then use
ssuuddoo without a password for a short period of time (5 minutes unless
overridden by the _t_i_m_e_o_u_t option). By default, ssuuddooeerrss uses a separate
record for each tty, which means that a user's login sessions are
authenticated separately. The _t_t_y___t_i_c_k_e_t_s option can be disabled to
overridden by the _t_i_m_e_s_t_a_m_p___t_i_m_e_o_u_t option). By default, ssuuddooeerrss uses a
separate record for each tty, which means that a user's login sessions
are authenticated separately. The _t_t_y___t_i_c_k_e_t_s option can be disabled to
force the use of a single time stamp for all of a user's sessions.
LLooggggiinngg
@@ -478,12 +478,15 @@ SSUUDDOOEERRSS FFIILLEE FFOORRMMAATT
Runas_Spec ::= '(' Runas_List? (':' Runas_List)? ')'
Option_Spec ::= (SELinux_Spec | Solaris_Priv_Spec | Tag_Spec)
Option_Spec ::= (SELinux_Spec | Solaris_Priv_Spec | Timeout_Spec |
Tag_Spec)
SELinux_Spec ::= ('ROLE=role' | 'TYPE=type')
Solaris_Priv_Spec ::= ('PRIVS=privset' | 'LIMITPRIVS=privset')
Timeout_Spec ::= 'TIMEOUT=timeout'
Tag_Spec ::= ('EXEC:' | 'NOEXEC:' | 'FOLLOW:' | 'NOFOLLOW' |
'LOG_INPUT:' | 'NOLOG_INPUT:' | 'LOG_OUTPUT:' |
'NOLOG_OUTPUT:' | 'MAIL:' | 'NOMAIL:' | 'PASSWD:' |
@@ -605,6 +608,16 @@ SSUUDDOOEERRSS FFIILLEE FFOORRMMAATT
Privileges can be excluded from a set by prefixing the privilege name
with either an `!' or `-' character.
TTiimmeeoouutt__SSppeecc
A command may have a timeout associated with it. If the timeout expires
before the command has exited, the command will be terminated. The
timeout may be specified in combinations of days, hours, minutes and
seconds. For example, a timeout of 7 days, 8 hours, 30 minutes and 10
seconds would be written as 7d8h30m10s where the letter following a
number indicates the unit of time. Any of the days, minutes, hours or
seconds may be omitted. If a number is specified without a unit, seconds
is assumed.
TTaagg__SSppeecc
A command may have zero or more tags associated with it. The following
tag values are supported: EXEC, NOEXEC, FOLLOW, NOFOLLOW, LOG_INPUT,
@@ -1422,6 +1435,13 @@ SSUUDDOOEERRSS OOPPTTIIOONNSS
file descriptor at which to start closing. The default
is 3.
command_timeout The maximum amount of time a command is allowed to run
before it is terminated. See the Timeout_Spec section
for a description of the timeout syntax.
This setting is only supported by version 1.8.19 or
higher.
maxseq The maximum sequence number that will be substituted
for the ``%{seq}'' escape in the I/O log file (see the
_i_o_l_o_g___d_i_r description above for more information).

View File

@@ -224,7 +224,7 @@ The user may then use
without a password for a short period of time
(\fR@timeout@\fR
minutes unless overridden by the
\fItimeout\fR
\fItimestamp_timeout\fR
option)
\&.
By default,
@@ -986,12 +986,15 @@ Cmnd_Spec ::= Runas_Spec? Option_Spec* Cmnd
Runas_Spec ::= '(' Runas_List? (':' Runas_List)? ')'
Option_Spec ::= (SELinux_Spec | Solaris_Priv_Spec | Tag_Spec)
Option_Spec ::= (SELinux_Spec | Solaris_Priv_Spec | Timeout_Spec |
Tag_Spec)
SELinux_Spec ::= ('ROLE=role' | 'TYPE=type')
Solaris_Priv_Spec ::= ('PRIVS=privset' | 'LIMITPRIVS=privset')
Timeout_Spec ::= 'TIMEOUT=timeout'
Tag_Spec ::= ('EXEC:' | 'NOEXEC:' | 'FOLLOW:' | 'NOFOLLOW' |
'LOG_INPUT:' | 'NOLOG_INPUT:' | 'LOG_OUTPUT:' |
'NOLOG_OUTPUT:' | 'MAIL:' | 'NOMAIL:' | 'PASSWD:' |
@@ -1253,6 +1256,18 @@ name with either an
or
\(oq\-\(cq
character.
.SS "Timeout_Spec"
A command may have a timeout associated with it.
If the timeout expires before the command has exited, the
command will be terminated.
The timeout may be specified in combinations of days, hours,
minutes and seconds.
For example, a timeout of 7 days, 8 hours, 30 minutes and
10 seconds would be written as
\fR7d8h30m10s\fR
where the letter following a number indicates the unit of time.
Any of the days, minutes, hours or seconds may be omitted.
If a number is specified without a unit, seconds is assumed.
.SS "Tag_Spec"
A command may have zero or more tags associated with it.
The following tag values are supported:
@@ -2988,6 +3003,15 @@ to start closing.
The default is
\fR3\fR.
.TP 18n
command_timeout
The maximum amount of time a command is allowed to run before
it is terminated.
See the
\fRTimeout_Spec\fR
section for a description of the timeout syntax.
.sp
This setting is only supported by version 1.8.19 or higher.
.TP 18n
maxseq
The maximum sequence number that will be substituted for the
\(Lq\fR%{seq}\fR\(Rq

View File

@@ -213,7 +213,7 @@ without a password for a short period of time
.Po
.Li @timeout@
minutes unless overridden by the
.Em timeout
.Em timestamp_timeout
option
.Pc .
By default,
@@ -939,12 +939,15 @@ Cmnd_Spec ::= Runas_Spec? Option_Spec* Cmnd
Runas_Spec ::= '(' Runas_List? (':' Runas_List)? ')'
Option_Spec ::= (SELinux_Spec | Solaris_Priv_Spec | Tag_Spec)
Option_Spec ::= (SELinux_Spec | Solaris_Priv_Spec | Timeout_Spec |
Tag_Spec)
SELinux_Spec ::= ('ROLE=role' | 'TYPE=type')
Solaris_Priv_Spec ::= ('PRIVS=privset' | 'LIMITPRIVS=privset')
Timeout_Spec ::= 'TIMEOUT=timeout'
Tag_Spec ::= ('EXEC:' | 'NOEXEC:' | 'FOLLOW:' | 'NOFOLLOW' |
'LOG_INPUT:' | 'NOLOG_INPUT:' | 'LOG_OUTPUT:' |
'NOLOG_OUTPUT:' | 'MAIL:' | 'NOMAIL:' | 'PASSWD:' |
@@ -1176,6 +1179,18 @@ name with either an
or
.Ql \-
character.
.Ss Timeout_Spec
A command may have a timeout associated with it.
If the timeout expires before the command has exited, the
command will be terminated.
The timeout may be specified in combinations of days, hours,
minutes and seconds.
For example, a timeout of 7 days, 8 hours, 30 minutes and
10 seconds would be written as
.Li 7d8h30m10s
where the letter following a number indicates the unit of time.
Any of the days, minutes, hours or seconds may be omitted.
If a number is specified without a unit, seconds is assumed.
.Ss Tag_Spec
A command may have zero or more tags associated with it.
The following tag values are supported:
@@ -2804,6 +2819,14 @@ option can be used to specify a different file descriptor at which
to start closing.
The default is
.Li 3 .
.It command_timeout
The maximum amount of time a command is allowed to run before
it is terminated.
See the
.Li Timeout_Spec
section for a description of the timeout syntax.
.Pp
This setting is only supported by version 1.8.20 or higher.
.It maxseq
The maximum sequence number that will be substituted for the
.Dq Li %{seq}

View File

@@ -152,8 +152,8 @@ AUTH_OBJS = sudo_auth.lo @AUTH_OBJS@
LIBPARSESUDOERS_OBJS = alias.lo audit.lo base64.lo defaults.lo hexchar.lo \
gram.lo match.lo match_addr.lo pwutil.lo pwutil_impl.lo \
rcstr.lo redblack.lo sudoers_debug.lo timestr.lo \
toke.lo toke_util.lo
rcstr.lo redblack.lo sudoers_debug.lo timeout.lo \
timestr.lo toke.lo toke_util.lo
SUDOERS_OBJS = $(AUTH_OBJS) boottime.lo check.lo editor.lo env.lo find_path.lo \
gc.lo goodpath.lo group_plugin.lo interfaces.lo iolog.lo \
@@ -1077,6 +1077,16 @@ testsudoers.o: $(srcdir)/testsudoers.c $(devdir)/def_data.h $(devdir)/gram.h \
$(srcdir)/sudoers_debug.h $(srcdir)/tsgetgrpw.h \
$(top_builddir)/config.h $(top_builddir)/pathnames.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/testsudoers.c
timeout.lo: $(srcdir)/timeout.c $(devdir)/def_data.h \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(incdir)/sudo_util.h $(srcdir)/defaults.h $(srcdir)/logging.h \
$(srcdir)/parse.h $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \
$(srcdir)/sudoers_debug.h $(top_builddir)/config.h \
$(top_builddir)/pathnames.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/timeout.c
timestamp.lo: $(srcdir)/timestamp.c $(devdir)/def_data.h \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \

View File

@@ -449,6 +449,10 @@ struct sudo_defs_types sudo_defs_table[] = {
"ignore_unknown_defaults", T_FLAG,
N_("Ignore unknown Defaults entries in sudoers instead of producing a warning"),
NULL,
}, {
"command_timeout", T_TIMEOUT|T_BOOL,
N_("Time in seconds after which the command will be terminated: %u"),
NULL,
}, {
NULL, 0, NULL
}

View File

@@ -208,6 +208,8 @@
#define def_fdexec (sudo_defs_table[I_FDEXEC].sd_un.tuple)
#define I_IGNORE_UNKNOWN_DEFAULTS 104
#define def_ignore_unknown_defaults (sudo_defs_table[I_IGNORE_UNKNOWN_DEFAULTS].sd_un.flag)
#define I_COMMAND_TIMEOUT 105
#define def_command_timeout (sudo_defs_table[I_COMMAND_TIMEOUT].sd_un.ival)
enum def_tuple {
never,

View File

@@ -329,3 +329,6 @@ fdexec
ignore_unknown_defaults
T_FLAG
"Ignore unknown Defaults entries in sudoers instead of producing a warning"
command_timeout
T_TIMEOUT|T_BOOL
"Time in seconds after which the command will be terminated: %u"

View File

@@ -101,6 +101,7 @@ static bool store_mode(const char *str, union sudo_defs_val *sd_un);
static int store_str(const char *str, union sudo_defs_val *sd_un);
static bool store_syslogfac(const char *str, union sudo_defs_val *sd_un);
static bool store_syslogpri(const char *str, union sudo_defs_val *sd_un);
static bool store_timeout(const char *str, union sudo_defs_val *sd_un);
static bool store_tuple(const char *str, union sudo_defs_val *sd_un, struct def_values *tuple_vals);
static bool store_uint(const char *str, union sudo_defs_val *sd_un);
static bool store_float(const char *str, union sudo_defs_val *sd_un);
@@ -178,6 +179,13 @@ dump_defaults(void)
}
}
break;
case T_TIMEOUT:
if (cur->sd_un.ival) {
sudo_printf(SUDO_CONV_INFO_MSG, desc,
cur->sd_un.ival);
sudo_printf(SUDO_CONV_INFO_MSG, "\n");
}
break;
case T_TUPLE:
for (def = cur->values; def->sval; def++) {
if (cur->sd_un.tuple == def->nval) {
@@ -301,6 +309,9 @@ parse_default_entry(struct sudo_defs_types *def, const char *val, int op,
case T_LIST:
rc = store_list(val, sd_un, op);
break;
case T_TIMEOUT:
rc = store_timeout(val, sd_un);
break;
case T_TUPLE:
rc = store_tuple(val, sd_un, def->values);
break;
@@ -986,6 +997,25 @@ store_mode(const char *str, union sudo_defs_val *sd_un)
debug_return_bool(true);
}
static bool
store_timeout(const char *str, union sudo_defs_val *sd_un)
{
debug_decl(store_mode, SUDOERS_DEBUG_DEFAULTS)
if (str == NULL) {
sd_un->ival = 0;
} else {
int seconds = parse_timeout(str);
if (seconds == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
"%s", str);
debug_return_bool(false);
}
sd_un->ival = seconds;
}
debug_return_bool(true);
}
static bool
list_op(const char *str, size_t len, union sudo_defs_val *sd_un,
enum list_ops op)

View File

@@ -99,6 +99,8 @@ struct early_default {
#define T_TUPLE 0x009
#undef T_FLOAT
#define T_FLOAT 0x010
#undef T_TIMEOUT
#define T_TIMEOUT 0x020
#undef T_MASK
#define T_MASK 0x0FF
#undef T_BOOL

File diff suppressed because it is too large Load Diff

View File

@@ -36,11 +36,12 @@
#define ROLE 292
#define PRIVS 293
#define LIMITPRIVS 294
#define MYSELF 295
#define SHA224_TOK 296
#define SHA256_TOK 297
#define SHA384_TOK 298
#define SHA512_TOK 299
#define CMND_TIMEOUT 295
#define MYSELF 296
#define SHA224_TOK 297
#define SHA256_TOK 298
#define SHA384_TOK 299
#define SHA512_TOK 300
#ifndef YYSTYPE_DEFINED
#define YYSTYPE_DEFINED
typedef union {

View File

@@ -1,6 +1,6 @@
%{
/*
* Copyright (c) 1996, 1998-2005, 2007-2013, 2014-2016
* Copyright (c) 1996, 1998-2005, 2007-2013, 2014-2017
* Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
@@ -43,6 +43,8 @@
#if defined(YYBISON) && defined(HAVE_ALLOCA_H) && !defined(__GNUC__)
# include <alloca.h>
#endif /* YYBISON && HAVE_ALLOCA_H && !__GNUC__ */
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include "sudoers.h" /* XXX */
@@ -66,6 +68,7 @@ struct userspec_list userspecs = TAILQ_HEAD_INITIALIZER(userspecs);
/*
* Local protoypes
*/
static void init_options(struct command_options *opts);
static bool add_defaults(int, struct member *, struct defaults *);
static bool add_userspec(struct member *, struct privilege *);
static struct defaults *new_default(char *, char *, short);
@@ -127,6 +130,7 @@ static struct sudo_digest *new_digest(int, const char *);
%token <tok> ROLE /* SELinux role */
%token <tok> PRIVS /* Solaris privileges */
%token <tok> LIMITPRIVS /* Solaris limit privileges */
%token <tok> CMND_TIMEOUT /* command timeout */
%token <tok> MYSELF /* run as myself, not another user */
%token <tok> SHA224_TOK /* sha224 token */
%token <tok> SHA256_TOK /* sha256 token */
@@ -159,7 +163,9 @@ static struct sudo_digest *new_digest(int, const char *);
%type <string> typespec
%type <string> privsspec
%type <string> limitprivsspec
%type <string> timeoutspec
%type <digest> digest
%type <options> options
%%
@@ -347,6 +353,9 @@ cmndspeclist : cmndspec
if ($3->limitprivs == NULL)
$3->limitprivs = prev->limitprivs;
#endif /* HAVE_PRIV_SET */
/* propagate command timeout */
if ($3->timeout == UNSPEC)
$3->timeout = prev->timeout;
/* propagate tags and runas list */
if ($3->tags.nopasswd == UNSPEC)
$3->tags.nopasswd = prev->tags.nopasswd;
@@ -411,6 +420,7 @@ cmndspec : runasspec options digcmnd {
cs->privs = $2.privs;
cs->limitprivs = $2.limitprivs;
#endif
cs->timeout = $2.timeout;
cs->tags = $2.tags;
cs->cmnd = $3;
HLTQ_INIT(cs, entries);
@@ -476,6 +486,11 @@ opcmnd : cmnd {
}
;
timeoutspec : CMND_TIMEOUT '=' WORD {
$$ = $3;
}
;
rolespec : ROLE '=' WORD {
$$ = $3;
}
@@ -563,13 +578,14 @@ runaslist : /* empty */ {
;
options : /* empty */ {
TAGS_INIT($$.tags);
#ifdef HAVE_SELINUX
$$.role = NULL, $$.type = NULL;
#endif
#ifdef HAVE_PRIV_SET
$$.privs = NULL, $$.limitprivs = NULL;
#endif
init_options(&$$);
}
| options timeoutspec {
$$.timeout = parse_timeout($2);
if ($$.timeout == -1) {
sudoerserror(N_("unable parse timeout value"));
YYERROR;
}
}
| options NOPASSWD {
$$.tags.nopasswd = true;
@@ -1145,3 +1161,21 @@ init_parser(const char *path, bool quiet)
debug_return_bool(ret);
}
/*
* Initialize all options in a cmndspec.
*/
static void
init_options(struct command_options *opts)
{
TAGS_INIT(opts->tags);
opts->timeout = UNSPEC;
#ifdef HAVE_SELINUX
opts->role = NULL;
opts->type = NULL;
#endif
#ifdef HAVE_PRIV_SET
opts->privs = NULL;
opts->limitprivs = NULL;
#endif
}

View File

@@ -2479,6 +2479,8 @@ sudo_ldap_display_entry_short(LDAP *ld, LDAPMessage *entry, struct passwd *pw,
sudo_lbuf_append(lbuf, negated ? "NOSETENV: " : "SETENV: ");
else if (strcmp(val, "mail_all_cmnds") == 0 || strcmp(val, "mail_always") == 0)
sudo_lbuf_append(lbuf, negated ? "NOMAIL: " : "MAIL: ");
else if (!negated && strcmp(val, "command_timeout") == 0)
sudo_lbuf_append(lbuf, "TIMEOUT=%s", val);
}
ldap_value_free_len(bv);
}

View File

@@ -147,6 +147,7 @@ sub print_record {
elsif (/^T_LOGPRI/) { $v = "ival"; }
elsif (/^T_TUPLE/) { $v = "tuple"; }
elsif (/^T_FLOAT/) { $v = "fval"; }
elsif (/^T_TIMEOUT/) { $v = "ival"; }
else { die "$0: unknown defaults type: $_\n"; }
}
printf HEADER "#define %-23s (sudo_defs_table[$defname].sd_un.${v})\n",

View File

@@ -145,7 +145,7 @@ sudo_file_setdefs(struct sudo_nss *nss)
int
sudo_file_lookup(struct sudo_nss *nss, int validated, int pwflag)
{
int match, host_match, runas_match, cmnd_match;
int match, host_match, runas_match, cmnd_match, timeout;
struct cmndspec *cs;
struct cmndtag *tags = NULL;
struct privilege *priv;
@@ -227,6 +227,7 @@ sudo_file_lookup(struct sudo_nss *nss, int validated, int pwflag)
if (cmnd_match != UNSPEC) {
match = cmnd_match;
tags = &cs->tags;
timeout = cs->timeout;
#ifdef HAVE_SELINUX
/* Set role and type if not specified on command line. */
if (user_role == NULL) {
@@ -301,6 +302,8 @@ sudo_file_lookup(struct sudo_nss *nss, int validated, int pwflag)
if (match == ALLOW) {
SET(validated, VALIDATE_SUCCESS);
CLR(validated, VALIDATE_FAILURE);
if (timeout > 0)
def_command_timeout = timeout;
if (tags != NULL) {
if (tags->nopasswd != UNSPEC)
def_authenticate = !tags->nopasswd;
@@ -370,6 +373,11 @@ sudo_file_append_cmnd(struct cmndspec *cs, struct cmndtag *tags,
if (cs->type)
sudo_lbuf_append(lbuf, "TYPE=%s ", cs->type);
#endif /* HAVE_SELINUX */
if (cs->timeout > 0) {
char numbuf[(((sizeof(int) * 8) + 2) / 3) + 2];
snprintf(numbuf, sizeof(numbuf), "%d", cs->timeout);
sudo_lbuf_append(lbuf, "TIMEOUT=%s ", numbuf);
}
if (TAG_CHANGED(setenv)) {
tags->setenv = cs->tags.setenv;
sudo_lbuf_append(lbuf, tags->setenv ? "SETENV: " : "NOSETENV: ");
@@ -481,6 +489,8 @@ new_long_entry(struct cmndspec *cs, struct cmndspec *prev_cs)
if (cs->type && (!prev_cs->type || strcmp(cs->type, prev_cs->type) != 0))
return true;
#endif /* HAVE_SELINUX */
if (cs->timeout != prev_cs->timeout)
return true;
return false;
}
@@ -553,6 +563,11 @@ sudo_file_display_priv_long(struct passwd *pw, struct userspec *us,
if (cs->type)
sudo_lbuf_append(lbuf, " Type: %s\n", cs->type);
#endif /* HAVE_SELINUX */
if (cs->timeout > 0) {
char numbuf[(((sizeof(int) * 8) + 2) / 3) + 2];
snprintf(numbuf, sizeof(numbuf), "%d", cs->timeout);
sudo_lbuf_append(lbuf, " Timeout: %s\n", numbuf);
}
sudo_lbuf_append(lbuf, _(" Commands:\n"));
}
sudo_lbuf_append(lbuf, "\t");

View File

@@ -113,7 +113,8 @@ struct cmndtag {
* Per-command option container struct.
*/
struct command_options {
struct cmndtag tags; /* Command tags */
struct cmndtag tags; /* tag specificaion */
int timeout; /* command timeout */
#ifdef HAVE_SELINUX
char *role, *type; /* SELinux role and type */
#endif
@@ -168,6 +169,7 @@ struct privilege {
/*
* Structure describing a linked list of Cmnd_Specs.
* XXX - include struct command_options instad of its contents inline
*/
struct cmndspec {
TAILQ_ENTRY(cmndspec) entries;
@@ -175,6 +177,7 @@ struct cmndspec {
struct member_list *runasgrouplist; /* list of runas groups */
struct member *cmnd; /* command to allow/deny */
struct cmndtag tags; /* tag specificaion */
int timeout; /* command timeout */
#ifdef HAVE_SELINUX
char *role, *type; /* SELinux role and type */
#endif
@@ -275,4 +278,7 @@ int hexchar(const char *s);
/* base64.c */
size_t base64_decode(const char *str, unsigned char *dst, size_t dsize);
/* timeout.c */
int parse_timeout(const char *timestr);
#endif /* SUDOERS_PARSE_H */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2016 Todd C. Miller <Todd.Miller@courtesan.com>
* Copyright (c) 2010-2017 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
@@ -580,6 +580,10 @@ sudoers_policy_exec_setup(char *argv[], char *envp[], mode_t cmnd_umask,
if ((command_info[info_len++] = sudo_new_key_val("iolog_group", def_iolog_group)) == NULL)
goto oom;
}
if (def_command_timeout != 0) {
if (asprintf(&command_info[info_len++], "timeout=%u", def_command_timeout) == -1)
goto oom;
}
if (cmnd_umask != ACCESSPERMS) {
if (asprintf(&command_info[info_len++], "umask=0%o", (unsigned int)cmnd_umask) == -1)
goto oom;

View File

@@ -0,0 +1,13 @@
# Test parsing of command_timeout and TIMEOUT syntax
Defaults command_timeout=2d8h10m59s
user0 ALL = TIMEOUT=7D4H10M30S /usr/bin/id, /usr/bin/who, TIMEOUT=0 /bin/ls
user1 ALL = TIMEOUT=7d4h10m30s /usr/bin/id
user2 ALL = TIMEOUT=4h10m30s /usr/bin/id
user3 ALL = TIMEOUT=10m30s /usr/bin/id
user4 ALL = TIMEOUT=14d /usr/bin/id
user5 ALL = TIMEOUT=5m /usr/bin/id
user6 ALL = TIMEOUT=30s /usr/bin/id
user7 ALL = TIMEOUT=45 /usr/bin/id
user8 ALL = TIMEOUT=7d4h10m30s /usr/bin/id, TIMEOUT=4h10m30s /usr/bin/id, \
TIMEOUT=10m30s /usr/bin/id, TIMEOUT=14d /usr/bin/id, \
TIMEOUT=5m /usr/bin/id, TIMEOUT=30s /usr/bin/id

View File

@@ -0,0 +1,180 @@
{
"Defaults": [
{
"Options": [
{ "command_timeout": "2d8h10m59s" }
]
}
],
"User_Specs": [
{
"User_List": [
{ "username": "user0" }
],
"Host_List": [
{ "hostalias": "ALL" }
],
"Cmnd_Specs": [
{
"Options": [
{ "command_timeout": 619830 }
],
"Commands": [
{ "command": "/usr/bin/id" },
{ "command": "/usr/bin/who" },
{ "command": "/bin/ls" }
]
}
]
},
{
"User_List": [
{ "username": "user1" }
],
"Host_List": [
{ "hostalias": "ALL" }
],
"Cmnd_Specs": [
{
"Options": [
{ "command_timeout": 619830 }
],
"Commands": [
{ "command": "/usr/bin/id" }
]
}
]
},
{
"User_List": [
{ "username": "user2" }
],
"Host_List": [
{ "hostalias": "ALL" }
],
"Cmnd_Specs": [
{
"Options": [
{ "command_timeout": 15030 }
],
"Commands": [
{ "command": "/usr/bin/id" }
]
}
]
},
{
"User_List": [
{ "username": "user3" }
],
"Host_List": [
{ "hostalias": "ALL" }
],
"Cmnd_Specs": [
{
"Options": [
{ "command_timeout": 630 }
],
"Commands": [
{ "command": "/usr/bin/id" }
]
}
]
},
{
"User_List": [
{ "username": "user4" }
],
"Host_List": [
{ "hostalias": "ALL" }
],
"Cmnd_Specs": [
{
"Options": [
{ "command_timeout": 1209600 }
],
"Commands": [
{ "command": "/usr/bin/id" }
]
}
]
},
{
"User_List": [
{ "username": "user5" }
],
"Host_List": [
{ "hostalias": "ALL" }
],
"Cmnd_Specs": [
{
"Options": [
{ "command_timeout": 300 }
],
"Commands": [
{ "command": "/usr/bin/id" }
]
}
]
},
{
"User_List": [
{ "username": "user6" }
],
"Host_List": [
{ "hostalias": "ALL" }
],
"Cmnd_Specs": [
{
"Options": [
{ "command_timeout": 30 }
],
"Commands": [
{ "command": "/usr/bin/id" }
]
}
]
},
{
"User_List": [
{ "username": "user7" }
],
"Host_List": [
{ "hostalias": "ALL" }
],
"Cmnd_Specs": [
{
"Options": [
{ "command_timeout": 45 }
],
"Commands": [
{ "command": "/usr/bin/id" }
]
}
]
},
{
"User_List": [
{ "username": "user8" }
],
"Host_List": [
{ "hostalias": "ALL" }
],
"Cmnd_Specs": [
{
"Options": [
{ "command_timeout": 619830 }
],
"Commands": [
{ "command": "/usr/bin/id" },
{ "command": "/usr/bin/id" },
{ "command": "/usr/bin/id" },
{ "command": "/usr/bin/id" },
{ "command": "/usr/bin/id" },
{ "command": "/usr/bin/id" }
]
}
]
}
]
}

View File

@@ -0,0 +1,14 @@
Parses OK.
Defaults command_timeout=2d8h10m59s
user0 ALL = TIMEOUT=619830 /usr/bin/id, TIMEOUT=619830 /usr/bin/who, /bin/ls
user1 ALL = TIMEOUT=619830 /usr/bin/id
user2 ALL = TIMEOUT=15030 /usr/bin/id
user3 ALL = TIMEOUT=630 /usr/bin/id
user4 ALL = TIMEOUT=1209600 /usr/bin/id
user5 ALL = TIMEOUT=300 /usr/bin/id
user6 ALL = TIMEOUT=30 /usr/bin/id
user7 ALL = TIMEOUT=45 /usr/bin/id
user8 ALL = TIMEOUT=619830 /usr/bin/id, TIMEOUT=15030 /usr/bin/id, TIMEOUT=630 /usr/bin/id, TIMEOUT=1209600 /usr/bin/id, TIMEOUT=300 /usr/bin/id, TIMEOUT=30 /usr/bin/id

View File

@@ -0,0 +1,11 @@
#
DEFAULTS DEFVAR = WORD(2)
WORD(5) ALL = CMND_TIMEOUT = WORD(5) COMMAND , COMMAND , CMND_TIMEOUT = WORD(5) COMMAND
WORD(5) ALL = CMND_TIMEOUT = WORD(5) COMMAND
WORD(5) ALL = CMND_TIMEOUT = WORD(5) COMMAND
WORD(5) ALL = CMND_TIMEOUT = WORD(5) COMMAND
WORD(5) ALL = CMND_TIMEOUT = WORD(5) COMMAND
WORD(5) ALL = CMND_TIMEOUT = WORD(5) COMMAND
WORD(5) ALL = CMND_TIMEOUT = WORD(5) COMMAND
WORD(5) ALL = CMND_TIMEOUT = WORD(5) COMMAND
WORD(5) ALL = CMND_TIMEOUT = WORD(5) COMMAND , CMND_TIMEOUT = WORD(5) COMMAND , CMND_TIMEOUT = WORD(5) COMMAND , CMND_TIMEOUT = WORD(5) COMMAND , CMND_TIMEOUT = WORD(5) COMMAND , CMND_TIMEOUT = WORD(5) COMMAND

View File

@@ -0,0 +1,8 @@
# Test command_timeout and TIMEOUT syntax errors
Defaults command_timeout=2d8h10m59ss
Defaults:root command_timeout=15f
user0 ALL = TIMEOUT=7dd4h10m30s /usr/bin/id, /usr/bin/who, TIMEOUT=0 /bin/ls
user1 ALL = TIMEOUT=7d4h10mm30s /usr/bin/id
user2 ALL = TIMEOUT=4hg10m30s /usr/bin/id
user3 ALL = TIMEOUT=10m30ss /usr/bin/id
user4 ALL = TIMEOUT=14g /usr/bin/id

View File

@@ -0,0 +1,6 @@
Parse error in sudoers near line 4 (problem with defaults entries).
Defaults command_timeout=2d8h10m59ss
Defaults:root command_timeout=15f

View File

@@ -0,0 +1,10 @@
#
DEFAULTS DEFVAR = WORD(2)
DEFAULTS_USER WORD(5) DEFVAR = WORD(2)
WORD(5) ALL = CMND_TIMEOUT = WORD(5) <*> COMMAND , COMMAND , CMND_TIMEOUT = WORD(5) COMMAND
WORD(5) ALL = CMND_TIMEOUT = WORD(5) <*> COMMAND
WORD(5) ALL = CMND_TIMEOUT = WORD(5) <*> COMMAND
WORD(5) ALL = CMND_TIMEOUT = WORD(5) <*> COMMAND
WORD(5) ALL = CMND_TIMEOUT = WORD(5) <*> COMMAND
testsudoers: sudoers:2 value "2d8h10m59ss" is invalid for option "command_timeout"
testsudoers: sudoers:3 value "15f" is invalid for option "command_timeout"

View File

@@ -634,6 +634,8 @@ print_privilege(struct privilege *priv)
if (cs->limitprivs)
printf("LIMITPRIVS=%s ", cs->limitprivs);
#endif /* HAVE_PRIV_SET */
if (cs->timeout > 0)
printf("TIMEOUT=%d ", cs->timeout);
if (TAG_CHANGED(follow))
printf("%sFOLLOW: ", cs->tags.follow ? "" : "NO");
if (TAG_CHANGED(log_input))

113
plugins/sudoers/timeout.c Normal file
View File

@@ -0,0 +1,113 @@
/*
* Copyright (c) 2017 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.
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <config.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#ifdef HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif /* HAVE_STRINGS_H */
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include "sudo_compat.h"
#include "sudoers_debug.h"
#include "parse.h"
/*
* Parse a command timeout in sudoers in the format 1d2h3m4s
* (days, hours, minutes, seconds) or a number of seconds with no suffix.
* Returns the number of seconds or -1 on error.
*/
int
parse_timeout(const char *timestr)
{
debug_decl(parse_timeout, SUDOERS_DEBUG_PARSER)
const char digits[] = "0123456789";
const char suffixes[] = "dhms";
const char *cp;
int timeout = 0;
size_t len = 0;
int idx = 0;
for (cp = timestr; *cp != '\0'; cp += len) {
char ch;
long l;
if ((len = strspn(cp, digits)) == 0) {
/* parse error */
errno = EINVAL;
debug_return_int(-1);
}
if (cp[len] == '\0') {
/* no suffix, assume seconds. */
ch = 's';
} else {
ch = tolower(cp[len]);
len++;
}
/* Find a matching suffix or return an error. */
while (suffixes[idx] != ch) {
if (suffixes[idx] == '\0') {
/* parse error */
errno = EINVAL;
debug_return_int(-1);
}
idx++;
}
errno = 0;
l = strtol(cp, NULL, 10);
if (errno == ERANGE || l > INT_MAX)
goto overflow;
switch (ch) {
case 'd':
if (l > INT_MAX / (24 * 60 * 60))
goto overflow;
l *= 24 * 60 * 60;
break;
case 'h':
if (l > INT_MAX / (60 * 60))
goto overflow;
l *= 60 * 60;
break;
case 'm':
if (l > INT_MAX / 60)
goto overflow;
l *= 60;
break;
}
if (l > INT_MAX - timeout)
goto overflow;
timeout += l;
}
debug_return_int(timeout);
overflow:
errno = EOVERFLOW;
debug_return_int(-1);
}

File diff suppressed because it is too large Load Diff

View File

@@ -530,6 +530,11 @@ ALL {
}
<INITIAL>TIMEOUT {
LEXTRACE("CMND_TIMEOUT ");
LEXRETURN(CMND_TIMEOUT);
}
<INITIAL>ROLE {
#ifdef HAVE_SELINUX
LEXTRACE("ROLE ");

View File

@@ -781,11 +781,17 @@ print_cmndspec_json(FILE *fp, struct cmndspec *cs, struct cmndspec **nextp,
}
/* Print tags */
if (TAGS_SET(cs->tags)) {
if (cs->timeout > 0 || TAGS_SET(cs->tags)) {
struct cmndtag tag = cs->tags;
fprintf(fp, "%*s\"Options\": [\n", indent, "");
indent += 4;
if (cs->timeout > 0) {
value.type = JSON_NUMBER;
value.u.number = cs->timeout;
print_pair_json(fp, "{ ", "command_timeout", &value,
TAGS_SET(tag) ? " },\n" : " }\n", indent);
}
if (tag.nopasswd != UNSPEC) {
value.type = JSON_BOOL;
value.u.boolean = !tag.nopasswd;