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;
*/
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;
}
* 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;
}
}
- 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 */
}
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;
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;
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;
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;
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;
out:
free(cgdir);
+ free(next);
return ret;
}
--- /dev/null
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <linux/un.h>
+#include <attr/xattr.h>
+#include <utime.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <libgen.h>
+#include <fcntl.h>
+#include <errno.h>
+
+
+void test_open(const char *path)
+{
+ int fd = open(path, O_RDONLY);
+ if (fd >= 0) {
+ fprintf(stderr, "leak at open of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT) {
+ fprintf(stderr, "leak at open of %s: errno was %d\n", path, errno);
+ exit(1);
+ }
+}
+
+void test_stat(const char *path)
+{
+ struct stat sb;
+ if (stat(path, &sb) >= 0) {
+ fprintf(stderr, "leak at stat of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT) {
+ fprintf(stderr, "leak at stat of %s: errno was %d\n", path, errno);
+ exit(1);
+ }
+}
+
+void test_access(const char *path)
+{
+ if (access(path, O_RDONLY) >= 0) {
+ fprintf(stderr, "leak at access of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT) {
+ fprintf(stderr, "leak at access of %s: errno was %d\n", path, errno);
+ exit(1);
+ }
+}
+
+void test_bind(const char *path)
+{
+ int sfd;
+ struct sockaddr_un my_addr, peer_addr;
+
+ sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ if (sfd < 0) {
+ fprintf(stderr, "Failed to open a socket for bind test\n");
+ exit(1);
+ }
+ memset(&my_addr, 0, sizeof(struct sockaddr_un));
+ my_addr.sun_family = AF_UNIX;
+ strncpy(my_addr.sun_path, path,
+ sizeof(my_addr.sun_path) - 1);
+ if (bind(sfd, (struct sockaddr *) &my_addr,
+ sizeof(struct sockaddr_un)) != -1) {
+ fprintf(stderr, "leak at bind of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at bind of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+ close(sfd);
+}
+
+void test_bindmount(const char *path)
+{
+ if (mount(path, path, "none", MS_BIND, NULL) == 0) {
+ fprintf(stderr, "leak at bind mount of %s\n", path);
+ exit(1);
+ }
+}
+
+void test_truncate(const char *path)
+{
+ if (truncate(path, 0) == 0) {
+ fprintf(stderr, "leak at truncate of %s\n", path);
+ exit(1);
+ }
+}
+
+void test_chdir(const char *path)
+{
+ if (chdir(path) == 0) {
+ fprintf(stderr, "leak at chdir to %s\n", path);
+ exit(1);
+ }
+}
+
+void test_rename(const char *path)
+{
+ char *d = strdupa(path), *tmpname;
+ d = dirname(d);
+ size_t len = strlen(path) + 30;
+
+ tmpname = alloca(len);
+ snprintf(tmpname, len, "%s/%d", d, (int)getpid());
+ if (rename(path, tmpname) == 0 || errno != ENOENT) {
+ fprintf(stderr, "leak at rename of %s\n", path);
+ exit(1);
+ }
+}
+
+void test_mkdir(const char *path)
+{
+ size_t len = strlen(path) + 30;
+ char *tmpname = alloca(len);
+ snprintf(tmpname, len, "%s/%d", path, (int)getpid());
+
+ if (mkdir(path, 0755) == 0) {
+ fprintf(stderr, "leak at mkdir of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT) {
+ fprintf(stderr, "leak at mkdir of %s, errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+ if (mkdir(tmpname, 0755) == 0) {
+ fprintf(stderr, "leak at mkdir of %s\n", tmpname);
+ exit(1);
+ }
+ if (errno != ENOENT) {
+ fprintf(stderr, "leak at mkdir of %s, errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+}
+
+void test_rmdir(const char *path)
+{
+ size_t len = strlen(path) + 30;
+ char *tmpname = alloca(len);
+ snprintf(tmpname, len, "%s/%d", path, (int)getpid());
+
+ if (rmdir(path) == 0 || errno != ENOENT) {
+ fprintf(stderr, "leak at rmdir of %s\n", path);
+ exit(1);
+ }
+ if (rmdir(tmpname) == 0 || errno != ENOENT) {
+ fprintf(stderr, "leak at rmdir of %s\n", tmpname);
+ exit(1);
+ }
+}
+
+void test_creat(const char *path)
+{
+ if (creat(path, 0755) >= 0) {
+ fprintf(stderr, "leak at creat of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at creat of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+}
+
+void test_link(const char *path)
+{
+ char *d = strdupa(path), *tmpname;
+ d = dirname(d);
+ size_t len = strlen(path) + 30;
+ tmpname = alloca(len);
+ snprintf(tmpname, len, "%s/%d", d, (int)getpid());
+
+ if (link(path, tmpname) == 0) {
+ fprintf(stderr, "leak at link of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at link of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+
+ if (link(tmpname, path) == 0) {
+ fprintf(stderr, "leak at link (2) of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at link (2) of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+}
+
+void test_unlink(const char *path)
+{
+ if (unlink(path) == 0) {
+ fprintf(stderr, "leak at unlink of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at unlink of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+}
+
+void test_symlink(const char *path)
+{
+ char *d = strdupa(path), *tmpname;
+ d = dirname(d);
+ size_t len = strlen(path) + 30;
+ tmpname = alloca(len);
+ snprintf(tmpname, len, "%s/%d", d, (int)getpid());
+
+ if (symlink(tmpname, path) == 0) {
+ fprintf(stderr, "leak at symlink of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at symlink of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+ if (symlink(path, tmpname) == 0) {
+ fprintf(stderr, "leak at symlink (2) of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at symlink (2) of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+}
+
+void test_readlink(const char *path)
+{
+ char *dest = alloca(2 * strlen(path));
+
+ if (readlink(path, dest, 2 * strlen(path)) >= 0) {
+ fprintf(stderr, "leak at readlink of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at readlink of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+}
+
+void test_chmod(const char *path)
+{
+ if (chmod(path, 0755) == 0) {
+ fprintf(stderr, "leak at chmod of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at chmod of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+}
+
+void test_chown(const char *path)
+{
+ if (chown(path, 0, 0) == 0) {
+ fprintf(stderr, "leak at chown of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at chown of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+}
+
+void test_lchown(const char *path)
+{
+ if (lchown(path, 0, 0) == 0) {
+ fprintf(stderr, "leak at lchown of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at lchown of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+}
+
+void test_mknod(const char *path)
+{
+ if (mknod(path, 0755, makedev(0, 0)) == 0) {
+ fprintf(stderr, "leak at mknod of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at mknod of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+}
+
+void test_chroot(const char *path)
+{
+ if (chroot(path) == 0) {
+ fprintf(stderr, "leak at chroot of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at chroot of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+}
+
+void test_xattrs(const char *path)
+{
+ /*
+ * might consider doing all of:
+ * setxattr
+ * lsetxattr
+ * getxattr
+ * lgetxattr
+ * listxattr
+ * llistxattr
+ * removexattr
+ * lremovexattr
+ */
+ char value[200];
+ if (getxattr(path, "security.selinux", value, 200) >= 0) {
+ fprintf(stderr, "leak at getxattr of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at getxattr of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+}
+
+void test_utimes(const char *path)
+{
+ struct utimbuf times;
+ times.actime = 0;
+ times.modtime = 0;
+
+ if (utime(path, ×) == 0) {
+ fprintf(stderr, "leak at utime of %s\n", path);
+ exit(1);
+ }
+ if (errno != ENOENT && errno != ENOSYS) {
+ fprintf(stderr, "leak at utime of %s: errno was %s\n", path, strerror(errno));
+ exit(1);
+ }
+}
+
+void test_openat(const char *path)
+{
+ char *d = strdupa(path), *f, *tmpname;
+ int fd, fd2;
+ f = basename(d);
+ d = dirname(d);
+ fd = open(d, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Error in openat test: could not open parent dir\n");
+ fprintf(stderr, "(this is expected on the second run)\n");
+ return;
+ }
+ fd2 = openat(fd, f, O_RDONLY);
+ if (fd2 >= 0 || errno != ENOENT) {
+ fprintf(stderr, "leak at openat of %s\n", f);
+ exit(1);
+ }
+ size_t len = strlen(path) + strlen("/cgroup.procs") + 1;
+ tmpname = alloca(len);
+ snprintf(tmpname, len, "%s/cgroup.procs", f);
+ fd2 = openat(fd, tmpname, O_RDONLY);
+ if (fd2 >= 0 || errno != ENOENT) {
+ fprintf(stderr, "leak at openat of %s\n", tmpname);
+ exit(1);
+ }
+ close(fd);
+}
+
+int main(int argc, char *argv[])
+{
+ char *procspath;
+ size_t len;
+
+ if (geteuid() != 0) {
+ fprintf(stderr, "Run me as root\n");
+ exit(1);
+ }
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s [lxcfs_test_cgroup_path]\n", argv[0]);
+ exit(1);
+ }
+
+ /* Try syscalls on the directory and on $directory/cgroup.procs */
+ len = strlen(argv[1]) + strlen("/cgroup.procs") + 1;
+ procspath = alloca(len);
+ snprintf(procspath, len, "%s/cgroup.procs", argv[1]);
+
+ test_open(argv[1]);
+ test_open(procspath);
+ test_stat(argv[1]);
+ test_stat(procspath);
+ test_access(argv[1]);
+ test_access(procspath);
+
+ test_bind(argv[1]);
+ test_bind(procspath);
+ test_bindmount(argv[1]);
+ test_bindmount(procspath);
+ test_truncate(argv[1]);
+ test_truncate(procspath);
+ test_chdir(argv[1]);
+ test_chdir(procspath);
+ test_rename(argv[1]);
+ test_rename(procspath);
+ test_mkdir(argv[1]);
+ test_mkdir(procspath);
+ test_rmdir(argv[1]);
+ test_rmdir(procspath);
+ test_creat(argv[1]);
+ test_creat(procspath);
+ test_link(argv[1]);
+ test_link(procspath);
+ test_unlink(argv[1]);
+ test_unlink(procspath);
+ test_symlink(argv[1]);
+ test_symlink(procspath);
+ test_readlink(argv[1]);
+ test_readlink(procspath);
+ test_chmod(argv[1]);
+ test_chmod(procspath);
+ test_chown(argv[1]);
+ test_chown(procspath);
+ test_lchown(argv[1]);
+ test_lchown(procspath);
+ test_mknod(argv[1]);
+ test_mknod(procspath);
+ test_chroot(argv[1]);
+ test_chroot(procspath);
+ test_xattrs(argv[1]);
+ test_xattrs(procspath);
+ test_utimes(argv[1]);
+ test_utimes(procspath);
+
+ test_openat(argv[1]);
+ // meh... linkat etc?
+
+ printf("All tests passed\n");
+ return 0;
+}