CVE-2017-5985: Missing Authorization (CWE-862)
value: lxc-user-nic in Linux Containers (LXC) allows local users with a lxc-usernet allocation to create network interfaces on the host and choose the name of those interfaces by leveraging lack of netns ownership check.
CWE description: The product does not perform an authorization check when an actor attempts to access a resource or perform an action.
CVSS v3 base score: 3.3
Published: 2017-03-14T17:59Z
Repository: https://github.com/lxc/lxc
Language: C
Files changed: src/lxc/lxc_user_nic.c
Commit date: 2017-03-09 11:35:16 -0500
Commit message: CVE-2017-5985: Ensure target netns is caller-owned

Before this commit, lxc-user-nic could potentially have been tricked into
operating on a network namespace over which the caller did not hold privilege.

This commit ensures that the caller is privileged over the network namespace by
temporarily dropping privilege.

Launchpad: https://bugs.launchpad.net/ubuntu/+source/lxc/+bug/1654676
Reported-by: Jann Horn <jannh@google.com>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>

Diff: diff --git a/src/lxc/lxc_user_nic.c b/src/lxc/lxc_user_nic.c
index 409a53a16..96dc3986f 100644
--- a/src/lxc/lxc_user_nic.c
+++ b/src/lxc/lxc_user_nic.c
@@ -48,10 +48,18 @@
 
 #include "config.h"
 #include "utils.h"
 #include "network.h"
 
+#define usernic_debug_stream(stream, format, ...)                              \
+	do {                                                                   \
+		fprintf(stream, "%s: %d: %s: " format, __FILE__, __LINE__,     \
+			__func__, __VA_ARGS__);                                \
+	} while (false)
+
+#define usernic_error(format, ...) usernic_debug_stream(stderr, format, __VA_ARGS__)
+
 static void usage(char *me, bool fail)
 {
 	fprintf(stderr, "Usage: %s lxcpath name pid type bridge nicname\n", me);
 	fprintf(stderr, " nicname is the name to use inside the container\n");
 	exit(fail ? 1 : 0);
@@ -668,72 +676,119 @@ again:
 	*(p++) = '/';
 	goto again;
 }
 
 #define VETH_DEF_NAME "eth%d"
