From c6d09e15c5a25fc1a16859e498af2af17b5dfb2f Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Fri, 4 Nov 2016 10:19:07 +0100 Subject: [PATCH 1/4] conf: implement resource limits This adds lxc.limit. options consisting of one or two colon separated numerical values (soft and optional hard limit). If only one number is specified it'll be used for both soft and hard limit. Additionally the word 'unlimited' can be used instead of numbers. Eg. lxc.limit.nofile = 30000:32768 lxc.limit.stack = unlimited Signed-off-by: Wolfgang Bumiller --- configure.ac | 2 +- src/lxc/attach.c | 5 ++ src/lxc/conf.c | 122 +++++++++++++++++++++++++++++++++++ src/lxc/conf.h | 26 ++++++++ src/lxc/confile.c | 161 ++++++++++++++++++++++++++++++++++++++++++++++ src/lxc/start.c | 5 ++ 6 files changed, 320 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index af04558275..287d57f559 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 9497148a31..9683017603 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 d0c51f6597..c8c2a5452f 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 b7d15cbd2e..7c99048e49 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 db0aa33ec0..7e2f265ae1 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 @@ static int config_cgroup(const char *key, const char *value, 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 e586881a91..fa1ade274d 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; From 93f9e90d72da6f71e61b4812b94bb4065548cfa5 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Fri, 4 Nov 2016 12:03:28 +0100 Subject: [PATCH 2/4] doc: add lxc.limit to lxc.container.conf Signed-off-by: Wolfgang Bumiller --- doc/lxc.container.conf.sgml.in | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/doc/lxc.container.conf.sgml.in b/doc/lxc.container.conf.sgml.in index fcccd8ba99..43f31397b9 100644 --- a/doc/lxc.container.conf.sgml.in +++ b/doc/lxc.container.conf.sgml.in @@ -1183,6 +1183,40 @@ proc proc proc nodev,noexec,nosuid 0 0 + + Resource limits + + The soft and hard resource limits for the container can be changed. + Unprivileged containers can only lower them. Resources which are not + explicitly specified will be inherited. + + + + + + + + + Specify the resource limit to be set. A limit is specified as two + colon separated values which are either numeric or the word + 'unlimited'. A single value can be used as a shortcut to set both + soft and hard limit to the same value. The permitted names the + "RLIMIT_" resource names in lowercase without the "RLIMIT_" + prefix, eg. RLIMIT_NOFILE should be specified as "nofile". See + + setrlimit + 2 + . + If used with no value, lxc will clear the resource limit + specified up to this point. A resource with no explicitly + configured limitation will be inherited from the process starting + up the container. + + + + + + Apparmor profile From fe17b651c529c8ed3c1b67e8fb4eabe5b099ca98 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Fri, 4 Nov 2016 11:45:47 +0100 Subject: [PATCH 3/4] test: resource limit config entries Signed-off-by: Wolfgang Bumiller --- src/tests/get_item.c | 64 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/tests/get_item.c b/src/tests/get_item.c index eb2274eef3..cb4ba42eb2 100644 --- a/src/tests/get_item.c +++ b/src/tests/get_item.c @@ -174,6 +174,70 @@ int main(int argc, char *argv[]) } printf("lxc.mount.entry returned %d %s\n", ret, v2); + ret = c->get_config_item(c, "lxc.limit", v3, 2047); + if (ret != 0) { + fprintf(stderr, "%d: get_config_item(limit) returned %d\n", __LINE__, ret); + goto out; + } + + if (!c->set_config_item(c, "lxc.limit.nofile", "1234:unlimited")) { + fprintf(stderr, "%d: failed to set limit.nofile\n", __LINE__); + goto out; + } + ret = c->get_config_item(c, "lxc.limit.nofile", v2, 255); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item(lxc.limit.nofile) returned %d\n", __LINE__, ret); + goto out; + } + if (strcmp(v2, "1234:unlimited")) { + fprintf(stderr, "%d: lxc.limit.nofile returned wrong value: %d %s not 14 1234:unlimited\n", __LINE__, ret, v2); + goto out; + } + printf("lxc.limit.nofile returned %d %s\n", ret, v2); + + if (!c->set_config_item(c, "lxc.limit.stack", "unlimited")) { + fprintf(stderr, "%d: failed to set limit.stack\n", __LINE__); + goto out; + } + ret = c->get_config_item(c, "lxc.limit.stack", v2, 255); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item(lxc.limit.stack) returned %d\n", __LINE__, ret); + goto out; + } + if (strcmp(v2, "unlimited")) { + fprintf(stderr, "%d: lxc.limit.stack returned wrong value: %d %s not 9 unlimited\n", __LINE__, ret, v2); + goto out; + } + printf("lxc.limit.stack returned %d %s\n", ret, v2); + +#define LIMIT_STACK "lxc.limit.stack = unlimited\n" +#define ALL_LIMITS "lxc.limit.nofile = 1234:unlimited\n" LIMIT_STACK + ret = c->get_config_item(c, "lxc.limit", v3, 2047); + if (ret != sizeof(ALL_LIMITS)-1) { + fprintf(stderr, "%d: get_config_item(limit) returned %d\n", __LINE__, ret); + goto out; + } + if (strcmp(v3, ALL_LIMITS)) { + fprintf(stderr, "%d: lxc.limit returned wrong value: %d %s not %d %s\n", __LINE__, ret, v3, (int)sizeof(ALL_LIMITS)-1, ALL_LIMITS); + goto out; + } + printf("lxc.limit returned %d %s\n", ret, v3); + + if (!c->clear_config_item(c, "lxc.limit.nofile")) { + fprintf(stderr, "%d: failed clearing limit.nofile\n", __LINE__); + goto out; + } + ret = c->get_config_item(c, "lxc.limit", v3, 2047); + if (ret != sizeof(LIMIT_STACK)-1) { + fprintf(stderr, "%d: get_config_item(limit) returned %d\n", __LINE__, ret); + goto out; + } + if (strcmp(v3, LIMIT_STACK)) { + fprintf(stderr, "%d: lxc.limit returned wrong value: %d %s not %d %s\n", __LINE__, ret, v3, (int)sizeof(LIMIT_STACK)-1, LIMIT_STACK); + goto out; + } + printf("lxc.limit returned %d %s\n", ret, v3); + if (!c->set_config_item(c, "lxc.aa_profile", "unconfined")) { fprintf(stderr, "%d: failed to set aa_profile\n", __LINE__); goto out; From a6390f01cc5a5c016c151e5f0d486d276dfd4555 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Fri, 23 Dec 2016 13:10:01 +0100 Subject: [PATCH 4/4] conf: less error prone pointer access These functions define pointer to their key shifted by a number and guard access to it later via another variable. Let's make this more explicit (and additionally have the pointer be NULL in the case where it is not supposed to be used). Signed-off-by: Wolfgang Bumiller --- src/lxc/conf.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lxc/conf.c b/src/lxc/conf.c index c8c2a5452f..530a57ed15 100644 --- a/src/lxc/conf.c +++ b/src/lxc/conf.c @@ -4256,10 +4256,14 @@ int lxc_clear_cgroups(struct lxc_conf *c, const char *key) { struct lxc_list *it,*next; bool all = false; - const char *k = key + 11; + const char *k = NULL; if (strcmp(key, "lxc.cgroup") == 0) all = true; + else if (strncmp(key, "lxc.cgroup.", sizeof("lxc.cgroup.")-1) == 0) + k = key + sizeof("lxc.cgroup.")-1; + else + return -1; lxc_list_for_each_safe(it, &c->cgroup, next) { struct lxc_cgroup *cg = it->elem; @@ -4346,11 +4350,15 @@ int lxc_clear_hooks(struct lxc_conf *c, const char *key) { struct lxc_list *it,*next; bool all = false, done = false; - const char *k = key + 9; + const char *k = NULL; int i; if (strcmp(key, "lxc.hook") == 0) all = true; + else if (strncmp(key, "lxc.hook.", sizeof("lxc.hook.")-1) == 0) + k = key + sizeof("lxc.hook.")-1; + else + return -1; for (i=0; i