[CRIU] [RFC] lsm: add support for c/ring LSM profiles

Tycho Andersen tycho.andersen at canonical.com
Fri Apr 24 12:11:21 PDT 2015


Back from the dead of last year, this patch is finally working! It is an RFC,
since I think there are a few things that are wrong with this patch:

1. lsmtype probably shouldn't live in inventory, but I didn't know where else
   to put it. When we figure out where to put this the lsmtype enum can move,
   as well as the random extern in lsm.c
2. lsm_profile maybe should live on pstree_item instead of ->core, but ->core
   is the only thing passed to sigreturn_restore().
3. I don't know how to restore selinux policies since we don't actually exec()

I tried originally to get this to work using libraries, 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.

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.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 Makefile.config          |   5 ++
 Makefile.crtools         |   1 +
 cr-dump.c                |   4 ++
 cr-restore.c             |  38 ++++++++++
 image.c                  |   5 ++
 include/lsm.h            |  28 ++++++++
 include/pstree.h         |   1 +
 include/restorer.h       |   4 ++
 lsm.c                    | 182 +++++++++++++++++++++++++++++++++++++++++++++++
 pie/restorer.c           |  24 +++++++
 protobuf/core.proto      |   2 +
 protobuf/inventory.proto |   2 +
 protobuf/pstree.proto    |   6 ++
 scripts/utilities.mak    |   4 ++
 14 files changed, 306 insertions(+)
 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..9ea98bf 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"
 
@@ -999,6 +1000,9 @@ static int collect_task(struct pstree_item *item)
 	if (pstree_alloc_cores(item))
 		goto err_close;
 
+	if (collect_lsm_profile(item) < 0)
+		goto err_close;
+
 	pr_info("Collected %d in %d state\n", item->pid.real, item->state);
 	return 0;
 
diff --git a/cr-restore.c b/cr-restore.c
index 9d28e69..0432f9e 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"
 
@@ -1937,6 +1938,9 @@ int cr_restore_tasks(void)
 	if (prepare_pstree() < 0)
 		goto err;
 
+	if (validate_lsm() < 0)
+		goto err;
+
 	if (crtools_prepare_shared() < 0)
 		goto err;
 
@@ -2619,6 +2623,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;
@@ -2682,6 +2690,23 @@ static int sigreturn_restore(pid_t pid, CoreEntry *core)
 
 	memcpy(tcp_socks_mem, rst_tcp_socks, rst_tcp_socks_len());
 
+	if (core->lsm_profile) {
+		char *rendered;
+		if (render_lsm_profile(core->lsm_profile, &rendered) < 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);
+	}
+
 	/*
 	 * Copy timerfd params for restorer args, we need to proceed
 	 * timer setting at the very late.
@@ -2825,6 +2850,19 @@ static int sigreturn_restore(pid_t pid, CoreEntry *core)
 	else
 		task_args->helpers = NULL;
 
+	if (core->lsm_profile) {
+		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.
 	 */
diff --git a/image.c b/image.c
index 7f3ceb5..bd63dbe 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..56bc9ea
--- /dev/null
+++ b/include/lsm.h
@@ -0,0 +1,28 @@
+#ifndef __CR_LSM_H__
+#define __CR_LSM_H__
+
+#include "pstree.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(struct pstree_item *item);
+
+/*
+ * 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/pstree.h b/include/pstree.h
index c0fdac6..40bb0cc 100644
--- a/include/pstree.h
+++ b/include/pstree.h
@@ -6,6 +6,7 @@
 #include "image.h"
 #include "rst_info.h"
 #include "protobuf/core.pb-c.h"
+#include "protobuf/pstree.pb-c.h"
 
 /*
  * That's the init process which usually inherit
diff --git a/include/restorer.h b/include/restorer.h
index ded084a..a50f41d 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..c2dc59b
--- /dev/null
+++ b/lsm.c
@@ -0,0 +1,182 @@
+#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/pstree.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 **profile_name)
+{
+	security_context_t ctx;
+
+	if (getpidcon_raw(pid, &ctx) < 0) {
+		pr_perror("getting selinux profile failed");
+		return -1;
+	}
+
+	*profile_name = xstrdup((char *)ctx);
+	freecon(ctx);
+	if (!*profile_name)
+		return -1;
+
+	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
+	if (access("/sys/kernel/security/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(struct pstree_item *item)
+{
+	if (name == NULL)
+		get_host_lsm();
+
+	if (lsmtype == LSMTYPE__NO_LSM)
+		return 0;
+
+	/*
+	 * Here and in validate(), we store the lsm_profile in core[0], since
+	 * thats what dump_task_core_all seems to use for the task core for
+	 * this pstree item.
+	 */
+	if (get_label(item->pid.real, &item->core[0]->lsm_profile) < 0)
+		return -1;
+
+	if (item->core[0]->lsm_profile)
+		pr_info("%d has lsm profile %s\n", item->pid.real, item->core[0]->lsm_profile);
+
+	return 0;
+}
+
+// in inventory.c
+extern Lsmtype image_lsm;
+
+int validate_lsm()
+{
+	struct pstree_item *it;
+
+	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.
+	 */
+	pr_warn("lsm types do not match: host %d migratee %d\n", lsmtype, image_lsm);
+
+	for_each_pstree_item(it) {
+		if (it->core[0]->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 (strcmp(profile, "unconfined_t") != 0) {
+			pr_err("I don't know how to set an selinux context without exec.");
+			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/core.proto b/protobuf/core.proto
index 1f44a47..0e3e2e3 100644
--- a/protobuf/core.proto
+++ b/protobuf/core.proto
@@ -80,4 +80,6 @@ message core_entry {
 	optional task_core_entry	tc		= 3;
 	optional task_kobj_ids_entry	ids		= 4;
 	optional thread_core_entry	thread_core	= 5;
+
+	optional string			lsm_profile	= 9;
 }
diff --git a/protobuf/inventory.proto b/protobuf/inventory.proto
index 97f572f..268eec5 100644
--- a/protobuf/inventory.proto
+++ b/protobuf/inventory.proto
@@ -1,4 +1,5 @@
 import "core.proto";
+import "pstree.proto";
 
 message inventory_entry {
 	required uint32			img_version	= 1;
@@ -6,4 +7,5 @@ message inventory_entry {
 	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/protobuf/pstree.proto b/protobuf/pstree.proto
index 6cbcfd3..01117ea 100644
--- a/protobuf/pstree.proto
+++ b/protobuf/pstree.proto
@@ -1,3 +1,9 @@
+enum lsmtype {
+	NO_LSM		= 0;
+	SELINUX		= 1;
+	APPARMOR	= 2;
+}
+
 message pstree_entry {
 	required uint32			pid		= 1;
 	required uint32			ppid		= 2;
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