diff -Nru lxcfs-0.11/debian/changelog lxcfs-0.11/debian/changelog --- lxcfs-0.11/debian/changelog 2015-10-28 02:11:17.000000000 -0500 +++ lxcfs-0.11/debian/changelog 2015-10-28 20:37:17.000000000 -0500 @@ -1,3 +1,11 @@ +lxcfs (0.11-0ubuntu3) xenial; urgency=medium + + * fix CVE-2015-1342 (LP: #1508481) + Ensure that a task under cgroup /a/b cannot mkdir, rmdir, or modify files + under, directories not under /a/b. Add a testcase for this. + + -- Serge Hallyn Wed, 28 Oct 2015 20:36:55 -0500 + lxcfs (0.11-0ubuntu2) xenial; urgency=medium * Set Delegate=yes in the systemd unit. Looks like that's needed to diff -Nru lxcfs-0.11/debian/patches/0002-cve-fix-checking-of-parent-dirs lxcfs-0.11/debian/patches/0002-cve-fix-checking-of-parent-dirs --- lxcfs-0.11/debian/patches/0002-cve-fix-checking-of-parent-dirs 1969-12-31 18:00:00.000000000 -0600 +++ lxcfs-0.11/debian/patches/0002-cve-fix-checking-of-parent-dirs 2015-10-28 21:15:42.000000000 -0500 @@ -0,0 +1,326 @@ +commit 893bea0b143d97c051059ee375b95b962516f7d2 +Author: Serge Hallyn +Date: Wed Oct 21 05:26:17 2015 +0000 + + fix checking of parent dirs + + Signed-off-by: Serge Hallyn + +Index: lxcfs-0.11/lxcfs.c +=================================================================== +--- lxcfs-0.11.orig/lxcfs.c ++++ lxcfs-0.11/lxcfs.c +@@ -242,6 +242,12 @@ static bool perms_include(int fmode, mod + return ((fmode & r) == r); + } + ++ ++/* ++ * taskcg is a/b/c ++ * querycg is /a/b/c/d/e ++ * we return 'd' ++ */ + static char *get_next_cgroup_dir(const char *taskcg, const char *querycg) + { + char *start, *end; +@@ -387,53 +393,71 @@ static void prune_init_slice(char *cg) + */ + static bool caller_is_in_ancestor(pid_t pid, const char *contrl, const char *cg, char **nextcg) + { +- char fnam[PROCLEN]; +- FILE *f; + bool answer = false; +- char *line = NULL; +- size_t len = 0; +- int ret; ++ char *c2 = get_pid_cgroup(pid, contrl); ++ char *linecmp; + +- ret = snprintf(fnam, PROCLEN, "/proc/%d/cgroup", pid); +- if (ret < 0 || ret >= PROCLEN) +- return false; +- if (!(f = fopen(fnam, "r"))) ++ if (!c2) + return false; ++ prune_init_slice(c2); + +- while (getline(&line, &len, f) != -1) { +- char *c1, *c2, *linecmp; +- if (!line[0]) +- continue; +- c1 = strchr(line, ':'); +- if (!c1) +- goto out; +- c1++; +- c2 = strchr(c1, ':'); +- if (!c2) +- goto out; +- *c2 = '\0'; +- if (strcmp(c1, contrl) != 0) +- continue; +- c2++; +- stripnewline(c2); +- prune_init_slice(c2); +- /* +- * callers pass in '/' for root cgroup, otherwise they pass +- * in a cgroup without leading '/' +- */ +- linecmp = *cg == '/' ? c2 : c2+1; +- if (strncmp(linecmp, cg, strlen(linecmp)) != 0) { +- if (nextcg) +- *nextcg = get_next_cgroup_dir(linecmp, cg); +- goto out; ++ /* ++ * callers pass in '/' for root cgroup, otherwise they pass ++ * in a cgroup without leading '/' ++ */ ++ linecmp = *cg == '/' ? c2 : c2+1; ++ if (strncmp(linecmp, cg, strlen(linecmp)) != 0) { ++ if (nextcg) { ++ *nextcg = get_next_cgroup_dir(linecmp, cg); + } ++ goto out; ++ } ++ answer = true; ++ ++out: ++ free(c2); ++ return answer; ++} ++ ++/* ++ * If caller is in /a/b/c, he may see that /a exists, but not /b or /a/c. ++ */ ++static bool caller_may_see_dir(pid_t pid, const char *contrl, const char *cg) ++{ ++ bool answer = false; ++ char *c2, *task_cg; ++ size_t target_len, task_len; ++ ++ if (strcmp(cg, "/") == 0) ++ return true; ++ ++ c2 = get_pid_cgroup(pid, contrl); ++ ++ if (!c2) ++ return false; ++ ++ task_cg = c2 + 1; ++ target_len = strlen(cg); ++ task_len = strlen(task_cg); ++ if (strcmp(cg, task_cg) == 0) { + answer = true; + goto out; + } ++ if (target_len < task_len) { ++ /* looking up a parent dir */ ++ if (strncmp(task_cg, cg, target_len) == 0 && task_cg[target_len] == '/') ++ answer = true; ++ goto out; ++ } ++ if (target_len > task_len) { ++ /* looking up a child dir */ ++ if (strncmp(task_cg, cg, task_len) == 0 && cg[task_len] == '/') ++ answer = true; ++ goto out; ++ } + + out: +- fclose(f); +- free(line); ++ free(c2); + return answer; + } + +@@ -622,6 +646,10 @@ static int cg_getattr(const char *path, + * cgroup, or cgdir if fpath is a file */ + + if (is_child_cgroup(controller, path1, path2)) { ++ if (!caller_may_see_dir(fc->pid, controller, cgroup)) { ++ ret = -ENOENT; ++ goto out; ++ } + if (!caller_is_in_ancestor(fc->pid, controller, cgroup, NULL)) { + /* this is just /cgroup/controller, return it as a dir */ + sb->st_mode = S_IFDIR | 00555; +@@ -700,8 +728,11 @@ static int cg_opendir(const char *path, + } + } + +- if (cgroup && !fc_may_access(fc, controller, cgroup, NULL, O_RDONLY)) { +- return -EACCES; ++ if (cgroup) { ++ if (!caller_may_see_dir(fc->pid, controller, cgroup)) ++ return -ENOENT; ++ if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY)) ++ return -EACCES; + } + + /* we'll free this at cg_releasedir */ +@@ -854,6 +885,10 @@ static int cg_open(const char *path, str + } + free_key(k); + ++ if (!caller_may_see_dir(fc->pid, controller, path1)) { ++ ret = -ENOENT; ++ goto out; ++ } + if (!fc_may_access(fc, controller, path1, path2, fi->flags)) { + // should never get here + ret = -EACCES; +@@ -1631,7 +1666,7 @@ out: + int cg_mkdir(const char *path, mode_t mode) + { + struct fuse_context *fc = fuse_get_context(); +- char *fpath = NULL, *path1, *cgdir = NULL, *controller; ++ char *fpath = NULL, *path1, *cgdir = NULL, *controller, *next = NULL; + const char *cgroup; + int ret; + +@@ -1653,6 +1688,14 @@ int cg_mkdir(const char *path, mode_t mo + else + path1 = cgdir; + ++ if (!caller_is_in_ancestor(fc->pid, controller, path1, &next)) { ++ if (!fpath || strcmp(next, fpath) == 0) ++ ret = -EEXIST; ++ else ++ ret = -ENOENT; ++ goto out; ++ } ++ + if (!fc_may_access(fc, controller, path1, NULL, O_RDWR)) { + ret = -EACCES; + goto out; +@@ -1690,13 +1733,14 @@ int cg_mkdir(const char *path, mode_t mo + + out: + free(cgdir); ++ free(next); + return ret; + } + + static int cg_rmdir(const char *path) + { + struct fuse_context *fc = fuse_get_context(); +- char *fpath = NULL, *cgdir = NULL, *controller; ++ char *fpath = NULL, *cgdir = NULL, *controller, *next = NULL; + const char *cgroup; + int ret; + +@@ -1717,8 +1761,14 @@ static int cg_rmdir(const char *path) + goto out; + } + +- fprintf(stderr, "rmdir: verifying access to %s:%s (req path %s)\n", +- controller, cgdir, path); ++ if (!caller_is_in_ancestor(fc->pid, controller, cgroup, &next)) { ++ if (!fpath || strcmp(next, fpath) == 0) ++ ret = -EBUSY; ++ else ++ ret = -ENOENT; ++ goto out; ++ } ++ + if (!fc_may_access(fc, controller, cgdir, NULL, O_WRONLY)) { + ret = -EACCES; + goto out; +@@ -1737,6 +1787,7 @@ static int cg_rmdir(const char *path) + + out: + free(cgdir); ++ free(next); + return ret; + } + +Index: lxcfs-0.11/tests/test_confinement.sh +=================================================================== +--- /dev/null ++++ lxcfs-0.11/tests/test_confinement.sh +@@ -0,0 +1,86 @@ ++#!/bin/bash ++ ++set -ex ++ ++[ $(id -u) -eq 0 ] ++ ++d=$(mktemp -d tmp.XXX) ++d2=$(mktemp -d tmp.XXX) ++ ++pid=-1 ++cleanup() { ++ [ $pid -ne -1 ] && kill -9 $pid ++ umount -l $d || true ++ umount -l $d2 || true ++ rm -rf $d $d2 ++} ++ ++trap cleanup EXIT HUP INT TERM ++ ++lxcfs $d & ++pid=$! ++ ++# put ourselves into x1 ++cgm movepidabs freezer / $$ ++cgm create freezer x1 ++cgm movepid freezer x1 $$ ++ ++mount -t cgroup -o freezer freezer $d2 ++sudo rmdir $d2/lxcfs_test_a1/lxcfs_test_a2 || true ++sudo rmdir $d2/lxcfs_test_a1 || true ++ ++echo "Making sure root cannot mkdir" ++bad=0 ++mkdir $d/cgroup/freezer/lxcfs_test_a1 && bad=1 ++if [ "${bad}" -eq 1 ]; then ++ false ++fi ++ ++echo "Making sure root cannot rmdir" ++mkdir $d2/lxcfs_test_a1 ++mkdir $d2/lxcfs_test_a1/lxcfs_test_a2 ++rmdir $d/cgroup/freezer/lxcfs_test_a1 && bad=1 ++if [ "${bad}" -eq 1 ]; then ++ false ++fi ++[ -d $d2/lxcfs_test_a1 ] ++rmdir $d/cgroup/freezer/lxcfs_test_a1/lxcfs_test_a2 && bad=1 ++if [ "${bad}" -eq 1 ]; then ++ false ++fi ++[ -d $d2/lxcfs_test_a1/lxcfs_test_a2 ] ++ ++echo "Making sure root cannot read/write" ++sleep 200 & ++p=$! ++echo $p > $d/cgroup/freezer/lxcfs_test_a1/tasks && bad=1 ++if [ "${bad}" -eq 1 ]; then ++ false ++fi ++cat $d/cgroup/freezer/lxcfs_test_a1/tasks && bad=1 ++if [ "${bad}" -eq 1 ]; then ++ false ++fi ++echo $p > $d/cgroup/freezer/lxcfs_test_a1/lxcfs_test_a2/tasks && bad=1 ++if [ "${bad}" -eq 1 ]; then ++ false ++fi ++cat $d/cgroup/freezer/lxcfs_test_a1/lxcfs_test_a2/tasks && bad=1 ++if [ "${bad}" -eq 1 ]; then ++ false ++fi ++ ++echo "Making sure root can act on descendents" ++mycg=$(cgm getpidcgroupabs freezer $$) ++newcg=${mycg}/lxcfs_test_a1 ++rmdir $d2/$newcg || true # cleanup previosu run ++mkdir $d/cgroup/freezer/$newcg ++echo $p > $d/cgroup/freezer/$newcg/tasks ++cat $d/cgroup/freezer/$newcg/tasks ++kill -9 $p ++while [ `wc -l $d/cgroup/freezer/$newcg/tasks | awk '{ print $1 }'` -ne 0 ]; do ++ sleep 1 ++done ++rmdir $d/cgroup/freezer/$newcg ++ ++echo "All tests passed!" diff -Nru lxcfs-0.11/debian/patches/series lxcfs-0.11/debian/patches/series --- lxcfs-0.11/debian/patches/series 2015-10-27 15:58:15.000000000 -0500 +++ lxcfs-0.11/debian/patches/series 2015-10-28 20:39:05.000000000 -0500 @@ -1 +1,2 @@ 0001-Don-t-depend-on-libcgmanager-dev.patch +0002-cve-fix-checking-of-parent-dirs diff -Nru lxcfs-0.11/debian/rules lxcfs-0.11/debian/rules --- lxcfs-0.11/debian/rules 2015-10-27 15:58:06.000000000 -0500 +++ lxcfs-0.11/debian/rules 2015-10-28 20:37:55.000000000 -0500 @@ -14,3 +14,7 @@ override_dh_autoreconf: [ -e m4 ] || mkdir m4 dh_autoreconf + +clean: + dh_autoreconf_clean + dh_clean