Pass resource limits values to the plugin in user_info[]

Sudo resets the resource limits early in its execution so
the plugin cannot tell what the original limits were itself.
This commit is contained in:
Todd C. Miller
2020-08-31 16:37:01 -06:00
parent 84e6e6ccf9
commit c4a579cf8a
9 changed files with 275 additions and 23 deletions

5
NEWS
View File

@@ -44,6 +44,11 @@ What's new in Sudo 1.9.3
for "sudoers_policy" but "sudoers_audit" is not listed, those for "sudoers_policy" but "sudoers_audit" is not listed, those
arguments will be applied to "sudoers_audit" instead. 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 What's new in Sudo 1.9.2
* Fixed package builds on RedHat Enterprise Linux 8. * Fixed package builds on RedHat Enterprise Linux 8.

View File

@@ -16,7 +16,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" 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 .nh
.if n .ad l .if n .ad l
.SH "NAME" .SH "NAME"
@@ -513,6 +513,97 @@ The parent process ID of the running
process. process.
Only available starting with API version 1.2. Only available starting with API version 1.2.
.TP 6n .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 sid=int
The session ID of the running The session ID of the running
\fBsudo\fR \fBsudo\fR
@@ -4942,6 +5033,11 @@ command was not run.
has increased from 255 to 1023 bytes. has increased from 255 to 1023 bytes.
.sp .sp
Support for audit and approval plugins was added. 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" .SH "SEE ALSO"
sudo.conf(@mansectform@), sudo.conf(@mansectform@),
sudoers(@mansectform@), sudoers(@mansectform@),

View File

@@ -15,7 +15,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\" .\"
.Dd June 16, 2020 .Dd August 31, 2020
.Dt SUDO_PLUGIN @mansectform@ .Dt SUDO_PLUGIN @mansectform@
.Os Sudo @PACKAGE_VERSION@ .Os Sudo @PACKAGE_VERSION@
.Sh NAME .Sh NAME
@@ -456,6 +456,86 @@ The parent process ID of the running
.Nm sudo .Nm sudo
process. process.
Only available starting with API version 1.2. 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 .It sid=int
The session ID of the running The session ID of the running
.Nm sudo .Nm sudo
@@ -4367,6 +4447,10 @@ command was not run.
has increased from 255 to 1023 bytes. has increased from 255 to 1023 bytes.
.Pp .Pp
Support for audit and approval plugins was added. 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 .El
.Sh SEE ALSO .Sh SEE ALSO
.Xr sudo.conf @mansectform@ , .Xr sudo.conf @mansectform@ ,

View File

@@ -21,7 +21,7 @@
/* API version major/minor */ /* API version major/minor */
#define SUDO_API_VERSION_MAJOR 1 #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_MKVERSION(x, y) (((x) << 16) | (y))
#define SUDO_API_VERSION SUDO_API_MKVERSION(SUDO_API_VERSION_MAJOR, SUDO_API_VERSION_MINOR) #define SUDO_API_VERSION SUDO_API_MKVERSION(SUDO_API_VERSION_MAJOR, SUDO_API_VERSION_MINOR)

View File

