CVE-2015-1334: DEPRECATED: Code (CWE-17)
value: attach.c in LXC 1.1.2 and earlier uses the proc filesystem in a container, which allows local container users to escape AppArmor or SELinux confinement by mounting a proc filesystem with a crafted (1) AppArmor profile or (2) SELinux label.
CWE description: This entry has been deprecated.  It was originally used for organizing the Development View (CWE-699) and some other views, but it introduced unnecessary complexity and depth to the resulting tree.
Published: 2015-08-12T14:59Z
Repository: https://github.com/lxc/lxc
Language: C
Files changed: src/lxc/attach.c
Commit date: 2015-07-22 10:10:19 -0400
Commit message: CVE-2015-1334: Don't use the container's /proc during attach

A user could otherwise over-mount /proc and prevent the apparmor profile
or selinux label from being written which combined with a modified
/bin/sh or other commonly used binary would lead to unconfined code
execution.

Reported-by: Roman Fiedler
Signed-off-by: Stéphane Graber <stgraber@ubuntu.com>

Diff: diff --git a/src/lxc/attach.c b/src/lxc/attach.c
index 731d7a63..436ae7a5 100644
--- a/src/lxc/attach.c
+++ b/src/lxc/attach.c
@@ -74,10 +74,86 @@
 #define MS_SLAVE (1<<19)
 #endif
 
 lxc_log_define(lxc_attach, lxc);
 
