diff options
author | Tycho Andersen <tycho.andersen@canonical.com> | 2015-05-07 01:18:42 +0300 |
---|---|---|
committer | Pavel Emelyanov <xemul@parallels.com> | 2015-05-08 15:31:05 +0300 |
commit | 5fe3a138df9d157e3e29c910ca9ff9186cd5ad7f (patch) | |
tree | d86a611e933af5826f52c128422f6f5b86c686a4 | |
parent | a8b7e53b46cac77f55e3039d07b8404b43e22909 (diff) |
lsm: add support for c/ring LSM profiles
This patch adds support for checkpoint and restore of two linux security
modules (apparmor and selinux). The actual checkpoint or restore code isn't
that interesting, other than that we have to do the LSM restore in the restorer
blob since it may block any number of things that we want to do as part of the
restore process.
I tried originally to get this to work using libraries in the restorer blob,
but I could _not_ get things to work correctly (I assume I was doing something
wrong with all the static linking, you can see my draft attempts here:
https://github.com/tych0/criu/commits/apparmor-using-libraries ). I can try to
resurrect this if it makes more sense, to do it that way, though.
v2: lsm_profile lives in creds.proto instead of the task core, look in a more
canonical place for selinuxfs and don't try to special case any selinux
profile names.
v3: only allow unconfined selinux profiles
Signed-off-by: Tycho Andersen <tycho.andersen@canonical.com>
Signed-off-by: Pavel Emelyanov <xemul@parallels.com>
-rw-r--r-- | Makefile.config | 5 | ||||
-rw-r--r-- | Makefile.crtools | 1 | ||||
-rw-r--r-- | cr-dump.c | 4 | ||||
-rw-r--r-- | cr-restore.c | 61 | ||||
-rw-r--r-- | image.c | 5 | ||||
-rw-r--r-- | include/lsm.h | 29 | ||||
-rw-r--r-- | include/restorer.h | 4 | ||||
-rw-r--r-- | lsm.c | 206 | ||||
-rw-r--r-- | pie/restorer.c | 24 | ||||
-rw-r--r-- | protobuf/creds.proto | 2 | ||||
-rw-r--r-- | protobuf/inventory.proto | 7 | ||||
-rw-r--r-- | scripts/utilities.mak | 4 |
12 files changed, 347 insertions, 5 deletions
diff --git a/Makefile.config b/Makefile.config index ac54775fc..e1d2a3b8d 100644 --- a/Makefile.config +++ b/Makefile.config @@ -8,6 +8,11 @@ ifeq ($(call try-cc,$(LIBBSD_DEV_TEST),-lbsd),y) DEFINES += -DCONFIG_HAS_LIBBSD endif +ifeq ($(call pkg-config-check,libselinux),y) + LIBS := -lselinux $(LIBS) + DEFINES += -DCONFIG_HAS_SELINUX +endif + $(CONFIG): scripts/utilities.mak scripts/feature-tests.mak include/config-base.h $(E) " GEN " $@ $(Q) @echo '#ifndef __CR_CONFIG_H__' > $@ diff --git a/Makefile.crtools b/Makefile.crtools index 650b9b0c3..403d6fa1c 100644 --- a/Makefile.crtools +++ b/Makefile.crtools @@ -64,6 +64,7 @@ obj-y += timerfd.o obj-y += aio.o obj-y += string.o obj-y += sigframe.o +obj-y += lsm.o ifeq ($(VDSO),y) obj-y += $(ARCH_DIR)/vdso.o endif @@ -74,6 +74,7 @@ #include "action-scripts.h" #include "aio.h" #include "security.h" +#include "lsm.h" #include "asm/dump.h" @@ -485,6 +486,9 @@ static int dump_task_creds(struct parasite_ctl *ctl, if (parasite_dump_creds(ctl, &ce) < 0) return -1; + if (collect_lsm_profile(ctl->pid.real, &ce) < 0) + return -1; + return pb_write_one(img_from_set(fds, CR_FD_CREDS), &ce, PB_CREDS); } diff --git a/cr-restore.c b/cr-restore.c index 73bb9ca2d..aa00dc2eb 100644 --- a/cr-restore.c +++ b/cr-restore.c @@ -74,6 +74,7 @@ #include "action-scripts.h" #include "aio.h" #include "security.h" +#include "lsm.h" #include "parasite-syscall.h" @@ -2248,7 +2249,7 @@ static inline int verify_cap_size(CredsEntry *ce) (ce->n_cap_prm == CR_CAP_SIZE) && (ce->n_cap_bnd == CR_CAP_SIZE)); } -static int prepare_creds(int pid, struct task_restore_args *args) +static int prepare_creds(int pid, struct task_restore_args *args, char **lsm_profile) { int ret; struct cr_img *img; @@ -2295,6 +2296,17 @@ static int prepare_creds(int pid, struct task_restore_args *args) return -1; } + *lsm_profile = NULL; + + if (ce->lsm_profile) { + if (validate_lsm(ce) < 0) + return -1; + + *lsm_profile = xstrdup(ce->lsm_profile); + if (!*lsm_profile) + return -1; + } + creds_entry__free_unpacked(ce, NULL); args->cap_last_cap = kdat.last_cap; @@ -2631,6 +2643,10 @@ static int sigreturn_restore(pid_t pid, CoreEntry *core) unsigned long aio_rings; MmEntry *mm = rsti(current)->mm; + char *lsm = NULL; + int lsm_profile_len = 0; + unsigned long lsm_pos = 0; + struct vm_area_list self_vmas; struct vm_area_list *vmas = &rsti(current)->vmas; int i; @@ -2783,6 +2799,32 @@ static int sigreturn_restore(pid_t pid, CoreEntry *core) task_args = mem; thread_args = (struct thread_restore_args *)(task_args + 1); + ret = prepare_creds(pid, task_args, &lsm); + if (ret < 0) + goto err; + + if (lsm) { + char *rendered; + int ret; + + ret = render_lsm_profile(lsm, &rendered); + xfree(lsm); + if (ret < 0) { + goto err_nv; + } + + lsm_pos = rst_mem_cpos(RM_PRIVATE); + lsm_profile_len = strlen(rendered); + lsm = rst_mem_alloc(lsm_profile_len + 1, RM_PRIVATE); + if (!lsm) { + xfree(rendered); + goto err_nv; + } + + strncpy(lsm, rendered, lsm_profile_len); + xfree(rendered); + } + /* * Get a reference to shared memory area which is * used to signal if shmem restoration complete @@ -2837,6 +2879,19 @@ static int sigreturn_restore(pid_t pid, CoreEntry *core) else task_args->helpers = NULL; + if (lsm) { + task_args->proc_attr_current = open_proc_rw(PROC_SELF, "attr/current"); + if (task_args->proc_attr_current < 0) { + pr_perror("Can't open attr/current"); + goto err; + } + + task_args->lsm_profile = rst_mem_remap_ptr(lsm_pos, RM_PRIVATE); + task_args->lsm_profile_len = lsm_profile_len; + } else { + task_args->lsm_profile = NULL; + } + /* * Arguments for task restoration. */ @@ -2937,10 +2992,6 @@ static int sigreturn_restore(pid_t pid, CoreEntry *core) if (ret < 0) goto err; - ret = prepare_creds(pid, task_args); - if (ret < 0) - goto err; - ret = prepare_mm(pid, task_args); if (ret < 0) goto err; @@ -8,6 +8,7 @@ #include "pstree.h" #include "stats.h" #include "cgroup.h" +#include "lsm.h" #include "protobuf.h" #include "protobuf/inventory.pb-c.h" #include "protobuf/pagemap.pb-c.h" @@ -17,6 +18,7 @@ bool ns_per_id = false; bool img_common_magic = true; TaskKobjIdsEntry *root_ids; u32 root_cg_set; +Lsmtype image_lsm; int check_img_inventory(void) { @@ -51,6 +53,8 @@ int check_img_inventory(void) root_cg_set = he->root_cg_set; } + image_lsm = he->lsmtype; + switch (he->img_version) { case CRTOOLS_IMAGES_V1: /* good old images. OK */ @@ -93,6 +97,7 @@ int write_img_inventory(void) he.has_fdinfo_per_id = true; he.ns_per_id = true; he.has_ns_per_id = true; + he.lsmtype = host_lsm_type(); crt.i.state = TASK_ALIVE; crt.i.pid.real = getpid(); diff --git a/include/lsm.h b/include/lsm.h new file mode 100644 index 000000000..d3b0c973c --- /dev/null +++ b/include/lsm.h @@ -0,0 +1,29 @@ +#ifndef __CR_LSM_H__ +#define __CR_LSM_H__ + +#include "protobuf/inventory.pb-c.h" +#include "protobuf/creds.pb-c.h" + +/* + * Get the Lsmtype for the current host. + */ +extern Lsmtype host_lsm_type(); + +/* + * Read the LSM profile for the pstree item + */ +extern int collect_lsm_profile(pid_t, CredsEntry *); + +/* + * Validate that the LSM profiles can be correctly applied (must happen after + * pstree is set up). + */ +extern int validate_lsm(); + +/* + * Render the profile name in the way that the LSM wants it written to + * /proc/<pid>/attr/current. + */ +int render_lsm_profile(char *profile, char **val); + +#endif /* __CR_LSM_H__ */ diff --git a/include/restorer.h b/include/restorer.h index 8b63a95fc..34396e342 100644 --- a/include/restorer.h +++ b/include/restorer.h @@ -154,6 +154,10 @@ struct task_restore_args { pid_t *helpers /* the TASK_HELPERS to wait on at the end of restore */; int n_helpers; + int proc_attr_current; + char *lsm_profile; + int lsm_profile_len; + #ifdef CONFIG_VDSO unsigned long vdso_rt_size; struct vdso_symtable vdso_sym_rt; /* runtime vdso symbols */ @@ -0,0 +1,206 @@ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> + +#include "config.h" +#include "pstree.h" +#include "util.h" + +#include "protobuf.h" +#include "protobuf/inventory.pb-c.h" +#include "protobuf/creds.pb-c.h" + +#ifdef CONFIG_HAS_SELINUX +#include <selinux/selinux.h> +#endif + +static Lsmtype lsmtype; +static int (*get_label)(pid_t, char **) = NULL; +static char *name = NULL; + +static int apparmor_get_label(pid_t pid, char **profile_name) +{ + FILE *f; + char *space; + + f = fopen_proc(pid, "attr/current"); + if (!f) + return -1; + + if (fscanf(f, "%ms", profile_name) != 1) { + fclose(f); + pr_perror("err scanfing"); + return -1; + } + + fclose(f); + + /* + * A profile name can be followed by an enforcement mode, e.g. + * lxc-default-with-nesting (enforced) + * but the profile name is just the part before the space. + */ + space = strstr(*profile_name, " "); + if (space) + *space = 0; + + /* + * An "unconfined" value means there is no profile, so we don't need to + * worry about trying to restore one. + */ + if (strcmp(*profile_name, "unconfined") == 0) + *profile_name = NULL; + + return 0; +} + +#ifdef CONFIG_HAS_SELINUX +static int selinux_get_label(pid_t pid, char **output) +{ + security_context_t ctx; + char *pos, *last; + int i; + + if (getpidcon_raw(pid, &ctx) < 0) { + pr_perror("getting selinux profile failed"); + return -1; + } + + *output = NULL; + + /* + * Since SELinux attributes can be finer grained than at the task + * level, and we currently don't try to dump any of these other bits, + * let's only allow unconfined profiles, which look something like: + * + * unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 + */ + pos = (char*)ctx; + for (i = 0; i < 3; i++) { + last = pos; + pos = strstr(pos, ":"); + if (!pos) { + pr_err("Invalid selinux context %s\n", (char *)ctx); + freecon(ctx); + return -1; + } + + *pos = 0; + if (!strstartswith(last, "unconfined_")) { + pr_err("Non unconfined selinux contexts not supported %s\n", last); + freecon(ctx); + return -1; + } + + pos++; + } + freecon(ctx); + + return 0; +} +#endif + +static void get_host_lsm() +{ + if (access("/sys/kernel/security/apparmor", F_OK) == 0) { + get_label = apparmor_get_label; + lsmtype = LSMTYPE__APPARMOR; + name = "apparmor"; + return; + } + +#ifdef CONFIG_HAS_SELINUX + /* + * This seems to be the canonical place to mount this fs if it is + * enabled, although we may (?) want to check /selinux for posterity as + * well. + */ + if (access("/sys/fs/selinux", F_OK) == 0) { + get_label = selinux_get_label; + lsmtype = LSMTYPE__SELINUX; + name = "selinux"; + return; + } +#endif + + get_label = NULL; + lsmtype = LSMTYPE__NO_LSM; + name = "none"; +} + +Lsmtype host_lsm_type() +{ + if (name == NULL) + get_host_lsm(); + + return lsmtype; +} + +int collect_lsm_profile(pid_t pid, CredsEntry *ce) +{ + if (name == NULL) + get_host_lsm(); + + ce->lsm_profile = NULL; + + if (lsmtype == LSMTYPE__NO_LSM) + return 0; + + if (get_label(pid, &ce->lsm_profile) < 0) + return -1; + + if (ce->lsm_profile) + pr_info("%d has lsm profile %s\n", pid, ce->lsm_profile); + + return 0; +} + +// in inventory.c +extern Lsmtype image_lsm; + +int validate_lsm(CredsEntry *ce) +{ + if (name == NULL) + get_host_lsm(); + + if (image_lsm == LSMTYPE__NO_LSM || image_lsm == lsmtype) + return 0; + + /* + * This is really only a problem if the processes have actually + * specified an LSM profile. If not, we won't restore anything anyway, + * so it's fine. + */ + if (ce->lsm_profile) { + pr_err("mismatched lsm types and lsm profile specified\n"); + return -1; + } + + return 0; +} + +int render_lsm_profile(char *profile, char **val) +{ + *val = NULL; + + switch (lsmtype) { + case LSMTYPE__APPARMOR: + if (strcmp(profile, "unconfined") != 0 && asprintf(val, "changeprofile %s", profile) < 0) { + *val = NULL; + return -1; + } + break; + case LSMTYPE__SELINUX: + if (asprintf(val, "%s", profile) < 0) { + *val = NULL; + return -1; + } + break; + default: + return -1; + } + + return 0; +} diff --git a/pie/restorer.c b/pie/restorer.c index d64fbf09d..8713c6a96 100644 --- a/pie/restorer.c +++ b/pie/restorer.c @@ -740,6 +740,25 @@ static int wait_helpers(struct task_restore_args *task_args) return 0; } +static int lsm_set_label(struct task_restore_args *args) +{ + int ret = -1; + + if (!args->lsm_profile) + return 0; + + pr_info("restoring lsm profile %s\n", args->lsm_profile); + + ret = sys_write(args->proc_attr_current, args->lsm_profile, args->lsm_profile_len); + sys_close(args->proc_attr_current); + if (ret < 0) { + pr_err("can't write lsm profile\n"); + return -1; + } + + return ret; +} + /* * The main routine to restore task via sigreturn. * This one is very special, we never return there @@ -1160,6 +1179,11 @@ long __export_restore_task(struct task_restore_args *args) ret = ret || restore_dumpable_flag(&args->mm); ret = ret || restore_pdeath_sig(args->t); + if (lsm_set_label(args) < 0) { + pr_err("lsm_set_label failed\n"); + goto core_restore_end; + } + futex_set_and_wake(&thread_inprogress, args->nr_threads); restore_finish_stage(CR_STATE_RESTORE_CREDS); diff --git a/protobuf/creds.proto b/protobuf/creds.proto index 68894ac07..1bf840513 100644 --- a/protobuf/creds.proto +++ b/protobuf/creds.proto @@ -16,4 +16,6 @@ message creds_entry { required uint32 secbits = 13; repeated uint32 groups = 14; + + optional string lsm_profile = 15; } diff --git a/protobuf/inventory.proto b/protobuf/inventory.proto index 97f572f22..a107bb11f 100644 --- a/protobuf/inventory.proto +++ b/protobuf/inventory.proto @@ -1,9 +1,16 @@ import "core.proto"; +enum lsmtype { + NO_LSM = 0; + SELINUX = 1; + APPARMOR = 2; +} + message inventory_entry { required uint32 img_version = 1; optional bool fdinfo_per_id = 2; optional task_kobj_ids_entry root_ids = 3; optional bool ns_per_id = 4; optional uint32 root_cg_set = 5; + optional lsmtype lsmtype = 6; } diff --git a/scripts/utilities.mak b/scripts/utilities.mak index d1b68faa5..6fb81c396 100644 --- a/scripts/utilities.mak +++ b/scripts/utilities.mak @@ -5,3 +5,7 @@ try-cc = $(shell sh -c \ echo "$(1)" | \ $(CC) $(DEFINES) -x c - $(2) $(3) -o "$$TMP" > /dev/null 2>&1 && echo y; \ rm -f "$$TMP"') + +# pkg-config-check +# Usage: ifeq ($(call pkg-config-check, library),y) +pkg-config-check = $(shell sh -c 'pkg-config $(1) && echo y') |