introduce lxc.cap.keep

The lxc configuration file currently supports 'lxc.cap.drop', a list of
capabilities to be dropped (using the bounding set) from the container.
The problem with this is that over time new capabilities are added.  So
an older container configuration file may, over time, become insecure.

Walter has in the past suggested replacing lxc.cap.drop with
lxc.cap.preserve, which would have the inverse sense - any capabilities
in that set would be kept, any others would be dropped.

Realistically both have the same problem - the sendmail capabilities
bug proved that running code with unexpectedly dropped privilege can be
dangerous.  This patch gives the admin a choice:  You can use either
lxc.cap.keep or lxc.cap.drop, not both.

Both continue to be ignored if a user namespace is in use.

Signed-off-by: Serge Hallyn <serge.hallyn@ubuntu.com>
This commit is contained in:
Serge Hallyn 2013-06-13 22:43:01 -05:00
parent 59d66af29d
commit 1fb86a7cdc
4 changed files with 176 additions and 3 deletions

View File

@ -771,6 +771,17 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>
<option>lxc.cap.keep</option>
</term>
<listitem>
<para>
Specify the capability to be kept in the container. All other
capabilities will be dropped.
</para>
</listitem>
</varlistentry>
</variablelist> </variablelist>
</refsect2> </refsect2>

View File