@@ -26,7 +26,7 @@
"INFO1=VALUE1", "INFO1=VALUE1",
"info2=value2" "info2=value2"
\], \],
"version": "1.15" "version": "1.16"
} }
(APPROVAL 2) Constructed: (APPROVAL 2) Constructed:
{ {
@@ -56,7 +56,7 @@
"INFO1=VALUE1", "INFO1=VALUE1",
"info2=value2" "info2=value2"
\], \],
"version": "1.15" "version": "1.16"
} }
(APPROVAL 1) Show version was called with arguments: (0,) (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') Python approval plugin (API 1.0): ApprovalTestPlugin (loaded from 'SRC_DIR/regress/plugin_approval_test.py')

View File

@@ -309,7 +309,7 @@ sudo_terminated(struct command_status *cstat)
debug_return_bool(false); 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" # error "Update sudo_needs_pty() after changing the plugin API"
#endif #endif
static bool static bool

View File

@@ -24,6 +24,7 @@
#include <config.h> #include <config.h>
#include <sys/resource.h> #include <sys/resource.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#ifdef __linux__ #ifdef __linux__
@@ -55,6 +56,11 @@
# define RLIM_INFINITY RLIM64_INFINITY # define RLIM_INFINITY RLIM64_INFINITY
#endif /* HAVE_SETRLIMIT64 */ #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 * macOS doesn't allow nofile soft limit to be infinite or
* the stack hard limit to be infinite. * 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 rlimit stack_fallback = { SUDO_STACK_MIN, 65532 * 1024 };
static struct saved_limit { static struct saved_limit {
const char *name; const char *name; /* rlimit_foo in lower case */
int resource; int resource; /* RLIMIT_FOO definition */
bool saved; bool override; /* override limit while sudo executes? */
struct rlimit *fallback; bool saved; /* true if we were able to get the value */
struct rlimit newlimit; struct rlimit *fallback; /* fallback if we fail to set to newlimit */
struct rlimit oldlimit; struct rlimit newlimit; /* new limit to use if override is true */
struct rlimit oldlimit; /* original limit, valid if saved is true */
} saved_limits[] = { } saved_limits[] = {
#ifdef RLIMIT_AS #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 #endif
{ "RLIMIT_CPU", RLIMIT_CPU, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, { "rlimit_core", RLIMIT_CORE, false },
{ "RLIMIT_DATA", RLIMIT_DATA, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, { "rlimit_cpu", RLIMIT_CPU, true, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } },
{ "RLIMIT_FSIZE", RLIMIT_FSIZE, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } }, { "rlimit_data", RLIMIT_DATA, true, false, NULL, { RLIM_INFINITY, RLIM_INFINITY } },
{ "RLIMIT_NOFILE", RLIMIT_NOFILE, false, &nofile_fallback, { 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 #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 #endif
#ifdef RLIMIT_RSS #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 #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; static struct rlimit corelimit;
@@ -228,6 +242,9 @@ unlimit_sudo(void)
(long long)lim->oldlimit.rlim_max); (long long)lim->oldlimit.rlim_max);
lim->saved = true; lim->saved = true;
if (!lim->override)
continue;
if (lim->newlimit.rlim_cur != RLIM_INFINITY) { if (lim->newlimit.rlim_cur != RLIM_INFINITY) {
/* Don't reduce the soft resource limit. */ /* Don't reduce the soft resource limit. */
if (lim->oldlimit.rlim_cur == RLIM_INFINITY || if (lim->oldlimit.rlim_cur == RLIM_INFINITY ||
@@ -284,7 +301,7 @@ restore_limits(void)
/* Restore resource limits to saved values. */ /* Restore resource limits to saved values. */
for (idx = 0; idx < nitems(saved_limits); idx++) { for (idx = 0; idx < nitems(saved_limits); idx++) {
struct saved_limit *lim = &saved_limits[idx]; struct saved_limit *lim = &saved_limits[idx];
if (lim->saved) { if (lim->override && lim->saved) {
struct rlimit rl = lim->oldlimit; struct rlimit rl = lim->oldlimit;
int i, rc; int i, rc;
@@ -324,3 +341,45 @@ restore_limits(void)
debug_return; 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);
}

View File

@@ -30,6 +30,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/resource.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
@@ -490,10 +491,11 @@ static char **
get_user_info(struct user_details *ud) get_user_info(struct user_details *ud)
{ {
char *cp, **user_info, path[PATH_MAX]; char *cp, **user_info, path[PATH_MAX];
size_t user_info_max = 32 + RLIM_NLIMITS;
unsigned int i = 0; unsigned int i = 0;
mode_t mask; mode_t mask;
struct passwd *pw; struct passwd *pw;
int fd; int fd, n;
debug_decl(get_user_info, SUDO_DEBUG_UTIL); debug_decl(get_user_info, SUDO_DEBUG_UTIL);
/* /*
@@ -512,7 +514,7 @@ get_user_info(struct user_details *ud)
memset(ud, 0, sizeof(*ud)); memset(ud, 0, sizeof(*ud));
/* XXX - bound check number of entries */ /* 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) if (user_info == NULL)
goto oom; goto oom;
@@ -614,6 +616,11 @@ get_user_info(struct user_details *ud)
if (asprintf(&user_info[++i], "cols=%d", ud->ts_cols) == -1) if (asprintf(&user_info[++i], "cols=%d", ud->ts_cols) == -1)
goto oom; 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; user_info[++i] = NULL;
/* Add to list of vectors to be garbage collected at exit. */ /* Add to list of vectors to be garbage collected at exit. */

View File

@@ -293,5 +293,6 @@ void restore_limits(void);
void restore_nproc(void); void restore_nproc(void);
void unlimit_nproc(void); void unlimit_nproc(void);
void unlimit_sudo(void); void unlimit_sudo(void);
int serialize_limits(char **info, size_t info_max);
#endif /* SUDO_SUDO_H */ #endif /* SUDO_SUDO_H */