-
 static int rename_in_ns(int pid, char *oldname, char **newnamep)
 {
-	int fd = -1, ofd = -1, ret, ifindex = -1;
+	uid_t ruid, suid, euid;
+	int fret = -1;
+	int fd = -1, ifindex = -1, ofd = -1, ret;
 	bool grab_newname = false;
 
 	ofd = lxc_preserve_ns(getpid(), "net");
 	if (ofd < 0) {
-		fprintf(stderr, "Failed opening network namespace path for '%d'.", getpid());
-		return -1;
+		usernic_error("Failed opening network namespace path for '%d'.", getpid());
+		return fret;
 	}
 
 	fd = lxc_preserve_ns(pid, "net");
 	if (fd < 0) {
-		fprintf(stderr, "Failed opening network namespace path for '%d'.", pid);
-		return -1;
+		usernic_error("Failed opening network namespace path for '%d'.", pid);
+		goto do_partial_cleanup;
+	}
+
+	ret = getresuid(&ruid, &euid, &suid);
+	if (ret < 0) {
+		usernic_error("Failed to retrieve real, effective, and saved "
+			      "user IDs: %s\n",
+			      strerror(errno));
+		goto do_partial_cleanup;
+	}
+
+	ret = setns(fd, CLONE_NEWNET);
+	close(fd);
+	fd = -1;
+	if (ret < 0) {
+		usernic_error("Failed to setns() to the network namespace of "
+			      "the container with PID %d: %s.\n",
+			      pid, strerror(errno));
+		goto do_partial_cleanup;
 	}
 
-	if (setns(fd, 0) < 0) {
-		fprintf(stderr, "setns to container network namespace\n");
-		goto out_err;
+	ret = setresuid(ruid, ruid, 0);
+	if (ret < 0) {
+		usernic_error("Failed to drop privilege by setting effective "
+			      "user id and real user id to %d, and saved user "
+			      "ID to 0: %s.\n",
+			      ruid, strerror(errno));
+		// COMMENT(brauner): It's ok to jump to do_full_cleanup here
+		// since setresuid() will succeed when trying to set real,
+		// effective, and saved to values they currently have.
+		goto do_full_cleanup;
 	}
-	close(fd); fd = -1;
+
 	if (!*newnamep) {
 		grab_newname = true;
 		*newnamep = VETH_DEF_NAME;
-		if (!(ifindex = if_nametoindex(oldname))) {
-			fprintf(stderr, "failed to get netdev index\n");
-			goto out_err;
+
+		ifindex = if_nametoindex(oldname);
+		if (!ifindex) {
+			usernic_error("Failed to get netdev index: %s.\n", strerror(errno));
+			goto do_full_cleanup;
 		}
 	}
-	if ((ret = lxc_netdev_rename_by_name(oldname, *newnamep)) < 0) {
-		fprintf(stderr, "Error %d renaming netdev %s to %s in container\n", ret, oldname, *newnamep);
-		goto out_err;
+
+	ret = lxc_netdev_rename_by_name(oldname, *newnamep);
+	if (ret < 0) {
+		usernic_error("Error %d renaming netdev %s to %s in container.\n", ret, oldname, *newnamep);
+		goto do_full_cleanup;
 	}
+
 	if (grab_newname) {
-		char ifname[IFNAMSIZ], *namep = ifname;
+		char ifname[IFNAMSIZ];
+		char *namep = ifname;
+
 		if (!if_indextoname(ifindex, namep)) {
-			fprintf(stderr, "Failed to get new netdev name\n");
-			goto out_err;
+			usernic_error("Failed to get new netdev name: %s.\n", strerror(errno));
+			goto do_full_cleanup;
 		}
+
 		*newnamep = strdup(namep);
 		if (!*newnamep)
-			goto out_err;
+			goto do_full_cleanup;
 	}
-	if (setns(ofd, 0) < 0) {
-		fprintf(stderr, "Error returning to original netns\n");
-		close(ofd);
-		return -1;
+
+	fret = 0;
+
+do_full_cleanup:
+	ret = setresuid(ruid, euid, suid);
+	if (ret < 0) {
+		usernic_error("Failed to restore privilege by setting effective "
+			      "user id to %d, real user id to %d, and saved user "
+			      "ID to %d: %s.\n",
+			      ruid, euid, suid, strerror(errno));
+		fret = -1;
+		// COMMENT(brauner): setns() should fail if setresuid() doesn't
+		// succeed but there's no harm in falling through; keeps the
+		// code cleaner.
 	}
-	close(ofd);
 
-	return 0;
+	ret = setns(ofd, CLONE_NEWNET);
+	if (ret < 0) {
+		usernic_error("Failed to setns() to original network namespace "
+			      "of PID %d: %s.\n",
+			      ofd, strerror(errno));
+		fret = -1;
+	}
 
-out_err:
-	if (ofd >= 0)
-		close(ofd);
-	if (setns(ofd, 0) < 0)
-		fprintf(stderr, "Error returning to original network namespace\n");
+do_partial_cleanup:
 	if (fd >= 0)
 		close(fd);
-	return -1;
+	close(ofd);
+
+	return fret;
 }
 
 /*
  * If the caller (real uid, not effective uid) may read the
  * /proc/[pid]/ns/net, then it is either the caller's netns or one

Vulnerable code: 
	int fd = -1, ofd = -1, ret, ifindex = -1;
		fprintf(stderr, "Failed opening network namespace path for '%d'.", getpid());
		return -1;
		fprintf(stderr, "Failed opening network namespace path for '%d'.", pid);
		return -1;
	if (setns(fd, 0) < 0) {
		fprintf(stderr, "setns to container network namespace\n");
		goto out_err;
	close(fd); fd = -1;
		if (!(ifindex = if_nametoindex(oldname))) {
			fprintf(stderr, "failed to get netdev index\n");
			goto out_err;
	if ((ret = lxc_netdev_rename_by_name(oldname, *newnamep)) < 0) {
		fprintf(stderr, "Error %d renaming netdev %s to %s in container\n", ret, oldname, *newnamep);
		goto out_err;
		char ifname[IFNAMSIZ], *namep = ifname;
			fprintf(stderr, "Failed to get new netdev name\n");
			goto out_err;
			goto out_err;
	if (setns(ofd, 0) < 0) {
		fprintf(stderr, "Error returning to original netns\n");
		close(ofd);
		return -1;
	close(ofd);
	return 0;
out_err:
	if (ofd >= 0)
		close(ofd);
	if (setns(ofd, 0) < 0)
		fprintf(stderr, "Error returning to original network namespace\n");
	return -1;
Fixed code: #define usernic_debug_stream(stream, format, ...)                              \
	do {                                                                   \
		fprintf(stream, "%s: %d: %s: " format, __FILE__, __LINE__,     \
			__func__, __VA_ARGS__);                                \
	} while (false)

#define usernic_error(format, ...) usernic_debug_stream(stderr, format, __VA_ARGS__)

	uid_t ruid, suid, euid;
	int fret = -1;
	int fd = -1, ifindex = -1, ofd = -1, ret;
		usernic_error("Failed opening network namespace path for '%d'.", getpid());
		return fret;
		usernic_error("Failed opening network namespace path for '%d'.", pid);
		goto do_partial_cleanup;
	}

	ret = getresuid(&ruid, &euid, &suid);
	if (ret < 0) {
		usernic_error("Failed to retrieve real, effective, and saved "
			      "user IDs: %s\n",
			      strerror(errno));
		goto do_partial_cleanup;
	}

	ret = setns(fd, CLONE_NEWNET);
	close(fd);
	fd = -1;
	if (ret < 0) {
		usernic_error("Failed to setns() to the network namespace of "
			      "the container with PID %d: %s.\n",
			      pid, strerror(errno));
		goto do_partial_cleanup;
	ret = setresuid(ruid, ruid, 0);
	if (ret < 0) {
		usernic_error("Failed to drop privilege by setting effective "
			      "user id and real user id to %d, and saved user "
			      "ID to 0: %s.\n",
			      ruid, strerror(errno));
		// COMMENT(brauner): It's ok to jump to do_full_cleanup here
		// since setresuid() will succeed when trying to set real,
		// effective, and saved to values they currently have.
		goto do_full_cleanup;


		ifindex = if_nametoindex(oldname);
		if (!ifindex) {
			usernic_error("Failed to get netdev index: %s.\n", strerror(errno));
			goto do_full_cleanup;

	ret = lxc_netdev_rename_by_name(oldname, *newnamep);
	if (ret < 0) {
		usernic_error("Error %d renaming netdev %s to %s in container.\n", ret, oldname, *newnamep);
		goto do_full_cleanup;

		char ifname[IFNAMSIZ];
		char *namep = ifname;

			usernic_error("Failed to get new netdev name: %s.\n", strerror(errno));
			goto do_full_cleanup;

			goto do_full_cleanup;

	fret = 0;

do_full_cleanup:
	ret = setresuid(ruid, euid, suid);
	if (ret < 0) {
		usernic_error("Failed to restore privilege by setting effective "
			      "user id to %d, real user id to %d, and saved user "
			      "ID to %d: %s.\n",
			      ruid, euid, suid, strerror(errno));
		fret = -1;
		// COMMENT(brauner): setns() should fail if setresuid() doesn't
		// succeed but there's no harm in falling through; keeps the
		// code cleaner.
	ret = setns(ofd, CLONE_NEWNET);
	if (ret < 0) {
		usernic_error("Failed to setns() to original network namespace "
			      "of PID %d: %s.\n",
			      ofd, strerror(errno));
		fret = -1;
	}
do_partial_cleanup:
	close(ofd);

	return fret;