@ -1838,7 +1838,73 @@ static int setup_caps(struct lxc_list *caps)
} }
DEBUG("capabilities has been setup"); DEBUG("capabilities have been setup");
return 0;
}
static int dropcaps_except(struct lxc_list *caps)
{
struct lxc_list *iterator;
char *keep_entry;
char *ptr;
int i, capid;
int numcaps = lxc_caps_last_cap() + 1;
INFO("found %d capabilities\n", numcaps);
// caplist[i] is 1 if we keep capability i
int *caplist = alloca(numcaps * sizeof(int));
memset(caplist, 0, numcaps * sizeof(int));
lxc_list_for_each(iterator, caps) {
keep_entry = iterator->elem;
capid = -1;
for (i = 0; i < sizeof(caps_opt)/sizeof(caps_opt[0]); i++) {
if (strcmp(keep_entry, caps_opt[i].name))
continue;
capid = caps_opt[i].value;
break;
}
if (capid < 0) {
/* try to see if it's numeric, so the user may specify
* capabilities that the running kernel knows about but
* we don't */
capid = strtol(keep_entry, &ptr, 10);
if (!ptr || *ptr != '\0' ||
capid == LONG_MIN || capid == LONG_MAX)
/* not a valid number */
capid = -1;
else if (capid > lxc_caps_last_cap())
/* we have a number but it's not a valid
* capability */
capid = -1;
}
if (capid < 0) {
ERROR("unknown capability %s", keep_entry);
return -1;
}
DEBUG("drop capability '%s' (%d)", keep_entry, capid);
caplist[capid] = 1;
}
for (i=0; i<numcaps; i++) {
if (caplist[i])
continue;
if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0)) {
SYSERROR("failed to remove capability %d", i);
return -1;
}
}
DEBUG("capabilities have been setup");
return 0; return 0;
} }
@ -2180,6 +2246,7 @@ struct lxc_conf *lxc_conf_init(void)
lxc_list_init(&new->network); lxc_list_init(&new->network);
lxc_list_init(&new->mount_list); lxc_list_init(&new->mount_list);
lxc_list_init(&new->caps); lxc_list_init(&new->caps);
lxc_list_init(&new->keepcaps);
lxc_list_init(&new->id_map); lxc_list_init(&new->id_map);
for (i=0; i<NUM_LXC_HOOKS; i++) for (i=0; i<NUM_LXC_HOOKS; i++)
lxc_list_init(&new->hooks[i]); lxc_list_init(&new->hooks[i]);
@ -2926,7 +2993,16 @@ int lxc_setup(const char *name, struct lxc_conf *lxc_conf, const char *lxcpath)
} }
if (lxc_list_empty(&lxc_conf->id_map)) { if (lxc_list_empty(&lxc_conf->id_map)) {
if (setup_caps(&lxc_conf->caps)) { if (!lxc_list_empty(&lxc_conf->keepcaps)) {
if (!lxc_list_empty(&lxc_conf->caps)) {
ERROR("Simultaneously requested dropping and keeping caps");
return -1;
}
if (dropcaps_except(&lxc_conf->keepcaps)) {
ERROR("failed to keep requested caps\n");
return -1;
}
} else if (setup_caps(&lxc_conf->caps)) {
ERROR("failed to drop capabilities"); ERROR("failed to drop capabilities");
return -1; return -1;
} }
@ -3125,6 +3201,18 @@ int lxc_clear_idmaps(struct lxc_conf *c)
return 0; return 0;
} }
int lxc_clear_config_keepcaps(struct lxc_conf *c)
{
struct lxc_list *it,*next;
lxc_list_for_each_safe(it, &c->keepcaps, next) {
lxc_list_del(it);
free(it->elem);
free(it);
}
return 0;
}
int lxc_clear_cgroups(struct lxc_conf *c, const char *key) int lxc_clear_cgroups(struct lxc_conf *c, const char *key)
{ {
struct lxc_list *it,*next; struct lxc_list *it,*next;
@ -3224,6 +3312,7 @@ void lxc_conf_free(struct lxc_conf *conf)
#endif #endif
lxc_seccomp_free(conf); lxc_seccomp_free(conf);
lxc_clear_config_caps(conf); lxc_clear_config_caps(conf);
lxc_clear_config_keepcaps(conf);
lxc_clear_cgroups(conf, "lxc.cgroup"); lxc_clear_cgroups(conf, "lxc.cgroup");
lxc_clear_hooks(conf, "lxc.hook"); lxc_clear_hooks(conf, "lxc.hook");
lxc_clear_mount_entries(conf); lxc_clear_mount_entries(conf);

View File

@ -232,7 +232,8 @@ struct lxc_rootfs {
* @network : network configuration * @network : network configuration
* @utsname : container utsname * @utsname : container utsname
* @fstab : path to a fstab file format * @fstab : path to a fstab file format
* @caps : list of the capabilities * @caps : list of the capabilities to drop
* @keepcaps : list of the capabilities to keep
* @tty_info : tty data * @tty_info : tty data
* @console : console data * @console : console data
* @ttydir : directory (under /dev) in which to create console and ttys * @ttydir : directory (under /dev) in which to create console and ttys
@ -266,6 +267,7 @@ struct lxc_conf {
int num_savednics; int num_savednics;
struct lxc_list mount_list; struct lxc_list mount_list;
struct lxc_list caps; struct lxc_list caps;
struct lxc_list keepcaps;
struct lxc_tty_info tty_info; struct lxc_tty_info tty_info;
struct lxc_console console; struct lxc_console console;
struct lxc_rootfs rootfs; struct lxc_rootfs rootfs;
@ -323,6 +325,7 @@ extern void lxc_delete_tty(struct lxc_tty_info *tty_info);
extern int lxc_clear_config_network(struct lxc_conf *c); extern int lxc_clear_config_network(struct lxc_conf *c);
extern int lxc_clear_nic(struct lxc_conf *c, const char *key); extern int lxc_clear_nic(struct lxc_conf *c, const char *key);
extern int lxc_clear_config_caps(struct lxc_conf *c); extern int lxc_clear_config_caps(struct lxc_conf *c);
extern int lxc_clear_config_keepcaps(struct lxc_conf *c);
extern int lxc_clear_cgroups(struct lxc_conf *c, const char *key); extern int lxc_clear_cgroups(struct lxc_conf *c, const char *key);
extern int lxc_clear_mount_entries(struct lxc_conf *c); extern int lxc_clear_mount_entries(struct lxc_conf *c);
extern int lxc_clear_hooks(struct lxc_conf *c, const char *key); extern int lxc_clear_hooks(struct lxc_conf *c, const char *key);

View File

@ -85,6 +85,7 @@ static int config_network_script(const char *, const char *, struct lxc_conf *);
static int config_network_ipv6(const char *, const char *, struct lxc_conf *); static int config_network_ipv6(const char *, const char *, struct lxc_conf *);
static int config_network_ipv6_gateway(const char *, const char *, struct lxc_conf *); static int config_network_ipv6_gateway(const char *, const char *, struct lxc_conf *);
static int config_cap_drop(const char *, const char *, struct lxc_conf *); static int config_cap_drop(const char *, const char *, struct lxc_conf *);
static int config_cap_keep(const char *, const char *, struct lxc_conf *);
static int config_console(const char *, const char *, struct lxc_conf *); static int config_console(const char *, const char *, struct lxc_conf *);
static int config_seccomp(const char *, const char *, struct lxc_conf *); static int config_seccomp(const char *, const char *, struct lxc_conf *);
static int config_includefile(const char *, const char *, struct lxc_conf *); static int config_includefile(const char *, const char *, struct lxc_conf *);
@ -136,6 +137,7 @@ static struct lxc_config_t config[] = {
/* config_network_nic must come after all other 'lxc.network.*' entries */ /* config_network_nic must come after all other 'lxc.network.*' entries */
{ "lxc.network.", config_network_nic }, { "lxc.network.", config_network_nic },
{ "lxc.cap.drop", config_cap_drop }, { "lxc.cap.drop", config_cap_drop },
{ "lxc.cap.keep", config_cap_keep },
{ "lxc.console", config_console }, { "lxc.console", config_console },
{ "lxc.seccomp", config_seccomp }, { "lxc.seccomp", config_seccomp },
{ "lxc.include", config_includefile }, { "lxc.include", config_includefile },
@ -1272,6 +1274,52 @@ static int config_mount(const char *key, const char *value,
return 0; return 0;
} }
static int config_cap_keep(const char *key, const char *value,
struct lxc_conf *lxc_conf)
{
char *keepcaps, *keepptr, *sptr, *token;
struct lxc_list *keeplist;
int ret = -1;
if (!strlen(value))
return -1;
keepcaps = strdup(value);
if (!keepcaps) {
SYSERROR("failed to dup '%s'", value);
return -1;
}
/* in case several capability keep is specified in a single line
* split these caps in a single element for the list */
for (keepptr = keepcaps;;keepptr = NULL) {
token = strtok_r(keepptr, " \t", &sptr);
if (!token) {
ret = 0;
break;
}
keeplist = malloc(sizeof(*keeplist));
if (!keeplist) {
SYSERROR("failed to allocate keepcap list");
break;
}
keeplist->elem = strdup(token);
if (!keeplist->elem) {
SYSERROR("failed to dup '%s'", token);
free(keeplist);
break;
}
lxc_list_add_tail(&lxc_conf->keepcaps, keeplist);
}
free(keepcaps);
return ret;
}
static int config_cap_drop(const char *key, const char *value, static int config_cap_drop(const char *key, const char *value,
struct lxc_conf *lxc_conf) struct lxc_conf *lxc_conf)
{ {
@ -1638,6 +1686,22 @@ static int lxc_get_item_cap_drop(struct lxc_conf *c, char *retv, int inlen)
return fulllen; return fulllen;
} }
static int lxc_get_item_cap_keep(struct lxc_conf *c, char *retv, int inlen)
{
int len, fulllen = 0;
struct lxc_list *it;
if (!retv)
inlen = 0;
else
memset(retv, 0, inlen);
lxc_list_for_each(it, &c->keepcaps) {
strprint(retv, inlen, "%s\n", (char *)it->elem);
}
return fulllen;
}
static int lxc_get_mount_entries(struct lxc_conf *c, char *retv, int inlen) static int lxc_get_mount_entries(struct lxc_conf *c, char *retv, int inlen)
{ {
int len, fulllen = 0; int len, fulllen = 0;
@ -1818,6 +1882,8 @@ int lxc_get_config_item(struct lxc_conf *c, const char *key, char *retv,
v = c->rootfs.pivot; v = c->rootfs.pivot;
else if (strcmp(key, "lxc.cap.drop") == 0) else if (strcmp(key, "lxc.cap.drop") == 0)
return lxc_get_item_cap_drop(c, retv, inlen); return lxc_get_item_cap_drop(c, retv, inlen);
else if (strcmp(key, "lxc.cap.keep") == 0)
return lxc_get_item_cap_keep(c, retv, inlen);
else if (strncmp(key, "lxc.hook", 8) == 0) else if (strncmp(key, "lxc.hook", 8) == 0)
return lxc_get_item_hooks(c, retv, inlen, key); return lxc_get_item_hooks(c, retv, inlen, key);
else if (strcmp(key, "lxc.network") == 0) else if (strcmp(key, "lxc.network") == 0)
@ -1841,6 +1907,8 @@ int lxc_clear_config_item(struct lxc_conf *c, const char *key)
return lxc_clear_nic(c, key + 12); return lxc_clear_nic(c, key + 12);
else if (strcmp(key, "lxc.cap.drop") == 0) else if (strcmp(key, "lxc.cap.drop") == 0)
return lxc_clear_config_caps(c); return lxc_clear_config_caps(c);
else if (strcmp(key, "lxc.cap.keep") == 0)
return lxc_clear_config_keepcaps(c);
else if (strncmp(key, "lxc.cgroup", 10) == 0) else if (strncmp(key, "lxc.cgroup", 10) == 0)
return lxc_clear_cgroups(c, key); return lxc_clear_cgroups(c, key);
else if (strcmp(key, "lxc.mount.entries") == 0) else if (strcmp(key, "lxc.mount.entries") == 0)
@ -1953,6 +2021,8 @@ void write_config(FILE *fout, struct lxc_conf *c)
} }
lxc_list_for_each(it, &c->caps) lxc_list_for_each(it, &c->caps)
fprintf(fout, "lxc.cap.drop = %s\n", (char *)it->elem); fprintf(fout, "lxc.cap.drop = %s\n", (char *)it->elem);
lxc_list_for_each(it, &c->keepcaps)
fprintf(fout, "lxc.cap.keep = %s\n", (char *)it->elem);
lxc_list_for_each(it, &c->id_map) { lxc_list_for_each(it, &c->id_map) {
struct id_map *idmap = it->elem; struct id_map *idmap = it->elem;
fprintf(fout, "lxc.id_map = %c %lu %lu %lu\n", fprintf(fout, "lxc.id_map = %c %lu %lu %lu\n",