#include <config.h>
#include <sys/resource.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef __linux__
# include <sys/prctl.h>
#endif
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <sudo.h>
#if defined(OPEN_MAX) && OPEN_MAX > 256
# define SUDO_OPEN_MAX OPEN_MAX
#else
# define SUDO_OPEN_MAX 256
#endif
#ifdef __LP64__
# define SUDO_STACK_MIN (4 * 1024 * 1024)
#else
# define SUDO_STACK_MIN (2 * 1024 * 1024)
#endif
#ifdef HAVE_SETRLIMIT64
# define getrlimit(a, b) getrlimit64((a), (b))
# define setrlimit(a, b) setrlimit64((a), (b))
# define rlimit rlimit64
# define rlim_t rlim64_t
# undef RLIM_INFINITY
# define RLIM_INFINITY RLIM64_INFINITY
#endif
#if !defined(RLIMIT_AS) && defined(RLIMIT_VMEM)
# define RLIMIT_AS RLIMIT_VMEM
#endif
static struct rlimit stack_fallback = { SUDO_STACK_MIN, 65532 * 1024 };
static struct saved_limit {
const char *name;
int resource;
bool override;
bool saved;
bool policy;
bool preserve;
rlim_t minlimit;
struct rlimit *fallback;
struct rlimit newlimit;
struct rlimit oldlimit;
struct rlimit policylimit;
} saved_limits[] = {
#ifdef RLIMIT_AS
{
"rlimit_as",
RLIMIT_AS,
true,
false,
false,
false,
1 * 1024 * 1024 * 1024,
NULL,
{ RLIM_INFINITY, RLIM_INFINITY }
},
#endif
{
"rlimit_core",
RLIMIT_CORE,
false
},
{
"rlimit_cpu",
RLIMIT_CPU,
true,
false,
false,
false,
RLIM_INFINITY,
NULL,
{ RLIM_INFINITY, RLIM_INFINITY }
},
{
"rlimit_data",
RLIMIT_DATA,
true,
false,
false,
false,
1 * 1024 * 1024 * 1024,
NULL,
{ RLIM_INFINITY, RLIM_INFINITY }
},
{
"rlimit_fsize",
RLIMIT_FSIZE,
true,
false,
false,
false,
RLIM_INFINITY,
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,
false,
false,
SUDO_OPEN_MAX,
NULL,
{ SUDO_OPEN_MAX, RLIM_INFINITY }
},
#ifdef RLIMIT_NPROC
{
"rlimit_nproc",
RLIMIT_NPROC,
true,
false,
false,
false,
RLIM_INFINITY,
NULL,
{ RLIM_INFINITY, RLIM_INFINITY }
},
#endif
#ifdef RLIMIT_RSS
{
"rlimit_rss",
RLIMIT_RSS,
true,
false,
false,
false,
RLIM_INFINITY,
NULL,
{ RLIM_INFINITY, RLIM_INFINITY }
},
#endif
{
"rlimit_stack",
RLIMIT_STACK,
true,
false,
false,
false,
SUDO_STACK_MIN,
&stack_fallback,
{ SUDO_STACK_MIN, RLIM_INFINITY }
}
};
static struct rlimit corelimit;
static bool coredump_disabled;
#ifdef __linux__
static struct rlimit nproclimit;
static int dumpflag;
#endif
void
disable_coredump(void)
{
debug_decl(disable_coredump, SUDO_DEBUG_UTIL);
if (getrlimit(RLIMIT_CORE, &corelimit) == 0) {
struct rlimit rl = corelimit;
rl.rlim_cur = 0;
sudo_debug_printf(SUDO_DEBUG_INFO,
"RLIMIT_CORE [%lld, %lld] -> [%lld, %lld]",
(long long)corelimit.rlim_cur, (long long)corelimit.rlim_max,
(long long)rl.rlim_cur, (long long)rl.rlim_max);
if (setrlimit(RLIMIT_CORE, &rl) == -1) {
sudo_warn("setrlimit(RLIMIT_CORE)");
} else {
coredump_disabled = true;
#ifdef __linux__
if ((dumpflag = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)");
dumpflag = 0;
}
if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"prctl(PR_SET_DUMPABLE, 0, 0, 0, 0)");
}
#endif
}
} else {
sudo_warn("getrlimit(RLIMIT_CORE)");
}
debug_return;
}
static void
restore_coredump(void)
{
debug_decl(restore_coredump, SUDO_DEBUG_UTIL);
if (coredump_disabled) {
if (setrlimit(RLIMIT_CORE, &corelimit) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"setrlimit(RLIMIT_CORE, [%lld, %lld])",
(long long)corelimit.rlim_cur, (long long)corelimit.rlim_max);
}
#ifdef __linux__
if (prctl(PR_SET_DUMPABLE, dumpflag, 0, 0, 0) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"prctl(PR_SET_DUMPABLE, %d, 0, 0, 0)", dumpflag);
}
#endif
}
debug_return;
}
void
unlimit_nproc(void)
{
#ifdef __linux__
struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY };
debug_decl(unlimit_nproc, SUDO_DEBUG_UTIL);
if (getrlimit(RLIMIT_NPROC, &nproclimit) != 0)
sudo_warn("getrlimit(RLIMIT_NPROC)");
sudo_debug_printf(SUDO_DEBUG_INFO, "RLIMIT_NPROC [%lld, %lld] -> [inf, inf]",
(long long)nproclimit.rlim_cur, (long long)nproclimit.rlim_max);
if (setrlimit(RLIMIT_NPROC, &rl) == -1) {
rl.rlim_cur = rl.rlim_max = nproclimit.rlim_max;
sudo_debug_printf(SUDO_DEBUG_INFO,
"RLIMIT_NPROC [%lld, %lld] -> [%lld, %lld]",
(long long)nproclimit.rlim_cur, (long long)nproclimit.rlim_max,
(long long)rl.rlim_cur, (long long)rl.rlim_max);
if (setrlimit(RLIMIT_NPROC, &rl) != 0)
sudo_warn("setrlimit(RLIMIT_NPROC)");
}
debug_return;
#endif
}
void
restore_nproc(void)
{
#ifdef __linux__
debug_decl(restore_nproc, SUDO_DEBUG_UTIL);
if (setrlimit(RLIMIT_NPROC, &nproclimit) != 0) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"setrlimit(RLIMIT_NPROC, [%lld, %lld])",
(long long)nproclimit.rlim_cur, (long long)nproclimit.rlim_max);
}
debug_return;
#endif
}
void
unlimit_sudo(void)
{
unsigned int idx;
int pass, rc;
debug_decl(unlimit_sudo, SUDO_DEBUG_UTIL);
for (idx = 0; idx < nitems(saved_limits); idx++) {
struct saved_limit *lim = &saved_limits[idx];
if (getrlimit(lim->resource, &lim->oldlimit) == -1)
continue;
sudo_debug_printf(SUDO_DEBUG_INFO,
"getrlimit(%s) -> [%lld, %lld]", lim->name,
(long long)lim->oldlimit.rlim_cur,
(long long)lim->oldlimit.rlim_max);
lim->saved = true;
if (lim->minlimit != RLIM_INFINITY) {
if (lim->oldlimit.rlim_cur >= lim->minlimit)
lim->override = false;
}
if (!lim->override)
continue;
for (pass = 0; pass < 2; pass++) {
if (lim->newlimit.rlim_cur != RLIM_INFINITY) {
if (lim->oldlimit.rlim_cur == RLIM_INFINITY ||
lim->oldlimit.rlim_cur > lim->newlimit.rlim_cur)
lim->newlimit.rlim_cur = lim->oldlimit.rlim_cur;
}
if (lim->newlimit.rlim_max != RLIM_INFINITY) {
if (lim->oldlimit.rlim_max == RLIM_INFINITY ||
lim->oldlimit.rlim_max > lim->newlimit.rlim_max)
lim->newlimit.rlim_max = lim->oldlimit.rlim_max;
}
if ((rc = setrlimit(lim->resource, &lim->newlimit)) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"setrlimit(%s, [%lld, %lld])", lim->name,
(long long)lim->newlimit.rlim_cur,
(long long)lim->newlimit.rlim_max);
if (pass == 0 && lim->fallback != NULL) {
lim->newlimit.rlim_cur = lim->fallback->rlim_cur;
lim->newlimit.rlim_max = lim->fallback->rlim_max;
continue;
}
}
break;
}
if (rc == -1) {
lim->newlimit.rlim_cur = lim->oldlimit.rlim_max;
lim->newlimit.rlim_max = lim->oldlimit.rlim_max;
if ((rc = setrlimit(lim->resource, &lim->newlimit)) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"setrlimit(%s, [%lld, %lld])", lim->name,
(long long)lim->newlimit.rlim_cur,
(long long)lim->newlimit.rlim_max);
}
}
if (rc == -1)
sudo_warn("setrlimit(%s)", lim->name);
}
debug_return;
}
void
restore_limits(void)
{
unsigned int idx;
debug_decl(restore_limits, SUDO_DEBUG_UTIL);
for (idx = 0; idx < nitems(saved_limits); idx++) {
struct saved_limit *lim = &saved_limits[idx];
if (lim->override && lim->saved) {
struct rlimit rl = lim->oldlimit;
int i, rc;
for (i = 0; i < 10; i++) {
rc = setrlimit(lim->resource, &rl);
if (rc != -1 || errno != EINVAL)
break;
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 (rl.rlim_cur > LLONG_MAX / 2)
break;
rl.rlim_cur *= 2;
if (lim->newlimit.rlim_cur != RLIM_INFINITY &&
rl.rlim_cur > lim->newlimit.rlim_cur) {
rl.rlim_cur = lim->newlimit.rlim_cur;
}
if (rl.rlim_max != RLIM_INFINITY &&
rl.rlim_cur > rl.rlim_max) {
rl.rlim_max = rl.rlim_cur;
}
rc = setrlimit(lim->resource, &rl);
if (rc != -1 || errno != EINVAL)
break;
}
if (rc == -1)
sudo_warn("setrlimit(%s)", lim->name);
}
}
restore_coredump();
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;
errno = 0;
#ifdef HAVE_STRTOULL
ullval = strtoull(str, &ep, 10);
if (str == ep || (errno == ERANGE && ullval == ULLONG_MAX))
debug_return_bool(false);
#else
ullval = strtoul(str, &ep, 10);
if (str == ep || (errno == ERANGE && ullval == ULONG_MAX))
debug_return_bool(false);
#endif
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) {
lim->policy = false;
lim->preserve = false;
debug_return_bool(true);
}
if (strcmp(val, "user") == 0) {
lim->policy = false;
lim->preserve = true;
debug_return_bool(true);
}
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);
}
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) {
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;
}
size_t
serialize_rlimits(char **info, size_t info_max)
{
char *str;
size_t idx, nstored = 0;
debug_decl(serialize_rlimits, 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[STRLEN_MAX_UNSIGNED(unsigned long long) + 1];
char maxlim[STRLEN_MAX_UNSIGNED(unsigned long long) + 1];
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_size_t(nstored);
oom:
while (nstored)
free(info[--nstored]);
debug_return_size_t((size_t)-1);
}