CVE-2015-1331: Improper Link Resolution Before File Access ('Link Following') (CWE-59)
value: lxclock.c in LXC 1.1.2 and earlier allows local users to create arbitrary files via a symlink attack on /run/lock/lxc/*.
CWE description: The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.
Published: 2015-08-12T14:59Z
Repository: https://github.com/lxc/lxc
Language: C
Files changed: src/lxc/lxclock.c, src/tests/locktests.c
Commit date: 2015-07-22 10:09:09 -0400
Commit message: CVE-2015-1331: lxclock: use /run/lxc/lock rather than /run/lock/lxc

This prevents an unprivileged user to use LXC to create arbitrary file
on the filesystem.

Signed-off-by: Serge Hallyn <serge.hallyn@ubuntu.com>
Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Stéphane Graber <stgraber@ubuntu.com>

Diff: diff --git a/src/lxc/lxclock.c b/src/lxc/lxclock.c
index fe13898d..e9e95f7a 100644
--- a/src/lxc/lxclock.c
+++ b/src/lxc/lxclock.c
@@ -101,69 +101,42 @@ static char *lxclock_name(const char *p, const char *n)
 	int len;
 	char *dest;
 	char *rundir;
 
 	/* lockfile will be:
-	 * "/run" + "/lock/lxc/$lxcpath/$lxcname + '\0' if root
+	 * "/run" + "/lxc/lock/$lxcpath/$lxcname + '\0' if root
 	 * or
-	 * $XDG_RUNTIME_DIR + "/lock/lxc/$lxcpath/$lxcname + '\0' if non-root
+	 * $XDG_RUNTIME_DIR + "/lxc/lock/$lxcpath/$lxcname + '\0' if non-root
 	 */
 
-	/* length of "/lock/lxc/" + $lxcpath + "/" + "." + $lxcname + '\0' */
-	len = strlen("/lock/lxc/") + strlen(n) + strlen(p) + 3;
+	/* length of "/lxc/lock/" + $lxcpath + "/" + "." + $lxcname + '\0' */
+	len = strlen("/lxc/lock/") + strlen(n) + strlen(p) + 3;
 	rundir = get_rundir();
 	if (!rundir)
 		return NULL;
 	len += strlen(rundir);
 
 	if ((dest = malloc(len)) == NULL) {
 		free(rundir);
 		return NULL;
 	}
 
-	ret = snprintf(dest, len, "%s/lock/lxc/%s", rundir, p);
+	ret = snprintf(dest, len, "%s/lxc/lock/%s", rundir, p);
 	if (ret < 0 || ret >= len) {
 		free(dest);
 		free(rundir);
 		return NULL;
 	}
 	ret = mkdir_p(dest, 0755);
 	if (ret < 0) {
-		/* fall back to "/tmp/" + $(id -u) + "/lxc" + $lxcpath + "/" + "." + $lxcname + '\0'
-		 * * maximum length of $(id -u) is 10 calculated by (log (2 ** (sizeof(uid_t) * 8) - 1) / log 10 + 1)
-		 * * lxcpath always starts with '/'
-		 */
-		int l2 = 22 + strlen(n) + strlen(p);
-		if (l2 > len) {
-			char *d;
-			d = realloc(dest, l2);
-			if (!d) {
-				free(dest);
-				free(rundir);
-				return NULL;
-			}
-			len = l2;
-			dest = d;
-		}
-		ret = snprintf(dest, len, "/tmp/%d/lxc%s", geteuid(), p);
-		if (ret < 0 || ret >= len) {
-			free(dest);
-			free(rundir);
-			return NULL;
-		}
-		ret = mkdir_p(dest, 0755);
-		if (ret < 0) {
-			free(dest);
-			free(rundir);
-			return NULL;
-		}
-		ret = snprintf(dest, len, "/tmp/%d/lxc%s/.%s", geteuid(), p, n);
-	} else
-		ret = snprintf(dest, len, "%s/lock/lxc/%s/.%s", rundir, p, n);
+		free(dest);
+		free(rundir);
+		return NULL;
+	}
 
+	ret = snprintf(dest, len, "%s/lxc/lock/%s/.%s", rundir, p, n);
 	free(rundir);
-
 	if (ret < 0 || ret >= len) {
 		free(dest);
 		return NULL;
 	}
 	return dest;
diff --git a/src/tests/locktests.c b/src/tests/locktests.c
index dd3393a8..233ca127 100644
--- a/src/tests/locktests.c
+++ b/src/tests/locktests.c
@@ -120,11 +120,11 @@ int main(int argc, char *argv[])
 	if (!lock) {
 		fprintf(stderr, "%d: failed to get lock\n", __LINE__);
 		exit(1);
 	}
 	struct stat sb;
-	char *pathname = RUNTIME_PATH "/lock/lxc/var/lib/lxc/";
+	char *pathname = RUNTIME_PATH "/lxc/lock/var/lib/lxc/";
 	ret = stat(pathname, &sb);
 	if (ret != 0) {
 		fprintf(stderr, "%d: filename %s not created\n", __LINE__,
 			pathname);
 		exit(1);

Vulnerable code: 	 * "/run" + "/lock/lxc/$lxcpath/$lxcname + '\0' if root
	 * $XDG_RUNTIME_DIR + "/lock/lxc/$lxcpath/$lxcname + '\0' if non-root
	/* length of "/lock/lxc/" + $lxcpath + "/" + "." + $lxcname + '\0' */
	len = strlen("/lock/lxc/") + strlen(n) + strlen(p) + 3;
	ret = snprintf(dest, len, "%s/lock/lxc/%s", rundir, p);
		/* fall back to "/tmp/" + $(id -u) + "/lxc" + $lxcpath + "/" + "." + $lxcname + '\0'
		 * * maximum length of $(id -u) is 10 calculated by (log (2 ** (sizeof(uid_t) * 8) - 1) / log 10 + 1)
		 * * lxcpath always starts with '/'
		 */
		int l2 = 22 + strlen(n) + strlen(p);
		if (l2 > len) {
			char *d;
			d = realloc(dest, l2);
			if (!d) {
				free(dest);
				free(rundir);
				return NULL;
			}
			len = l2;
			dest = d;
		}
		ret = snprintf(dest, len, "/tmp/%d/lxc%s", geteuid(), p);
		if (ret < 0 || ret >= len) {
			free(dest);
			free(rundir);
			return NULL;
		}
		ret = mkdir_p(dest, 0755);
		if (ret < 0) {
			free(dest);
			free(rundir);
			return NULL;
		}
		ret = snprintf(dest, len, "/tmp/%d/lxc%s/.%s", geteuid(), p, n);
	} else
		ret = snprintf(dest, len, "%s/lock/lxc/%s/.%s", rundir, p, n);

	char *pathname = RUNTIME_PATH "/lock/lxc/var/lib/lxc/";
Fixed code: 	 * "/run" + "/lxc/lock/$lxcpath/$lxcname + '\0' if root
	 * $XDG_RUNTIME_DIR + "/lxc/lock/$lxcpath/$lxcname + '\0' if non-root
	/* length of "/lxc/lock/" + $lxcpath + "/" + "." + $lxcname + '\0' */
	len = strlen("/lxc/lock/") + strlen(n) + strlen(p) + 3;
	ret = snprintf(dest, len, "%s/lxc/lock/%s", rundir, p);
		free(dest);
		free(rundir);
		return NULL;
	}
	ret = snprintf(dest, len, "%s/lxc/lock/%s/.%s", rundir, p, n);
	char *pathname = RUNTIME_PATH "/lxc/lock/var/lib/lxc/";