diff -Nru lxcfs-0.7/debian/changelog lxcfs-0.7/debian/changelog --- lxcfs-0.7/debian/changelog 2015-04-20 08:45:30.000000000 -0500 +++ lxcfs-0.7/debian/changelog 2015-10-26 16:58:08.000000000 -0500 @@ -1,3 +1,11 @@ +lxcfs (0.7-0ubuntu4cve1) vivid-proposed; 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 Mon, 26 Oct 2015 16:57:40 -0500 + lxcfs (0.7-0ubuntu4) vivid; urgency=medium * Add some more sanity checks (LP: #1413405) diff -Nru lxcfs-0.7/debian/patches/0005-fix-checking-of-parent-dirs.patch lxcfs-0.7/debian/patches/0005-fix-checking-of-parent-dirs.patch --- lxcfs-0.7/debian/patches/0005-fix-checking-of-parent-dirs.patch 1969-12-31 18:00:00.000000000 -0600 +++ lxcfs-0.7/debian/patches/0005-fix-checking-of-parent-dirs.patch 2015-10-26 17:17:57.000000000 -0500 @@ -0,0 +1,342 @@ +From cc1d05c12af0ec93392671a97815ac64ab935f25 Mon Sep 17 00:00:00 2001 +From: Serge Hallyn +Date: Wed, 21 Oct 2015 05:26:17 +0000 +Subject: [PATCH 1/1] fix checking of parent dirs + +Signed-off-by: Serge Hallyn +--- + lxcfs.c | 128 ++++++++++++++++++++++++++++++++++++++++++---------------------- + 1 file changed, 85 insertions(+), 43 deletions(-) + +Index: lxcfs-0.7/lxcfs.c +=================================================================== +--- lxcfs-0.7.orig/lxcfs.c ++++ lxcfs-0.7/lxcfs.c +@@ -180,6 +180,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; +@@ -248,17 +254,11 @@ static void stripnewline(char *x) + x[l-1] = '\0'; + } + +-/* +- * If caller is in /a/b/c/d, he may only act on things under cg=/a/b/c/d. +- * If caller is in /a, he may act on /a/b, but not on /b. +- * if the answer is false and nextcg is not NULL, then *nextcg will point +- * to a nih_alloc'd string containing the next cgroup directory under cg +- */ +-static bool caller_is_in_ancestor(pid_t pid, const char *contrl, const char *cg, char **nextcg) ++static char *get_pid_cgroup(pid_t pid, const char *contrl) + { + nih_local char *fnam = NULL; + FILE *f; +- bool answer = false; ++ char *answer = NULL; + char *line = NULL; + size_t len = 0; + +@@ -267,7 +267,7 @@ static bool caller_is_in_ancestor(pid_t + return false; + + while (getline(&line, &len, f) != -1) { +- char *c1, *c2, *linecmp; ++ char *c1, *c2; + if (!line[0]) + continue; + c1 = strchr(line, ':'); +@@ -282,17 +282,7 @@ static bool caller_is_in_ancestor(pid_t + continue; + c2++; + stripnewline(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; +- } +- answer = true; ++ answer = NIH_MUST( nih_strdup(NULL, c2) ); + goto out; + } + +@@ -303,6 +293,72 @@ out: + } + + /* ++ * If caller is in /a/b/c/d, he may only act on things under cg=/a/b/c/d. ++ * If caller is in /a, he may act on /a/b, but not on /b. ++ * if the answer is false and nextcg is not NULL, then *nextcg will point ++ * to a nih_alloc'd string containing the next cgroup directory under cg ++ */ ++static bool caller_is_in_ancestor(pid_t pid, const char *contrl, const char *cg, char **nextcg) ++{ ++ nih_local char *c2 = get_pid_cgroup(pid, contrl); ++ char *linecmp; ++ ++ if (!c2) ++ return false; ++ /* ++ * 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); ++ } ++ return false; ++ } ++ return true; ++ ++} ++ ++/* ++ * 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) ++{ ++ nih_local char *c2 = NULL; ++ size_t l1, l2; ++ ++ if (strcmp(cg, "/") == 0) ++ return true; ++ ++ c2 = get_pid_cgroup(pid, contrl); ++ ++ if (!c2) ++ return false; ++ ++ char *tcg = c2 + 1; ++ l1 = strlen(cg); ++ l2 = strlen(tcg); ++ if (strcmp(cg, tcg) == 0) ++ return true; ++ ++ if (l1 < l2) { ++ /* looking up a parent dir */ ++ if (strncmp(tcg, cg, l1) == 0 && tcg[l1] == '/') ++ return true; ++ return false; ++ } ++ if (l1 > l2) { ++ /* looking up a child dir */ ++ if (strncmp(tcg, cg, l2) == 0 && cg[l2] == '/') ++ return true; ++ return false; ++ } ++ ++ return false; ++ } ++ ++/* + * given /cgroup/freezer/a/b, return "freezer". this will be nih-allocated + * and needs to be nih_freed. + */ +@@ -483,6 +539,8 @@ 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)) ++ return -ENOENT; + 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; +@@ -552,6 +610,8 @@ static int cg_opendir(const char *path, + cgroup = "/"; + } + ++ if (!caller_may_see_dir(fc->pid, controller, cgroup)) ++ return -ENOENT; + if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY)) + return -EACCES; + return 0; +@@ -671,6 +731,9 @@ static int cg_open(const char *path, str + } + + if ((k = get_cgroup_key(controller, path1, path2)) != NULL) { ++ if (!caller_may_see_dir(fc->pid, controller, path1)) ++ return -ENOENT; ++ + if (!fc_may_access(fc, controller, path1, path2, fi->flags)) + // should never get here + return -EACCES; +@@ -1431,6 +1494,14 @@ int cg_mkdir(const char *path, mode_t mo + else + path1 = cgdir; + ++ nih_local char *next = NULL; ++ bool ret = caller_is_in_ancestor(fc->pid, controller, path1, &next); ++ if (!ret ) { ++ if (!fpath || strcmp(next, fpath) == 0) ++ return -EEXIST; ++ return -ENOENT; ++ } ++ + if (!fc_may_access(fc, controller, path1, NULL, O_RDWR)) + return -EACCES; + +@@ -1466,6 +1537,14 @@ static int cg_rmdir(const char *path) + if (!fpath) + return -EINVAL; + ++ nih_local char *next = NULL; ++ bool ret = caller_is_in_ancestor(fc->pid, controller, cgroup, &next); ++ if (!ret ) { ++ if (!fpath || strcmp(next, fpath) == 0) ++ return -EBUSY; ++ return -ENOENT; ++ } ++ + if (!fc_may_access(fc, controller, cgdir, NULL, O_WRONLY)) + return -EACCES; + +@@ -1523,44 +1602,6 @@ static void get_blkio_io_value(char *str + } + } + +-static char *get_pid_cgroup(pid_t pid, const char *contrl) +-{ +- nih_local char *fnam = NULL; +- FILE *f; +- char *answer = NULL; +- char *line = NULL; +- size_t len = 0; +- +- fnam = NIH_MUST( nih_sprintf(NULL, "/proc/%d/cgroup", pid) ); +- if (!(f = fopen(fnam, "r"))) +- return false; +- +- while (getline(&line, &len, f) != -1) { +- char *c1, *c2; +- 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); +- answer = NIH_MUST( nih_strdup(NULL, c2) ); +- goto out; +- } +- +-out: +- fclose(f); +- free(line); +- return answer; +-} +- + /* + * FUSE ops for /proc + */ +Index: lxcfs-0.7/tests/test_confinement.sh +=================================================================== +--- /dev/null ++++ lxcfs-0.7/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.7/debian/patches/series lxcfs-0.7/debian/patches/series --- lxcfs-0.7/debian/patches/series 2015-04-20 08:47:37.000000000 -0500 +++ lxcfs-0.7/debian/patches/series 2015-10-26 16:46:16.000000000 -0500 @@ -2,3 +2,4 @@ 0002-Make-sure-that-that-cgroup-and-the-controller-are-se.patch 0003-free-d-at-program-end.patch 0004-Add-some-more-sanity-checks.patch +0005-fix-checking-of-parent-dirs.patch