Add front-end support for setting resouce limits.

The special value "user" means preserve the invoking user's limit.
The value "default" means don't override the default limit for the
user as assigned by the system (PAM, loging.conf, userdb, etc).
This commit is contained in:
Todd C. Miller
2021-11-08 18:21:11 -07:00
parent f279df5e18
commit 74ef983f60
4 changed files with 240 additions and 4 deletions

View File

@@ -164,6 +164,9 @@ exec_setup(struct command_details *details, int intercept_fd, int errfd)
if (ISSET(details->flags, CD_OVERRIDE_UMASK)) if (ISSET(details->flags, CD_OVERRIDE_UMASK))
(void) umask(details->umask); (void) umask(details->umask);
/* Apply resource limits specified by the policy, if any. */
set_policy_rlimits();
/* Close fds before chroot (need /dev) or uid change (prlimit on Linux). */ /* Close fds before chroot (need /dev) or uid change (prlimit on Linux). */
close_fds(details, errfd, intercept_fd); close_fds(details, errfd, intercept_fd);

View File

@@ -30,6 +30,7 @@
#ifdef __linux__ #ifdef __linux__
# include <sys/prctl.h> # include <sys/prctl.h>
#endif #endif
#include <ctype.h>
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
@@ -77,10 +78,13 @@ static struct saved_limit {
int resource; /* RLIMIT_FOO definition */ int resource; /* RLIMIT_FOO definition */
bool override; /* override limit while sudo executes? */ bool override; /* override limit while sudo executes? */
bool saved; /* true if we were able to get the value */ bool saved; /* true if we were able to get the value */
bool policy; /* true if policy specified an rlimit */
bool preserve; /* true if policy says to preserve user limit */
rlim_t minlimit; /* only modify limit if less than this value */ rlim_t minlimit; /* only modify limit if less than this value */
struct rlimit *fallback; /* fallback if we fail to set to newlimit */ struct rlimit *fallback; /* fallback if we fail to set to newlimit */
struct rlimit newlimit; /* new limit to use if override is true */ struct rlimit newlimit; /* new limit to use if override is true */
struct rlimit oldlimit; /* original limit, valid if saved is true */ struct rlimit oldlimit; /* original limit, valid if saved is true */
struct rlimit policylimit; /* limit from policy, valid if policy is true */
} saved_limits[] = { } saved_limits[] = {
#ifdef RLIMIT_AS #ifdef RLIMIT_AS
{ {
@@ -88,6 +92,8 @@ static struct saved_limit {
RLIMIT_AS, RLIMIT_AS,
true, /* override */ true, /* override */
false, /* saved */ false, /* saved */
false, /* policy */
false, /* preserve */
1 * 1024 * 1024 * 1024, /* minlimit */ 1 * 1024 * 1024 * 1024, /* minlimit */
NULL, /* fallback */ NULL, /* fallback */
{ RLIM_INFINITY, RLIM_INFINITY } /* newlimit */ { RLIM_INFINITY, RLIM_INFINITY } /* newlimit */
@@ -103,6 +109,8 @@ static struct saved_limit {
RLIMIT_CPU, RLIMIT_CPU,
true, /* override */ true, /* override */
false, /* saved */ false, /* saved */
false, /* policy */
false, /* preserve */
RLIM_INFINITY, /* minlimit */ RLIM_INFINITY, /* minlimit */
NULL, NULL,
{ RLIM_INFINITY, RLIM_INFINITY } { RLIM_INFINITY, RLIM_INFINITY }
@@ -112,6 +120,8 @@ static struct saved_limit {
RLIMIT_DATA, RLIMIT_DATA,
true, /* override */ true, /* override */
false, /* saved */ false, /* saved */
false, /* policy */
false, /* preserve */
1 * 1024 * 1024 * 1024, /* minlimit */ 1 * 1024 * 1024 * 1024, /* minlimit */
NULL, NULL,
{ RLIM_INFINITY, RLIM_INFINITY } { RLIM_INFINITY, RLIM_INFINITY }
@@ -121,6 +131,8 @@ static struct saved_limit {
RLIMIT_FSIZE, RLIMIT_FSIZE,
true, /* override */ true, /* override */
false, /* saved */ false, /* saved */
false, /* policy */
false, /* preserve */
RLIM_INFINITY, /* minlimit */ RLIM_INFINITY, /* minlimit */
NULL, NULL,
{ RLIM_INFINITY, RLIM_INFINITY } { RLIM_INFINITY, RLIM_INFINITY }
@@ -144,6 +156,8 @@ static struct saved_limit {
RLIMIT_NOFILE, RLIMIT_NOFILE,
true, /* override */ true, /* override */
false, /* saved */ false, /* saved */
false, /* policy */
false, /* preserve */
SUDO_OPEN_MAX, /* minlimit */ SUDO_OPEN_MAX, /* minlimit */
NULL, NULL,
{ SUDO_OPEN_MAX, RLIM_INFINITY } { SUDO_OPEN_MAX, RLIM_INFINITY }
@@ -154,6 +168,8 @@ static struct saved_limit {
RLIMIT_NPROC, RLIMIT_NPROC,
true, /* override */ true, /* override */
false, /* saved */ false, /* saved */
false, /* policy */
false, /* preserve */
RLIM_INFINITY, /* minlimit */ RLIM_INFINITY, /* minlimit */
NULL, NULL,
{ RLIM_INFINITY, RLIM_INFINITY } { RLIM_INFINITY, RLIM_INFINITY }
@@ -165,6 +181,8 @@ static struct saved_limit {
RLIMIT_RSS, RLIMIT_RSS,
true, /* override */ true, /* override */
false, /* saved */ false, /* saved */
false, /* policy */
false, /* preserve */
RLIM_INFINITY, /* minlimit */ RLIM_INFINITY, /* minlimit */
NULL, NULL,
{ RLIM_INFINITY, RLIM_INFINITY } { RLIM_INFINITY, RLIM_INFINITY }
@@ -175,6 +193,8 @@ static struct saved_limit {
RLIMIT_STACK, RLIMIT_STACK,
true, /* override */ true, /* override */
false, /* saved */ false, /* saved */
false, /* policy */
false, /* preserve */
RLIM_INFINITY, /* minlimit */ RLIM_INFINITY, /* minlimit */
&stack_fallback, &stack_fallback,
{ SUDO_STACK_MIN, RLIM_INFINITY } { SUDO_STACK_MIN, RLIM_INFINITY }
@@ -427,12 +447,218 @@ restore_limits(void)
debug_return; debug_return;
} }
static bool
store_rlimit(const char *str, rlim_t *val, bool soft)
{
const size_t inflen = sizeof("infinity") - 1;
debug_decl(store_rlimit, SUDO_DEBUG_UTIL);
if (isdigit((unsigned char)*str)) {
unsigned long long ullval = 0;
char *ep;
/* XXX - some systems may lack strtoull() */
errno = 0;
ullval = strtoull(str, &ep, 10);
if (str == ep || (errno == ERANGE && ullval == ULLONG_MAX))
debug_return_bool(false);
if (*ep == '\0' || (soft && *ep == ',')) {
*val = ullval;
debug_return_bool(true);
}
goto done;
}
if (strncmp(str, "infinity", inflen) == 0) {
if (str[inflen] == '\0' || (soft && str[inflen] == ',')) {
*val = RLIM_INFINITY;
debug_return_bool(true);
}
}
done:
debug_return_bool(false);
}
static bool
set_policy_rlimit(int resource, const char *val)
{
unsigned int idx;
debug_decl(set_policy_rlimit, SUDO_DEBUG_UTIL);
for (idx = 0; idx < nitems(saved_limits); idx++) {
struct saved_limit *lim = &saved_limits[idx];
const char *hard, *soft = val;
if (lim->resource != resource)
continue;
if (strcmp(val, "default") == 0) {
/* Use system-assigned limit set by begin_session(). */
lim->policy = false;
lim->preserve = false;
debug_return_bool(true);
}
if (strcmp(val, "user") == 0) {
/* Preserve invoking user's limit. */
lim->policy = false;
lim->preserve = true;
debug_return_bool(true);
}
/*
* Expect limit in the form "soft,hard" or "limit" (both soft+hard).
*/
hard = strchr(val, ',');
if (hard != NULL)
hard++;
else
hard = soft;
if (store_rlimit(soft, &lim->policylimit.rlim_cur, true) &&
store_rlimit(hard, &lim->policylimit.rlim_max, false)) {
lim->policy = true;
lim->preserve = false;
debug_return_bool(true);
}
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"%s: invalid rlimit: %s", lim->name, val);
debug_return_bool(false);
}
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"invalid resource limit: %d", resource);
debug_return_bool(false);
}
bool
parse_policy_rlimit(const char *str)
{
bool ret = false;
debug_decl(parse_policy_rlimit, SUDO_DEBUG_UTIL);
#ifdef RLIMIT_AS
if (strncmp(str, "as=", sizeof("as=") - 1) == 0) {
str += sizeof("as=") - 1;
ret = set_policy_rlimit(RLIMIT_AS, str);
} else
#endif
#ifdef RLIMIT_CORE
if (strncmp(str, "core=", sizeof("core=") - 1) == 0) {
str += sizeof("core=") - 1;
ret = set_policy_rlimit(RLIMIT_CORE, str);
} else
#endif
#ifdef RLIMIT_CPU
if (strncmp(str, "cpu=", sizeof("cpu=") - 1) == 0) {
str += sizeof("cpu=") - 1;
ret = set_policy_rlimit(RLIMIT_CPU, str);
} else
#endif
#ifdef RLIMIT_DATA
if (strncmp(str, "data=", sizeof("data=") - 1) == 0) {
str += sizeof("data=") - 1;
ret = set_policy_rlimit(RLIMIT_DATA, str);
} else
#endif
#ifdef RLIMIT_FSIZE
if (strncmp(str, "fsize=", sizeof("fsize=") - 1) == 0) {
str += sizeof("fsize=") - 1;
ret = set_policy_rlimit(RLIMIT_FSIZE, str);
} else
#endif
#ifdef RLIMIT_LOCKS
if (strncmp(str, "locks=", sizeof("locks=") - 1) == 0) {
str += sizeof("locks=") - 1;
ret = set_policy_rlimit(RLIMIT_LOCKS, str);
} else
#endif
#ifdef RLIMIT_MEMLOCK
if (strncmp(str, "memlock=", sizeof("memlock=") - 1) == 0) {
str += sizeof("memlock=") - 1;
ret = set_policy_rlimit(RLIMIT_MEMLOCK, str);
} else
#endif
#ifdef RLIMIT_NOFILE
if (strncmp(str, "nofile=", sizeof("nofile=") - 1) == 0) {
str += sizeof("nofile=") - 1;
ret = set_policy_rlimit(RLIMIT_NOFILE, str);
} else
#endif
#ifdef RLIMIT_NPROC
if (strncmp(str, "nproc=", sizeof("nproc=") - 1) == 0) {
str += sizeof("nproc=") - 1;
ret = set_policy_rlimit(RLIMIT_NPROC, str);
} else
#endif
#ifdef RLIMIT_RSS
if (strncmp(str, "rss=", sizeof("rss=") - 1) == 0) {
str += sizeof("rss=") - 1;
ret = set_policy_rlimit(RLIMIT_RSS, str);
} else
#endif
#ifdef RLIMIT_STACK
if (strncmp(str, "stack=", sizeof("stack=") - 1) == 0) {
str += sizeof("stack=") - 1;
ret = set_policy_rlimit(RLIMIT_STACK, str);
}
#endif
debug_return_bool(ret);
}
/*
* Set resource limits as specified by the security policy (if any).
* This should be run as part of the session setup but after PAM,
* login.conf, etc.
*/
void
set_policy_rlimits(void)
{
unsigned int idx;
debug_decl(set_policy_rlimits, SUDO_DEBUG_UTIL);
for (idx = 0; idx < nitems(saved_limits); idx++) {
struct saved_limit *lim = &saved_limits[idx];
struct rlimit *rl;
int rc;
if (!lim->policy && (!lim->preserve || !lim->saved))
continue;
rl = lim->preserve ? &lim->oldlimit : &lim->policylimit;
if ((rc = setrlimit(lim->resource, rl)) == 0) {
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
"setrlimit(%s, [%lld, %lld])", lim->name,
(long long)rl->rlim_cur, (long long)rl->rlim_max);
continue;
}
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"setrlimit(%s, [%lld, %lld])", lim->name,
(long long)rl->rlim_cur, (long long)rl->rlim_max);
if (rl->rlim_cur > lim->oldlimit.rlim_max || rl->rlim_max > lim->oldlimit.rlim_max) {
/* Try setting policy rlim_cur to old rlim_max. */
if (rl->rlim_cur > lim->oldlimit.rlim_max)
rl->rlim_cur = lim->oldlimit.rlim_max;
if (rl->rlim_max > lim->oldlimit.rlim_max)
rl->rlim_max = lim->oldlimit.rlim_max;
if ((rc = setrlimit(lim->resource, rl)) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"setrlimit(%s, [%lld, %lld])", lim->name,
(long long)rl->rlim_cur, (long long)rl->rlim_max);
}
}
if (rc == -1)
sudo_warn("setrlimit(%s)", lim->name);
}
debug_return;
}
int int
serialize_limits(char **info, size_t info_max) serialize_rlimits(char **info, size_t info_max)
{ {
char *str; char *str;
unsigned int idx, nstored = 0; unsigned int idx, nstored = 0;
debug_decl(serialize_limits, SUDO_DEBUG_UTIL); debug_decl(serialize_rlimits, SUDO_DEBUG_UTIL);
for (idx = 0; idx < nitems(saved_limits); idx++) { for (idx = 0; idx < nitems(saved_limits); idx++) {
const struct saved_limit *lim = &saved_limits[idx]; const struct saved_limit *lim = &saved_limits[idx];

View File

@@ -620,7 +620,7 @@ get_user_info(struct user_details *ud)
if (asprintf(&info[++i], "cols=%d", ud->ts_cols) == -1) if (asprintf(&info[++i], "cols=%d", ud->ts_cols) == -1)
goto oom; goto oom;
n = serialize_limits(&info[i + 1], info_max - (i + 1)); n = serialize_rlimits(&info[i + 1], info_max - (i + 1));
if (n == -1) if (n == -1)
goto oom; goto oom;
i += n; i += n;
@@ -753,6 +753,10 @@ command_info_to_details(char * const info[], struct command_details *details)
} }
break; break;
case 'r': case 'r':
if (strncmp("rlimit_", info[i], sizeof("rlimit_") - 1) == 0) {
parse_policy_rlimit(info[i] + sizeof("rlimit_") - 1);
break;
}
if (strncmp("runas_egid=", info[i], sizeof("runas_egid=") - 1) == 0) { if (strncmp("runas_egid=", info[i], sizeof("runas_egid=") - 1) == 0) {
cp = info[i] + sizeof("runas_egid=") - 1; cp = info[i] + sizeof("runas_egid=") - 1;
id = sudo_strtoid(cp, &errstr); id = sudo_strtoid(cp, &errstr);
@@ -1378,6 +1382,7 @@ policy_init_session(struct command_details *details)
details->info); details->info);
} }
} }
done: done:
debug_return_int(ret); debug_return_int(ret);
} }

View File

@@ -300,8 +300,10 @@ int tcsetpgrp_nobg(int fd, pid_t pgrp_id);
void disable_coredump(); void disable_coredump();
void restore_limits(void); void restore_limits(void);
void restore_nproc(void); void restore_nproc(void);
void set_policy_rlimits(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); int serialize_rlimits(char **info, size_t info_max);
bool parse_policy_rlimit(const char *str);
#endif /* SUDO_SUDO_H */ #endif /* SUDO_SUDO_H */