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 diff --git a/cr-dump.c b/cr-dump.c index ac4186508..f865967db 100644 --- a/cr-dump.c +++ b/cr-dump.c @@ -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; diff --git a/image.c b/image.c index 5906bc889..bb0edaa04 100644 --- a/image.c +++ b/image.c @@ -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//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 */ diff --git a/lsm.c b/lsm.c new file mode 100644 index 000000000..90b395f5d --- /dev/null +++ b/lsm.c @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include + +#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 +#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')