diff --git a/NEWS b/NEWS index d5f4fc3d1..2ae665a1a 100644 --- a/NEWS +++ b/NEWS @@ -44,6 +44,11 @@ What's new in Sudo 1.9.3 for "sudoers_policy" but "sudoers_audit" is not listed, those arguments will be applied to "sudoers_audit" instead. + * The user's resource limits are now passed to sudo plugins in + the user_info[] list. A plugin cannot determine the limits + itself because sudo changes the limits while it runs to prevent + resource starvation. + What's new in Sudo 1.9.2 * Fixed package builds on RedHat Enterprise Linux 8. diff --git a/doc/sudo_plugin.man.in b/doc/sudo_plugin.man.in index 4ba139f49..041391467 100644 --- a/doc/sudo_plugin.man.in +++ b/doc/sudo_plugin.man.in @@ -16,7 +16,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.TH "SUDO_PLUGIN" "5" "June 16, 2020" "Sudo @PACKAGE_VERSION@" "File Formats Manual" +.TH "SUDO_PLUGIN" "5" "August 31, 2020" "Sudo @PACKAGE_VERSION@" "File Formats Manual" .nh .if n .ad l .SH "NAME" @@ -513,6 +513,97 @@ The parent process ID of the running process. Only available starting with API version 1.2. .TP 6n +rlimit_as=soft,hard +The maximum size to which the process's address space may grow (in bytes), +if supported by the operating system. +The soft and hard limits are separated by a comma. +A value of +\(lqinfinity\(rq +indicates that there is no limit. +Only available starting with API version 1.16. +.TP 6n +rlimit_core=soft,hard +The largest size core dump file that may be created (in bytes). +The soft and hard limits are separated by a comma. +A value of +\(lqinfinity\(rq +indicates that there is no limit. +Only available starting with API version 1.16. +.TP 6n +rlimit_cpu=soft,hard +The maximum amount of CPU time that the process may use (in seconds). +The soft and hard limits are separated by a comma. +A value of +\(lqinfinity\(rq +indicates that there is no limit. +Only available starting with API version 1.16. +.TP 6n +rlimit_data=soft,hard +The maximum size of the data segment for the process (in bytes). +The soft and hard limits are separated by a comma. +A value of +\(lqinfinity\(rq +indicates that there is no limit. +Only available starting with API version 1.16. +.TP 6n +rlimit_fsize=soft,hard +The largest size file that the process may create (in bytes). +The soft and hard limits are separated by a comma. +A value of +\(lqinfinity\(rq +indicates that there is no limit. +Only available starting with API version 1.16. +.TP 6n +rlimit_locks=soft,hard +The maximum number of locks that the process may establish, +if supported by the operating system. +The soft and hard limits are separated by a comma. +A value of +\(lqinfinity\(rq +indicates that there is no limit. +Only available starting with API version 1.16. +.TP 6n +rlimit_memlock=soft,hard +The maximum size that the process may lock in memory (in bytes), +if supported by the operating system. +The soft and hard limits are separated by a comma. +A value of +\(lqinfinity\(rq +indicates that there is no limit. +Only available starting with API version 1.16. +.TP 6n +rlimit_nofile=soft,hard +The maximum number of files that the process may have open. +The soft and hard limits are separated by a comma. +A value of +\(lqinfinity\(rq +indicates that there is no limit. +Only available starting with API version 1.16. +.TP 6n +rlimit_nproc=soft,hard +The maximum number of processes that the user may run simultaneously. +The soft and hard limits are separated by a comma. +A value of +\(lqinfinity\(rq +indicates that there is no limit. +Only available starting with API version 1.16. +.TP 6n +rlimit_rss=soft,hard +The maximum size to which the process's resident set size may grow (in bytes). +The soft and hard limits are separated by a comma. +A value of +\(lqinfinity\(rq +indicates that there is no limit. +Only available starting with API version 1.16. +.TP 6n +rlimit_stack=soft,hard +The maximum size to which the process's stack may grow (in bytes). +The soft and hard limits are separated by a comma. +A value of +\(lqinfinity\(rq +indicates that there is no limit. +Only available starting with API version 1.16. +.TP 6n sid=int The session ID of the running \fBsudo\fR @@ -4942,6 +5033,11 @@ command was not run. has increased from 255 to 1023 bytes. .sp Support for audit and approval plugins was added. +.TP 6n +Version 1.16 (sudo 1.9.3) +Initial resource limit values were added to the +\fRuser_info\fR +list. .SH "SEE ALSO" sudo.conf(@mansectform@), sudoers(@mansectform@), diff --git a/doc/sudo_plugin.mdoc.in b/doc/sudo_plugin.mdoc.in index c942db8a8..f29e549d8 100644 --- a/doc/sudo_plugin.mdoc.in +++ b/doc/sudo_plugin.mdoc.in @@ -15,7 +15,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd June 16, 2020 +.Dd August 31, 2020 .Dt SUDO_PLUGIN @mansectform@ .Os Sudo @PACKAGE_VERSION@ .Sh NAME @@ -456,6 +456,86 @@ The parent process ID of the running .Nm sudo process. Only available starting with API version 1.2. +.It rlimit_as=soft,hard +The maximum size to which the process's address space may grow (in bytes), +if supported by the operating system. +The soft and hard limits are separated by a comma. +A value of +.Dq infinity +indicates that there is no limit. +Only available starting with API version 1.16. +.It rlimit_core=soft,hard +The largest size core dump file that may be created (in bytes). +The soft and hard limits are separated by a comma. +A value of +.Dq infinity +indicates that there is no limit. +Only available starting with API version 1.16. +.It rlimit_cpu=soft,hard +The maximum amount of CPU time that the process may use (in seconds). +The soft and hard limits are separated by a comma. +A value of +.Dq infinity +indicates that there is no limit. +Only available starting with API version 1.16. +.It rlimit_data=soft,hard +The maximum size of the data segment for the process (in bytes). +The soft and hard limits are separated by a comma. +A value of +.Dq infinity +indicates that there is no limit. +Only available starting with API version 1.16. +.It rlimit_fsize=soft,hard +The largest size file that the process may create (in bytes). +The soft and hard limits are separated by a comma. +A value of +.Dq infinity +indicates that there is no limit. +Only available starting with API version 1.16. +.It rlimit_locks=soft,hard +The maximum number of locks that the process may establish, +if supported by the operating system. +The soft and hard limits are separated by a comma. +A value of +.Dq infinity +indicates that there is no limit. +Only available starting with API version 1.16. +.It rlimit_memlock=soft,hard +The maximum size that the process may lock in memory (in bytes), +if supported by the operating system. +The soft and hard limits are separated by a comma. +A value of +.Dq infinity +indicates that there is no limit. +Only available starting with API version 1.16. +.It rlimit_nofile=soft,hard +The maximum number of files that the process may have open. +The soft and hard limits are separated by a comma. +A value of +.Dq infinity +indicates that there is no limit. +Only available starting with API version 1.16. +.It rlimit_nproc=soft,hard +The maximum number of processes that the user may run simultaneously. +The soft and hard limits are separated by a comma. +A value of +.Dq infinity +indicates that there is no limit. +Only available starting with API version 1.16. +.It rlimit_rss=soft,hard +The maximum size to which the process's resident set size may grow (in bytes). +The soft and hard limits are separated by a comma. +A value of +.Dq infinity +indicates that there is no limit. +Only available starting with API version 1.16. +.It rlimit_stack=soft,hard +The maximum size to which the process's stack may grow (in bytes). +The soft and hard limits are separated by a comma. +A value of +.Dq infinity +indicates that there is no limit. +Only available starting with API version 1.16. .It sid=int The session ID of the running .Nm sudo @@ -4367,6 +4447,10 @@ command was not run. has increased from 255 to 1023 bytes. .Pp Support for audit and approval plugins was added. +.It Version 1.16 (sudo 1.9.3) +Initial resource limit values were added to the +.Li user_info +list. .El .Sh SEE ALSO .Xr sudo.conf @mansectform@ , diff --git a/include/sudo_plugin.h b/include/sudo_plugin.h index 61f0f3dd9..a75ca06cd 100644 --- a/include/sudo_plugin.h +++ b/include/sudo_plugin.h @@ -21,7 +21,7 @@ /* API version major/minor */ #define SUDO_API_VERSION_MAJOR 1 -#define SUDO_API_VERSION_MINOR 15 +#define SUDO_API_VERSION_MINOR 16 #define SUDO_API_MKVERSION(x, y) (((x) << 16) | (y)) #define SUDO_API_VERSION SUDO_API_MKVERSION(SUDO_API_VERSION_MAJOR, SUDO_API_VERSION_MINOR) diff --git a/plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stdout b/plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stdout index aae182d78..f914fcc5a 100644 --- a/plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stdout +++ b/plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stdout @@ -26,7 +26,7 @@ "INFO1=VALUE1", "info2=value2" \], - "version": "1.15" + "version": "1.16" } (APPROVAL 2) Constructed: { @@ -56,7 +56,7 @@ "INFO1=VALUE1", "info2=value2" \], - "version": "1.15" + "version": "1.16" } (APPROVAL 1) Show version was called with arguments: (0,) Python approval plugin (API 1.0): ApprovalTestPlugin (loaded from 'SRC_DIR/regress/plugin_approval_test.py') diff --git a/src/exec.c b/src/exec.c index 9c830fd63..0c2ba41a8 100644 --- a/src/exec.c +++ b/src/exec.c @@ -309,7 +309,7 @@ sudo_terminated(struct command_status *cstat) debug_return_bool(false); } -#if SUDO_API_VERSION != SUDO_API_MKVERSION(1, 15) +#if SUDO_API_VERSION != SUDO_API_MKVERSION(1, 16) # error "Update sudo_needs_pty() after changing the plugin API" #endif static bool diff --git a/src/limits.c b/src/limits.c index 257caa9f0..ec0c4eded 100644 --- a/src/limits.c +++ b/src/limits.c @@ -24,6 +24,7 @@ #include #include +#include #include #include #ifdef __linux__ @@ -55,6 +56,11 @@ # define RLIM_INFINITY RLIM64_INFINITY #endif /* HAVE_SETRLIMIT64 */ +/* Older BSD systems have RLIMIT_VMEM, not RLIMIT_AS. */ +#if !defined(RLIMIT_AS) && defined(RLIMIT_VMEM) +# define RLIMIT_AS RLIMIT_VMEM +#endif + /* * macOS doesn't allow nofile soft limit to be infinite or * the stack hard limit to be infinite. @@ -64,27 +70,35 @@ static struct rlimit nofile_fallback = { SUDO_OPEN_MAX, RLIM_INFINITY }; static struct rlimit stack_fallback = { SUDO_STACK_MIN, 65532 * 1024 }; static struct saved_limit { - const char *name; - int resource; - bool saved; - struct rlimit *fallback; - struct rlimit newlimit; - struct rlimit oldlimit; + const char *name; /* rlimit_foo in lower case */ + int resource; /* RLIMIT_FOO definition */ + bool override; /* override limit while sudo executes? */ + bool saved; /* true if we were able to get the value */ + struct rlimit *fallback; /* fallback if we fail to set to newlimit */ + struct rlimit newlimit; /* new limit to use if override is true */ + struct rlimit oldlimit; /* original limit, valid if saved is true */ } saved_limits[] = { #ifdef RLIMIT_AS - { "RLIMIT_AS", RLIMIT_AS, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, + { "rlimit_as", RLIMIT_AS, true, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, #endif - { "RLIMIT_CPU", RLIMIT_CPU, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, - { "RLIMIT_DATA", RLIMIT_DATA, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, - { "RLIMIT_FSIZE", RLIMIT_FSIZE, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, - { "RLIMIT_NOFILE", RLIMIT_NOFILE, false, &nofile_fallback, { RLIM_INFINITY, RLIM_INFINITY } }, + { "rlimit_core", RLIMIT_CORE, false }, + { "rlimit_cpu", RLIMIT_CPU, true, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, + { "rlimit_data", RLIMIT_DATA, true, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, + { "rlimit_fsize", RLIMIT_FSIZE, true, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, +#ifdef RLIMIT_LOCKS + { "rlimit_locks", RLIMIT_LOCKS, false }, +#endif +#ifdef RLIMIT_MEMLOCK + { "rlimit_memlock", RLIMIT_MEMLOCK, false }, +#endif + { "rlimit_nofile", RLIMIT_NOFILE, true, false, &nofile_fallback, { RLIM_INFINITY, RLIM_INFINITY } }, #ifdef RLIMIT_NPROC - { "RLIMIT_NPROC", RLIMIT_NPROC, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, + { "rlimit_nproc", RLIMIT_NPROC, true, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, #endif #ifdef RLIMIT_RSS - { "RLIMIT_RSS", RLIMIT_RSS, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, + { "rlimit_rss", RLIMIT_RSS, true, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, #endif - { "RLIMIT_STACK", RLIMIT_STACK, false, &stack_fallback, { SUDO_STACK_MIN, RLIM_INFINITY } } + { "rlimit_stack", RLIMIT_STACK, true, false, &stack_fallback, { SUDO_STACK_MIN, RLIM_INFINITY } } }; static struct rlimit corelimit; @@ -228,6 +242,9 @@ unlimit_sudo(void) (long long)lim->oldlimit.rlim_max); lim->saved = true; + if (!lim->override) + continue; + if (lim->newlimit.rlim_cur != RLIM_INFINITY) { /* Don't reduce the soft resource limit. */ if (lim->oldlimit.rlim_cur == RLIM_INFINITY || @@ -284,7 +301,7 @@ restore_limits(void) /* Restore resource limits to saved values. */ for (idx = 0; idx < nitems(saved_limits); idx++) { struct saved_limit *lim = &saved_limits[idx]; - if (lim->saved) { + if (lim->override && lim->saved) { struct rlimit rl = lim->oldlimit; int i, rc; @@ -324,3 +341,45 @@ restore_limits(void) debug_return; } + +int +serialize_limits(char **info, size_t info_max) +{ + char *str; + unsigned int idx, nstored = 0; + debug_decl(serialize_limits, SUDO_DEBUG_UTIL); + + for (idx = 0; idx < nitems(saved_limits); idx++) { + const struct saved_limit *lim = &saved_limits[idx]; + const struct rlimit *rl = &lim->oldlimit; + char curlim[(((sizeof(int) * 8) + 2) / 3) + 2]; + char maxlim[(((sizeof(int) * 8) + 2) / 3) + 2]; + + if (!lim->saved) + continue; + + if (nstored == info_max) + goto oom; + + if (rl->rlim_cur == RLIM_INFINITY) { + strlcpy(curlim, "infinity", sizeof(curlim)); + } else { + snprintf(curlim, sizeof(curlim), "%llu", + (unsigned long long)rl->rlim_cur); + } + if (rl->rlim_max == RLIM_INFINITY) { + strlcpy(maxlim, "infinity", sizeof(maxlim)); + } else { + snprintf(maxlim, sizeof(maxlim), "%llu", + (unsigned long long)rl->rlim_max); + } + if (asprintf(&str, "%s=%s,%s", lim->name, curlim, maxlim) == -1) + goto oom; + info[nstored++] = str; + } + debug_return_int(nstored); +oom: + while (nstored--) + free(info[nstored]); + debug_return_int(-1); +} diff --git a/src/sudo.c b/src/sudo.c index 62096c57c..a32ef1466 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -490,10 +491,11 @@ static char ** get_user_info(struct user_details *ud) { char *cp, **user_info, path[PATH_MAX]; + size_t user_info_max = 32 + RLIM_NLIMITS; unsigned int i = 0; mode_t mask; struct passwd *pw; - int fd; + int fd, n; debug_decl(get_user_info, SUDO_DEBUG_UTIL); /* @@ -512,7 +514,7 @@ get_user_info(struct user_details *ud) memset(ud, 0, sizeof(*ud)); /* XXX - bound check number of entries */ - user_info = reallocarray(NULL, 32, sizeof(char *)); + user_info = reallocarray(NULL, user_info_max, sizeof(char *)); if (user_info == NULL) goto oom; @@ -614,6 +616,11 @@ get_user_info(struct user_details *ud) if (asprintf(&user_info[++i], "cols=%d", ud->ts_cols) == -1) goto oom; + n = serialize_limits(&user_info[i + 1], user_info_max - (i + 1)); + if (n == -1) + goto oom; + i += n; + user_info[++i] = NULL; /* Add to list of vectors to be garbage collected at exit. */ diff --git a/src/sudo.h b/src/sudo.h index 23f734c6e..7603fd2f7 100644 --- a/src/sudo.h +++ b/src/sudo.h @@ -293,5 +293,6 @@ void restore_limits(void); void restore_nproc(void); void unlimit_nproc(void); void unlimit_sudo(void); +int serialize_limits(char **info, size_t info_max); #endif /* SUDO_SUDO_H */