+int lsm_set_label_at(int procfd, int on_exec, char* lsm_label) {
+	int labelfd = -1;
+	int ret = 0;
+	const char* name;
+	char* command = NULL;
+
+	name = lsm_name();
+
+	if (strcmp(name, "nop") == 0)
+		goto out;
+
+	if (strcmp(name, "none") == 0)
+		goto out;
+
+	/* We don't support on-exec with AppArmor */
+	if (strcmp(name, "AppArmor") == 0)
+		on_exec = 0;
+
+	if (on_exec) {
+		labelfd = openat(procfd, "self/attr/exec", O_RDWR);
+	}
+	else {
+		labelfd = openat(procfd, "self/attr/current", O_RDWR);
+	}
+
+	if (labelfd < 0) {
+		SYSERROR("Unable to open LSM label");
+		ret = -1;
+		goto out;
+	}
+
+	if (strcmp(name, "AppArmor") == 0) {
+		int size;
+
+		command = malloc(strlen(lsm_label) + strlen("changeprofile ") + 1);
+		if (!command) {
+			SYSERROR("Failed to write apparmor profile");
+			ret = -1;
+			goto out;
+		}
+
+		size = sprintf(command, "changeprofile %s", lsm_label);
+		if (size < 0) {
+			SYSERROR("Failed to write apparmor profile");
+			ret = -1;
+			goto out;
+		}
+
+		if (write(labelfd, command, size + 1) < 0) {
+			SYSERROR("Unable to set LSM label");
+			ret = -1;
+			goto out;
+		}
+	}
+	else if (strcmp(name, "SELinux") == 0) {
+		if (write(labelfd, lsm_label, strlen(lsm_label) + 1) < 0) {
+			SYSERROR("Unable to set LSM label");
+			ret = -1;
+			goto out;
+		}
+	}
+	else {
+		ERROR("Unable to restore label for unknown LSM: %s", name);
+		ret = -1;
+		goto out;
+	}
+
+out:
+	free(command);
+
+	if (labelfd != -1)
+		close(labelfd);
+
+	return ret;
+}
+
 static struct lxc_proc_context_info *lxc_proc_get_context_info(pid_t pid)
 {
 	struct lxc_proc_context_info *info = calloc(1, sizeof(*info));
 	FILE *proc_file;
 	char proc_fn[MAXPATHLEN];
@@ -568,10 +644,11 @@ struct attach_clone_payload {
 	int ipc_socket;
 	lxc_attach_options_t* options;
 	struct lxc_proc_context_info* init_ctx;
 	lxc_attach_exec_t exec_function;
 	void* exec_payload;
+	int procfd;
 };
 
 static int attach_child_main(void* data);
 
 /* help the optimizer along if it doesn't know that exit always exits */
@@ -620,10 +697,11 @@ int lxc_attach(const char* name, const char* lxcpath, lxc_attach_exec_t exec_fun
 	pid_t init_pid, pid, attached_pid, expected;
 	struct lxc_proc_context_info *init_ctx;
 	char* cwd;
 	char* new_cwd;
 	int ipc_sockets[2];
+	int procfd;
 	signed long personality;
 
 	if (!options)
 		options = &attach_static_default_options;
 
@@ -831,10 +909,17 @@ int lxc_attach(const char* name, const char* lxcpath, lxc_attach_exec_t exec_fun
 		ERROR("error communicating with child process");
 		shutdown(ipc_sockets[1], SHUT_RDWR);
 		rexit(-1);
 	}
 
+	procfd = open("/proc", O_DIRECTORY | O_RDONLY);
+	if (procfd < 0) {
+		SYSERROR("Unable to open /proc");
+		shutdown(ipc_sockets[1], SHUT_RDWR);
+		rexit(-1);
+	}
+
 	/* attach now, create another subprocess later, since pid namespaces
 	 * only really affect the children of the current process
 	 */
 	ret = lxc_attach_to_ns(init_pid, options->namespaces);
 	if (ret < 0) {
@@ -858,11 +943,12 @@ int lxc_attach(const char* name, const char* lxcpath, lxc_attach_exec_t exec_fun
 		struct attach_clone_payload payload = {
 			.ipc_socket = ipc_sockets[1],
 			.options = options,
 			.init_ctx = init_ctx,
 			.exec_function = exec_function,
-			.exec_payload = exec_payload
+			.exec_payload = exec_payload,
+			.procfd = procfd
 		};
 		/* We use clone_parent here to make this subprocess a direct child of
 		 * the initial process. Then this intermediate process can exit and
 		 * the parent can directly track the attached process.
 		 */
@@ -896,10 +982,11 @@ int lxc_attach(const char* name, const char* lxcpath, lxc_attach_exec_t exec_fun
 
 static int attach_child_main(void* data)
 {
 	struct attach_clone_payload* payload = (struct attach_clone_payload*)data;
 	int ipc_socket = payload->ipc_socket;
+	int procfd = payload->procfd;
 	lxc_attach_options_t* options = payload->options;
 	struct lxc_proc_context_info* init_ctx = payload->init_ctx;
 #if HAVE_SYS_PERSONALITY_H
 	long new_personality;
 #endif
@@ -1036,25 +1123,15 @@ static int attach_child_main(void* data)
 
 	shutdown(ipc_socket, SHUT_RDWR);
 	close(ipc_socket);
 
 	/* set new apparmor profile/selinux context */
-	if ((options->namespaces & CLONE_NEWNS) && (options->attach_flags & LXC_ATTACH_LSM)) {
+	if ((options->namespaces & CLONE_NEWNS) && (options->attach_flags & LXC_ATTACH_LSM) && init_ctx->lsm_label) {
 		int on_exec;
-		int proc_mounted;
 
 		on_exec = options->attach_flags & LXC_ATTACH_LSM_EXEC ? 1 : 0;
-		proc_mounted = mount_proc_if_needed("/");
-		if (proc_mounted == -1) {
-			ERROR("Error mounting a sane /proc");
-			rexit(-1);
-		}
-		ret = lsm_process_label_set(init_ctx->lsm_label,
-				init_ctx->container->lxc_conf, 0, on_exec);
-		if (proc_mounted)
-			umount("/proc");
-		if (ret < 0) {
+		if (lsm_set_label_at(procfd, on_exec, init_ctx->lsm_label) < 0) {
 			rexit(-1);
 		}
 	}
 
 	if (init_ctx->container && init_ctx->container->lxc_conf &&
@@ -1101,10 +1178,13 @@ static int attach_child_main(void* data)
 				SYSERROR("Unable to clear CLOEXEC from fd");
 			}
 		}
 	}
 
+	/* we don't need proc anymore */
+	close(procfd);
+
 	/* we're done, so we can now do whatever the user intended us to do */
 	rexit(payload->exec_function(payload->exec_payload));
 }
 
 int lxc_attach_run_command(void* payload)

Vulnerable code: 			.exec_payload = exec_payload
	if ((options->namespaces & CLONE_NEWNS) && (options->attach_flags & LXC_ATTACH_LSM)) {
		int proc_mounted;
		proc_mounted = mount_proc_if_needed("/");
		if (proc_mounted == -1) {
			ERROR("Error mounting a sane /proc");
			rexit(-1);
		}
		ret = lsm_process_label_set(init_ctx->lsm_label,
				init_ctx->container->lxc_conf, 0, on_exec);
		if (proc_mounted)
			umount("/proc");
		if (ret < 0) {
Fixed code: int lsm_set_label_at(int procfd, int on_exec, char* lsm_label) {
	int labelfd = -1;
	int ret = 0;
	const char* name;
	char* command = NULL;

	name = lsm_name();

	if (strcmp(name, "nop") == 0)
		goto out;

	if (strcmp(name, "none") == 0)
		goto out;

	/* We don't support on-exec with AppArmor */
	if (strcmp(name, "AppArmor") == 0)
		on_exec = 0;

	if (on_exec) {
		labelfd = openat(procfd, "self/attr/exec", O_RDWR);
	}
	else {
		labelfd = openat(procfd, "self/attr/current", O_RDWR);
	}

	if (labelfd < 0) {
		SYSERROR("Unable to open LSM label");
		ret = -1;
		goto out;
	}

	if (strcmp(name, "AppArmor") == 0) {
		int size;

		command = malloc(strlen(lsm_label) + strlen("changeprofile ") + 1);
		if (!command) {
			SYSERROR("Failed to write apparmor profile");
			ret = -1;
			goto out;
		}

		size = sprintf(command, "changeprofile %s", lsm_label);
		if (size < 0) {
			SYSERROR("Failed to write apparmor profile");
			ret = -1;
			goto out;
		}

		if (write(labelfd, command, size + 1) < 0) {
			SYSERROR("Unable to set LSM label");
			ret = -1;
			goto out;
		}
	}
	else if (strcmp(name, "SELinux") == 0) {
		if (write(labelfd, lsm_label, strlen(lsm_label) + 1) < 0) {
			SYSERROR("Unable to set LSM label");
			ret = -1;
			goto out;
		}
	}
	else {
		ERROR("Unable to restore label for unknown LSM: %s", name);
		ret = -1;
		goto out;
	}

out:
	free(command);

	if (labelfd != -1)
		close(labelfd);

	return ret;
}

	int procfd;
	int procfd;
	procfd = open("/proc", O_DIRECTORY | O_RDONLY);
	if (procfd < 0) {
		SYSERROR("Unable to open /proc");
		shutdown(ipc_sockets[1], SHUT_RDWR);
		rexit(-1);
	}

			.exec_payload = exec_payload,
			.procfd = procfd
	int procfd = payload->procfd;
	if ((options->namespaces & CLONE_NEWNS) && (options->attach_flags & LXC_ATTACH_LSM) && init_ctx->lsm_label) {
		if (lsm_set_label_at(procfd, on_exec, init_ctx->lsm_label) < 0) {
	/* we don't need proc anymore */
	close(procfd);