[CRIU] [PATCH v3 1/3] lsm: add support for c/ring LSM profiles

Tycho Andersen tycho.andersen at canonical.com
Wed May 6 15:18:42 PDT 2015


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 at canonical.com>
---
 Makefile.config          |   5 ++
 Makefile.crtools         |   1 +
 cr-dump.c                |   4 +
 cr-restore.c             |  61 ++++++++++++--
 image.c                  |   5 ++
 include/lsm.h            |  29 +++++++
 include/restorer.h       |   4 +
 lsm.c                    | 206 +++++++++++++++++++++++++++++++++++++++++++++++
 pie/restorer.c           |  24 ++++++
 protobuf/creds.proto     |   2 +
 protobuf/inventory.proto |   7 ++
 scripts/utilities.mak    |   4 +
 12 files changed, 347 insertions(+), 5 deletions(-)
 create mode 100644 include/lsm.h
 create mode 100644 lsm.c

diff --git a/Makefile.config b/Makefile.config
index ac54775..e1d2a3b 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 650b9b0..403d6fa 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 ac41865..f865967 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 73bb9ca..aa00dc2 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 5906bc8..bb0edaa 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 0000000..d3b0c97
--- /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 8b63a95..34396e3 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 0000000..90b395f
--- /dev/null
+++ b/lsm.c
@@ -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 d64fbf0..8713c6a 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 68894ac..1bf8405 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 97f572f..a107bb1 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 d1b68fa..6fb81c3 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')
-- 
2.1.4



More information about the CRIU mailing list