[CRIU] [PATCH v3 6/7] apparmor: add support for suspending the LSM from userspace

Tycho Andersen tycho.andersen at canonical.com
Wed Oct 26 10:01:36 PDT 2016


The idea here is that we need something like PTRACE_O_SUSPEND_SECCOMP for
apparmor. The strategy here is just to replace the apparmor profile with
one that will work for all of the stuff CRIU needs to do, and then write
the old profile back at the end of the dump if necessary.

The patch in the next test uses a profile without "unix", to test this.

v2: use rm_rf instead of rmdir to clean up the policy directory, since
    apparmor_parser leaves all sorts of cache files and such in it.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 criu/apparmor.c         | 365 +++++++++++++++++++++++++++++++-----------------
 criu/include/apparmor.h |   7 +
 criu/lsm.c              |   6 +
 3 files changed, 252 insertions(+), 126 deletions(-)

diff --git a/criu/apparmor.c b/criu/apparmor.c
index 2c8d060..d09d881 100644
--- a/criu/apparmor.c
+++ b/criu/apparmor.c
@@ -4,6 +4,7 @@
 #include <fcntl.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <sys/mman.h>
 #include <unistd.h>
 #include <ftw.h>
 
@@ -344,65 +345,97 @@ err:
 	return -1;
 }
 
-int dump_aa_namespaces(void)
-{
-	ApparmorEntry *ae = NULL;
-	int ret;
-
-	if (n_namespaces == 0)
-		return 0;
+/* An AA profile that allows everything that the parasite needs to do */
+#define PARASITE_PROFILE (		\
+	"profile %s {\n"		\
+	"	/** rwmlkix,\n"		\
+	"	unix,\n"		\
+	"	capability,\n"		\
+	"	signal,\n"		\
+	"}\n")
 
-	ae = xmalloc(sizeof(*ae));
-	if (!ae)
-		return -1;
-	apparmor_entry__init(ae);
+char policydir[PATH_MAX] = ".criu.temp-aa-policy.XXXXXX";
 
-	ae->n_namespaces = n_namespaces;
-	ae->namespaces = namespaces;
+static void *get_suspend_policy(char *name, off_t *len)
+{
+	char policy[1024], file[PATH_MAX], cache[PATH_MAX];
+	void *ret = NULL;
+	int n, fd, policy_len;
+	struct stat sb;
+	char *cmd[] = {
+		"apparmor_parser",
+		"-QWL",
+		cache,
+		file,
+		NULL,
+	};
+
+	*len = 0;
+
+	policy_len = snprintf(policy, sizeof(policy), PARASITE_PROFILE, name);
+	if (policy_len < 0 || policy_len >= sizeof(policy)) {
+		pr_err("policy name %s too long\n", name);
+		return NULL;
+	}
 
-	ret = pb_write_one(img_from_set(glob_imgset, CR_FD_APPARMOR), ae, PB_APPARMOR);
+	n = snprintf(file, sizeof(file), "%s/%s", policydir, name);
+	if (n < 0 || n >= sizeof(policy)) {
+		pr_err("policy name %s too long\n", name);
+		return NULL;
+	}
 
-	apparmor_entry__free_unpacked(ae, NULL);
-	n_namespaces = -1;
-	namespaces = NULL;
+	n = snprintf(cache, sizeof(cache), "%s/cache", policydir);
+	if (n < 0 || n >= sizeof(policy)) {
+		pr_err("policy name %s too long\n", name);
+		return NULL;
+	}
 
-	return ret;
-}
+	fd = open(file, O_CREAT | O_WRONLY, 0600);
+	if (fd < 0) {
+		pr_perror("couldn't create %s", file);
+		return NULL;
+	}
 
