diff --git a/configure.ac b/configure.ac index af0455827..287d57f55 100644 --- a/configure.ac +++ b/configure.ac @@ -639,7 +639,7 @@ AC_CHECK_DECLS([PR_SET_NO_NEW_PRIVS], [], [], [#include ]) AC_CHECK_DECLS([PR_GET_NO_NEW_PRIVS], [], [], [#include ]) # Check for some headers -AC_CHECK_HEADERS([sys/signalfd.h pty.h ifaddrs.h sys/memfd.h sys/personality.h utmpx.h sys/timerfd.h]) +AC_CHECK_HEADERS([sys/signalfd.h pty.h ifaddrs.h sys/memfd.h sys/personality.h utmpx.h sys/timerfd.h sys/resource.h]) # lookup major()/minor()/makedev() AC_HEADER_MAJOR diff --git a/src/lxc/attach.c b/src/lxc/attach.c index 9497148a3..968301760 100644 --- a/src/lxc/attach.c +++ b/src/lxc/attach.c @@ -894,6 +894,11 @@ int lxc_attach(const char* name, const char* lxcpath, lxc_attach_exec_t exec_fun goto on_error; } + /* Setup resource limits */ + if (!lxc_list_empty(&init_ctx->container->lxc_conf->limits) && setup_resource_limits(&init_ctx->container->lxc_conf->limits, pid)) { + goto on_error; + } + /* Open /proc before setns() to the containers namespace so we * don't rely on any information from inside the container. */ diff --git a/src/lxc/conf.c b/src/lxc/conf.c index d0c51f659..c8c2a5452 100644 --- a/src/lxc/conf.c +++ b/src/lxc/conf.c @@ -239,6 +239,11 @@ struct caps_opt { int value; }; +struct limit_opt { + char *name; + int value; +}; + /* * The lxc_conf of the container currently being worked on in an * API call @@ -371,6 +376,57 @@ static struct caps_opt caps_opt[] = { static struct caps_opt caps_opt[] = {}; #endif +static struct limit_opt limit_opt[] = { +#ifdef RLIMIT_AS + { "as", RLIMIT_AS }, +#endif +#ifdef RLIMIT_CORE + { "core", RLIMIT_CORE }, +#endif +#ifdef RLIMIT_CPU + { "cpu", RLIMIT_CPU }, +#endif +#ifdef RLIMIT_DATA + { "data", RLIMIT_DATA }, +#endif +#ifdef RLIMIT_FSIZE + { "fsize", RLIMIT_FSIZE }, +#endif +#ifdef RLIMIT_LOCKS + { "locks", RLIMIT_LOCKS }, +#endif +#ifdef RLIMIT_MEMLOCK + { "memlock", RLIMIT_MEMLOCK }, +#endif +#ifdef RLIMIT_MSGQUEUE + { "msgqueue", RLIMIT_MSGQUEUE }, +#endif +#ifdef RLIMIT_NICE + { "nice", RLIMIT_NICE }, +#endif +#ifdef RLIMIT_NOFILE + { "nofile", RLIMIT_NOFILE }, +#endif +#ifdef RLIMIT_NPROC + { "nproc", RLIMIT_NPROC }, +#endif +#ifdef RLIMIT_RSS + { "rss", RLIMIT_RSS }, +#endif +#ifdef RLIMIT_RTPRIO + { "rtprio", RLIMIT_RTPRIO }, +#endif +#ifdef RLIMIT_RTTIME + { "rttime", RLIMIT_RTTIME }, +#endif +#ifdef RLIMIT_SIGPENDING + { "sigpending", RLIMIT_SIGPENDING }, +#endif +#ifdef RLIMIT_STACK + { "stack", RLIMIT_STACK }, +#endif +}; + static int run_buffer(char *buffer) { struct lxc_popen_FILE *f; @@ -2473,6 +2529,45 @@ static int setup_network(struct lxc_list *network) return 0; } +static int parse_resource(const char *res) { + size_t i; + int resid = -1; + + for (i = 0; i < sizeof(limit_opt)/sizeof(limit_opt[0]); ++i) { + if (strcmp(res, limit_opt[i].name) == 0) + return limit_opt[i].value; + } + + /* try to see if it's numeric, so the user may specify + * resources that the running kernel knows about but + * we don't */ + if (lxc_safe_int(res, &resid) == 0) + return resid; + return -1; +} + +int setup_resource_limits(struct lxc_list *limits, pid_t pid) { + struct lxc_list *it; + struct lxc_limit *lim; + int resid; + + lxc_list_for_each(it, limits) { + lim = it->elem; + + resid = parse_resource(lim->resource); + if (resid < 0) { + ERROR("unknown resource %s", lim->resource); + return -1; + } + + if (prlimit(pid, resid, &lim->limit, NULL) != 0) { + ERROR("failed to set limit %s: %s", lim->resource, strerror(errno)); + return -1; + } + } + return 0; +} + /* try to move physical nics to the init netns */ void lxc_restore_phys_nics_to_netns(int netnsfd, struct lxc_conf *conf) { @@ -2559,6 +2654,7 @@ struct lxc_conf *lxc_conf_init(void) lxc_list_init(&new->includes); lxc_list_init(&new->aliens); lxc_list_init(&new->environment); + lxc_list_init(&new->limits); for (i=0; ihooks[i]); lxc_list_init(&new->groups); @@ -4178,6 +4274,31 @@ int lxc_clear_cgroups(struct lxc_conf *c, const char *key) return 0; } +int lxc_clear_limits(struct lxc_conf *c, const char *key) +{ + struct lxc_list *it, *next; + bool all = false; + const char *k = NULL; + + if (strcmp(key, "lxc.limit") == 0) + all = true; + else if (strncmp(key, "lxc.limit.", sizeof("lxc.limit.")-1) == 0) + k = key + sizeof("lxc.limit.")-1; + else + return -1; + + lxc_list_for_each_safe(it, &c->limits, next) { + struct lxc_limit *lim = it->elem; + if (!all && strcmp(lim->resource, k) != 0) + continue; + lxc_list_del(it); + free(lim->resource); + free(lim); + free(it); + } + return 0; +} + int lxc_clear_groups(struct lxc_conf *c) { struct lxc_list *it,*next; @@ -4320,6 +4441,7 @@ void lxc_conf_free(struct lxc_conf *conf) lxc_clear_includes(conf); lxc_clear_aliens(conf); lxc_clear_environment(conf); + lxc_clear_limits(conf, "lxc.limit"); free(conf); } diff --git a/src/lxc/conf.h b/src/lxc/conf.h index b7d15cbd2..7c99048e4 100644 --- a/src/lxc/conf.h +++ b/src/lxc/conf.h @@ -30,6 +30,9 @@ #include #include #include +#if HAVE_SYS_RESOURCE_H +#include +#endif #include #include "list.h" @@ -149,6 +152,23 @@ struct lxc_cgroup { char *value; }; +#if !HAVE_SYS_RESOURCE_H +# define RLIM_INFINITY ((unsigned long)-1) +struct rlimit { + unsigned long rlim_cur; + unsigned long rlim_max; +}; +#endif +/* + * Defines a structure to configure resource limits to set via setrlimit(). + * @resource : the resource name in lowercase without the RLIMIT_ prefix + * @limit : the limit to set + */ +struct lxc_limit { + char *resource; + struct rlimit limit; +}; + enum idtype { ID_TYPE_UID, ID_TYPE_GID @@ -385,6 +405,9 @@ struct lxc_conf { /* Whether PR_SET_NO_NEW_PRIVS will be set for the container. */ bool no_new_privs; + + /* RLIMIT_* limits */ + struct lxc_list limits; }; #ifdef HAVE_TLS @@ -428,6 +451,7 @@ extern int lxc_clear_hooks(struct lxc_conf *c, const char *key); extern int lxc_clear_idmaps(struct lxc_conf *c); extern int lxc_clear_groups(struct lxc_conf *c); extern int lxc_clear_environment(struct lxc_conf *c); +extern int lxc_clear_limits(struct lxc_conf *c, const char *key); extern int lxc_delete_autodev(struct lxc_handler *handler); extern int do_rootfs_setup(struct lxc_conf *conf, const char *name, @@ -440,6 +464,8 @@ extern int do_rootfs_setup(struct lxc_conf *conf, const char *name, struct cgroup_process_info; extern int lxc_setup(struct lxc_handler *handler); +extern int setup_resource_limits(struct lxc_list *limits, pid_t pid); + extern void lxc_restore_phys_nics_to_netns(int netnsfd, struct lxc_conf *conf); extern int find_unmapped_nsuid(struct lxc_conf *conf, enum idtype idtype); diff --git a/src/lxc/confile.c b/src/lxc/confile.c index db0aa33ec..7e2f265ae 100644 --- a/src/lxc/confile.c +++ b/src/lxc/confile.c @@ -121,6 +121,7 @@ static int config_init_uid(const char *, const char *, struct lxc_conf *); static int config_init_gid(const char *, const char *, struct lxc_conf *); static int config_ephemeral(const char *, const char *, struct lxc_conf *); static int config_no_new_privs(const char *, const char *, struct lxc_conf *); +static int config_limit(const char *, const char *, struct lxc_conf *); static struct lxc_config_t config[] = { @@ -195,6 +196,7 @@ static struct lxc_config_t config[] = { { "lxc.ephemeral", config_ephemeral }, { "lxc.syslog", config_syslog }, { "lxc.no_new_privs", config_no_new_privs }, + { "lxc.limit", config_limit }, }; struct signame { @@ -1580,6 +1582,110 @@ out: return -1; } +static bool parse_limit_value(const char **value, unsigned long *res) { + char *endptr = NULL; + + if (strncmp(*value, "unlimited", sizeof("unlimited")-1) == 0) { + *res = RLIM_INFINITY; + *value += sizeof("unlimited")-1; + return true; + } + + errno = 0; + *res = strtoul(*value, &endptr, 10); + if (errno || !endptr) + return false; + *value = endptr; + + return true; +} + +static int config_limit(const char *key, const char *value, + struct lxc_conf *lxc_conf) +{ + struct lxc_list *limlist = NULL; + struct lxc_limit *limelem = NULL; + struct lxc_list *iter; + struct rlimit limit; + unsigned long limit_value; + + if (!value || strlen(value) == 0) + return lxc_clear_limits(lxc_conf, key); + + if (strncmp(key, "lxc.limit.", sizeof("lxc.limit.")-1) != 0) + return -1; + + key += sizeof("lxc.limit.")-1; + + /* soft limit comes first in the value */ + if (!parse_limit_value(&value, &limit_value)) + return -1; + limit.rlim_cur = limit_value; + + /* skip spaces and a colon */ + while (isspace(*value)) + ++value; + if (*value == ':') + ++value; + else if (*value) /* any other character is an error here */ + return -1; + while (isspace(*value)) + ++value; + + /* optional hard limit */ + if (*value) { + if (!parse_limit_value(&value, &limit_value)) + return -1; + limit.rlim_max = limit_value; + /* check for trailing garbage */ + while (isspace(*value)) + ++value; + if (*value) + return -1; + } else { + /* a single value sets both hard and soft limit */ + limit.rlim_max = limit.rlim_cur; + } + + /* find existing list element */ + lxc_list_for_each(iter, &lxc_conf->limits) { + limelem = iter->elem; + if (!strcmp(key, limelem->resource)) { + limelem->limit = limit; + return 0; + } + } + + /* allocate list element */ + limlist = malloc(sizeof(*limlist)); + if (!limlist) + goto out; + + limelem = malloc(sizeof(*limelem)); + if (!limelem) + goto out; + memset(limelem, 0, sizeof(*limelem)); + + limelem->resource = strdup(key); + if (!limelem->resource) + goto out; + limelem->limit = limit; + + limlist->elem = limelem; + + lxc_list_add_tail(&lxc_conf->limits, limlist); + + return 0; + +out: + free(limlist); + if (limelem) { + free(limelem->resource); + free(limelem); + } + return -1; +} + static int config_idmap(const char *key, const char *value, struct lxc_conf *lxc_conf) { char *token = "lxc.id_map"; @@ -2313,6 +2419,55 @@ static int lxc_get_cgroup_entry(struct lxc_conf *c, char *retv, int inlen, return fulllen; } +/* + * If you ask for a specific value, i.e. lxc.limit.nofile, then just the value + * will be printed. If you ask for 'lxc.limit', then all limit entries will be + * printed, in 'lxc.limit.resource = value' format. + */ +static int lxc_get_limit_entry(struct lxc_conf *c, char *retv, int inlen, + const char *key) +{ + int fulllen = 0, len; + int all = 0; + struct lxc_list *it; + + if (!retv) + inlen = 0; + else + memset(retv, 0, inlen); + + if (strcmp(key, "all") == 0) + all = 1; + + lxc_list_for_each(it, &c->limits) { + char buf[LXC_NUMSTRLEN64*2+2]; /* 2 colon separated 64 bit integers or the word 'unlimited' */ + int partlen; + struct lxc_limit *lim = it->elem; + + if (lim->limit.rlim_cur == RLIM_INFINITY) { + memcpy(buf, "unlimited", sizeof("unlimited")); + partlen = sizeof("unlimited")-1; + } else { + partlen = sprintf(buf, "%lu", lim->limit.rlim_cur); + } + if (lim->limit.rlim_cur != lim->limit.rlim_max) { + if (lim->limit.rlim_max == RLIM_INFINITY) { + memcpy(buf+partlen, ":unlimited", sizeof(":unlimited")); + } else { + sprintf(buf+partlen, ":%lu", lim->limit.rlim_max); + } + } + + if (all) { + strprint(retv, inlen, "lxc.limit.%s = %s\n", lim->resource, buf); + } else if (strcmp(lim->resource, key) == 0) { + strprint(retv, inlen, "%s", buf); + } + } + + return fulllen; +} + static int lxc_get_item_hooks(struct lxc_conf *c, char *retv, int inlen, const char *key) { @@ -2678,6 +2833,10 @@ int lxc_get_config_item(struct lxc_conf *c, const char *key, char *retv, v = c->syslog; else if (strcmp(key, "lxc.no_new_privs") == 0) return lxc_get_conf_int(c, retv, inlen, c->no_new_privs); + else if (strcmp(key, "lxc.limit") == 0) // all limits + return lxc_get_limit_entry(c, retv, inlen, "all"); + else if (strncmp(key, "lxc.limit.", 10) == 0) // specific limit + return lxc_get_limit_entry(c, retv, inlen, key + 10); else return -1; if (!v) @@ -2711,6 +2870,8 @@ int lxc_clear_config_item(struct lxc_conf *c, const char *key) return lxc_clear_environment(c); else if (strncmp(key, "lxc.id_map", 10) == 0) return lxc_clear_idmaps(c); + else if (strncmp(key, "lxc.limit", 9) == 0) + return lxc_clear_limits(c, key); return -1; } diff --git a/src/lxc/start.c b/src/lxc/start.c index e586881a9..fa1ade274 100644 --- a/src/lxc/start.c +++ b/src/lxc/start.c @@ -1261,6 +1261,11 @@ static int lxc_spawn(struct lxc_handler *handler) if (lxc_sync_barrier_child(handler, LXC_SYNC_POST_CONFIGURE)) goto out_delete_net; + if (!lxc_list_empty(&handler->conf->limits) && setup_resource_limits(&handler->conf->limits, handler->pid)) { + ERROR("failed to setup resource limits for '%s'", name); + return -1; + } + if (!cgroup_setup_limits(handler, true)) { ERROR("Failed to setup the devices cgroup for container \"%s\".", name); goto out_delete_net;