CVE-2017-5985 (published 2017-03-14T17:59Z)

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.

Severity: LOW
CVSS v2 base score: 2.1 (AV:L/AC:L/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 3.3, LOW (CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N)

Weakness information:
cwe_id: CWE-862
cwe_name: Missing Authorization
description: The product does not perform an authorization check when an actor attempts to access a resource or perform an action.
extended_description: 
            Assuming a user with a given identity, authorization is the process of determining whether that user can access a given resource, based on the user's privileges and any permissions or other access-control specifications that apply to the resource.
            When access control checks are not applied, users are able to access data or perform actions that they should not be allowed to perform. This can lead to a wide range of problems, including information exposures, denial of service, and arbitrary code execution.
         
         

Fix information:
repo_url: https://github.com/lxc/lxc
commit_details: repo_url: https://github.com/lxc/lxc
author: Christian Brauner
author_date: 2017-01-28 13:02:34+01:00
msg: 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>
repository: repo_url: https://github.com/lxc/lxc
repo_name: lxc/lxc
description: LXC - Linux Containers
repo_language: C
owner: lxc
file_changes: filename: lxc_user_nic.c
change_type: ModificationType.MODIFY
diff: @@ -50,6 +50,14 @@
 #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);
@@ -670,68 +678,115 @@ 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;
 }
 
 /*

programming_language: C