-bool check_aa_ns_dumping(void)
-{
-	char contents[48];
-	int major, minor, ret;
-	FILE *f;
+	n = write(fd, policy, policy_len);
+	close(fd);
+	if (n < 0 || n != policy_len) {
+		pr_perror("couldn't write policy for %s\n", file);
+		return NULL;
+	}
 
-	f = fopen(AA_SECURITYFS_PATH "/features/domain/stack", "r");
-	if (!f)
-		return false;
+	n = cr_system(-1, -1, -1, cmd[0], cmd, 0);
+	if (n < 0 || !WIFEXITED(n) || WEXITSTATUS(n)) {
+		pr_err("apparmor parsing failed %d\n", n);
+		return NULL;
+	}
 
-	ret = fscanf(f, "%48s", contents);
-	fclose(f);
-	if (ret != 1) {
-		pr_err("scanning aa stack feature failed\n");
-		return false;
+	n = snprintf(file, sizeof(file), "%s/cache/%s", policydir, name);
+	if (n < 0 || n >= sizeof(policy)) {
+		pr_err("policy name %s too long\n", name);
+		return NULL;
 	}
 
-	if (strcmp("yes", contents)) {
-		pr_warn("aa stack featured disabled: %s\n", contents);
-		return false;
+	fd = open(file, O_RDONLY);
+	if (fd < 0) {
+		pr_perror("couldn't open %s", file);
+		return NULL;
 	}
 
-	f = fopen(AA_SECURITYFS_PATH "/features/domain/version", "r");
-	if (!f)
-		return false;
+	if (fstat(fd, &sb) < 0) {
+		pr_perror("couldn't stat fd");
+		goto out;
+	}
 
-	ret = fscanf(f, "%d.%d", &major, &minor);
-	fclose(f);
-	if (ret != 2) {
-		pr_err("scanning aa stack version failed\n");
-		return false;
+	ret = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+	if (ret == MAP_FAILED) {
+		pr_perror("mmap of %s failed", file);
+		goto out;
 	}
 
-	return major >= 1 && minor >= 2;
+	*len = sb.st_size;
+out:
+	close(fd);
+	return ret;
 }
 
 #define NEXT_AA_TOKEN(pos)					\
@@ -422,117 +455,197 @@ bool check_aa_ns_dumping(void)
 		pos++;						\
 	}
 
-static int restore_aa_namespace(AaNamespace *ns, char *path, int offset, char *rewrite)
+static int write_aa_policy(AaNamespace *ns, char *path, int offset, char *rewrite, bool suspend)
 {
-	pid_t pid;
-	int status;
+	int i, my_offset, ret;
+	char *rewrite_pos = rewrite, namespace[PATH_MAX];
 
-	pid = fork();
-	if (pid < 0) {
-		pr_perror("fork failed");
-		return -1;
-	}
-
-	if (!pid) {
-		int i, my_offset, ret, fd;
-		char buf[PATH_MAX], *rewrite_pos = rewrite, namespace[PATH_MAX];
+	BUG_ON(rewrite && suspend);
 
-		if (!rewrite) {
+	if (!rewrite) {
+		strncpy(namespace, ns->name, sizeof(namespace));
+	} else {
+		NEXT_AA_TOKEN(rewrite_pos);
+		switch(*rewrite_pos) {
+		case ':':
+			*(rewrite_pos-3) = 0;
+			strncpy(namespace, rewrite_pos+1, sizeof(namespace));
+			namespace[strlen(namespace)-1] = 0;
+			*(rewrite_pos-3) = ':';
+			break;
+		default:
 			strncpy(namespace, ns->name, sizeof(namespace));
-		} else {
-			NEXT_AA_TOKEN(rewrite_pos);
-			switch(*rewrite_pos) {
-			case ':':
-				*(rewrite_pos-3) = 0;
-				strncpy(namespace, rewrite_pos+1, sizeof(namespace));
-				namespace[strlen(namespace)-1] = 0;
-				*(rewrite_pos-3) = ':';
-				break;
-			default:
-				strncpy(namespace, ns->name, sizeof(namespace));
-				*(rewrite_pos-3) = 0;
-				for (i = 0; i < ns->n_policies; i++) {
-					if (strcmp(ns->policies[i]->name, rewrite_pos))
-						pr_warn("binary rewriting of apparmor policies not supported right now, not renaming %s to %s\n", ns->policies[i]->name, rewrite_pos);
-				}
-				*(rewrite_pos-3) = '/';
+			*(rewrite_pos-3) = 0;
+			for (i = 0; i < ns->n_policies; i++) {
+				if (strcmp(ns->policies[i]->name, rewrite_pos))
+					pr_warn("binary rewriting of apparmor policies not supported right now, not renaming %s to %s\n", ns->policies[i]->name, rewrite_pos);
 			}
+			*(rewrite_pos-3) = '/';
 		}
+	}
 
-		ret = snprintf(buf, sizeof(buf), "changeprofile :%s:", namespace);
-		if (ret < 0 || ret >= sizeof(buf)) {
-			pr_err("profile %s too big\n", ns->name);
-			exit(1);
-		}
+	my_offset = snprintf(path+offset, PATH_MAX-offset, "/namespaces/%s", ns->name);
+	if (my_offset < 0 || my_offset >= PATH_MAX-offset) {
+		pr_err("snprintf'd too many characters\n");
+		return -1;
+	}
 
-		my_offset = snprintf(path+offset, PATH_MAX-offset, "/namespaces/%s", ns->name);
-		if (my_offset < 0 || my_offset >= PATH_MAX-offset) {
-			pr_err("snprintf'd too many characters\n");
-			exit(1);
-		}
+	if (!suspend && mkdir(path, 0755) < 0 && errno != EEXIST) {
+		pr_perror("failed to create namespace %s", path);
+		goto fail;
+	}
 
-		if (mkdir(path, 0755) < 0) {
-			if (errno == EEXIST) {
-				pr_warn("apparmor namespace %s already exists, restoring into it\n", path);
-			} else {
-				pr_perror("failed to create namespace %s", path);
-				exit(1);
-			}
-		}
+	for (i = 0; i < ns->n_namespaces; i++) {
+		if (write_aa_policy(ns, path, offset + my_offset, rewrite_pos, suspend) < 0)
+			goto fail;
+	}
 
-		fd = open_proc_rw(PROC_SELF, "attr/current");
+	ret = snprintf(path+offset+my_offset, sizeof(path)-offset-my_offset, "/.replace");
+	if (ret < 0 || ret >= sizeof(path)-offset-my_offset) {
+		pr_err("snprintf failed\n");
+		goto fail;
+	}
+
+	for (i = 0; i < ns->n_policies; i++) {
+		AaPolicy *p = ns->policies[i];
+		void *data = p->blob.data;
+		int fd, n;
+		off_t len = p->blob.len;
+
+		fd = open(path, O_WRONLY);
 		if (fd < 0) {
-			pr_perror("couldn't open attr/current");
+			pr_perror("couldn't open apparmor load file %s", path);
 			goto fail;
 		}
 
-		errno = 0;
-		ret = write(fd, buf, strlen(buf));
+		if (suspend) {
+			pr_info("suspending policy %s\n", p->name);
+			data = get_suspend_policy(p->name, &len);
+			if (!data) {
+				close(fd);
+				goto fail;
+			}
+		}
+
+		n = write(fd, data, len);
 		close(fd);
-		if (ret != strlen(buf)) {
-			pr_perror("failed to change aa namespace %s", buf);
+		if (suspend && munmap(data, len) < 0) {
+			pr_perror("failed to munmap");
 			goto fail;
 		}
 
-		for (i = 0; i < ns->n_namespaces; i++) {
-			if (restore_aa_namespace(ns, path, offset + my_offset, rewrite_pos) < 0)
-				goto fail;
+		if (n != len) {
+			pr_perror("write AA policy %s in %s failed", p->name, namespace);
+			goto fail;
 		}
+	}
 
-		for (i = 0; i < ns->n_policies; i++) {
-			int fd, n;
-			AaPolicy *p = ns->policies[i];
-
-			fd = open(AA_SECURITYFS_PATH "/.replace", O_WRONLY);
-			if (fd < 0) {
-				pr_perror("couldn't open apparmor load file");
-				goto fail;
-			}
-
-			n = write(fd, p->blob.data, p->blob.len);
-			close(fd);
-			if (n != p->blob.len) {
-				pr_perror("write AA policy failed");
-				goto fail;
-			}
-		}
+	return 0;
 
-		exit(0);
 fail:
+	if (!suspend) {
+		path[offset + my_offset] = 0;
 		rmdir(path);
-		exit(1);
 	}
 
-	if (waitpid(pid, &status, 0) < 0) {
-		pr_perror("waitpid failed");
+	pr_err("failed to write policy in AA namespace %s\n", namespace);
+	return -1;
+}
+
+static int do_suspend(bool suspend)
+{
+	int i;
+
+	for (i = 0; i < n_namespaces; i++) {
+		AaNamespace *ns = namespaces[i];
+		char path[PATH_MAX] = AA_SECURITYFS_PATH "/policy";
+
+		if (write_aa_policy(ns, path, strlen(path), NULL, suspend) < 0)
+			return -1;
+	}
+
+	return 0;
+}
+
+int suspend_aa(void)
+{
+	int ret;
+	if (!mkdtemp(policydir)) {
+		pr_perror("failed to make AA policy dir");
 		return -1;
 	}
 
-	if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
+	ret = do_suspend(true);
+	if (rm_rf(policydir) < 0)
+		pr_err("failed removing policy dir %s\n", policydir);
+
+	return ret;
+}
+
+int unsuspend_aa(void)
+{
+	return do_suspend(false);
+}
+
+int dump_aa_namespaces(void)
+{
+	ApparmorEntry *ae = NULL;
+	int ret;
+
+	if (n_namespaces == 0)
 		return 0;
 
-	pr_err("failed to restore aa namespace, worker exited: %d\n", status);
-	return -1;
+	ae = xmalloc(sizeof(*ae));
+	if (!ae)
+		return -1;
+	apparmor_entry__init(ae);
+
+	ae->n_namespaces = n_namespaces;
+	ae->namespaces = namespaces;
+
+	ret = pb_write_one(img_from_set(glob_imgset, CR_FD_APPARMOR), ae, PB_APPARMOR);
+
+	apparmor_entry__free_unpacked(ae, NULL);
+	n_namespaces = -1;
+	namespaces = NULL;
+
+	return ret;
+}
+
+bool check_aa_ns_dumping(void)
+{
+	char contents[48];
+	int major, minor, ret;
+	FILE *f;
+
+	f = fopen(AA_SECURITYFS_PATH "/features/domain/stack", "r");
+	if (!f)
+		return false;
+
+	ret = fscanf(f, "%48s", contents);
+	fclose(f);
+	if (ret != 1) {
+		pr_err("scanning aa stack feature failed\n");
+		return false;
+	}
+
+	if (strcmp("yes", contents)) {
+		pr_warn("aa stack featured disabled: %s\n", contents);
+		return false;
+	}
+
+	f = fopen(AA_SECURITYFS_PATH "/features/domain/version", "r");
+	if (!f)
+		return false;
+
+	ret = fscanf(f, "%d.%d", &major, &minor);
+	fclose(f);
+	if (ret != 2) {
+		pr_err("scanning aa stack version failed\n");
+		return false;
+	}
+
+	return major >= 1 && minor >= 2;
 }
 
 int prepare_apparmor_namespaces(void)
@@ -558,7 +671,7 @@ int prepare_apparmor_namespaces(void)
 	for (i = 0; i < ae->n_namespaces; i++) {
 		char path[PATH_MAX] = AA_SECURITYFS_PATH "/policy";
 
-		if (restore_aa_namespace(ae->namespaces[i], path, strlen(path), opts.lsm_profile) < 0) {
+		if (write_aa_policy(ae->namespaces[i], path, strlen(path), opts.lsm_profile, false) < 0) {
 			ret = -1;
 			goto out;
 		}
diff --git a/criu/include/apparmor.h b/criu/include/apparmor.h
index 223827e..828784b 100644
--- a/criu/include/apparmor.h
+++ b/criu/include/apparmor.h
@@ -4,6 +4,13 @@
 int collect_aa_namespace(char *profile);
 int dump_aa_namespaces(void);
 
+/* This is an operation similar to PTRACE_O_SUSPEND_SECCOMP but for apparmor,
+ * done entirely from userspace. All the namespaces to be dumped should be
+ * collected via collect_aa_namespaces() before calling this.
+ */
+int suspend_aa(void);
+int unsuspend_aa(void);
+
 extern bool ns_dumping_enabled;
 bool check_aa_ns_dumping(void);
 
diff --git a/criu/lsm.c b/criu/lsm.c
index 58dd2d0..7f3a57a 100644
--- a/criu/lsm.c
+++ b/criu/lsm.c
@@ -190,6 +190,10 @@ int collect_and_suspend_lsm(void)
 
 	/* now, suspend the LSM */
 	switch(lsmtype) {
+	case LSMTYPE__APPARMOR:
+		if (suspend_aa() < 0)
+			return -1;
+		break;
 	default:
 		pr_warn("don't know how to suspend LSM %d\n", lsmtype);
 	}
@@ -199,6 +203,8 @@ int collect_and_suspend_lsm(void)
 
 int unsuspend_lsm(void)
 {
+	if (lsmtype == LSMTYPE__APPARMOR && unsuspend_aa())
+		return -1;
 
 	return 0;
 }
-- 
2.9.3



More information about the CRIU mailing list