]> git.proxmox.com Git - mirror_lxcfs.git/blobdiff - bindings.c
bindings: caller_may_see_dir()
[mirror_lxcfs.git] / bindings.c
index 97fc39746ec9c6e0d55d421878ae7a3e88f1aa19..fb905c19c62daa8736e6d495e2b5dc1c1d6e7200 100644 (file)
@@ -307,11 +307,11 @@ static void append_line(char **contents, size_t *len, char *line, ssize_t linele
        *len = newlen;
 }
 
-static char *slurp_file(const char *from)
+static char *slurp_file(const char *from, int fd)
 {
        char *line = NULL;
        char *contents = NULL;
-       FILE *f = fopen(from, "r");
+       FILE *f = fdopen(fd, "r");
        size_t len = 0, fulllen = 0;
        ssize_t linelen;
 
@@ -350,12 +350,6 @@ static bool write_string(const char *fnam, const char *string)
        return true;
 }
 
-/*
- * hierarchies, i.e. 'cpu,cpuacct'
- */
-char **hierarchies;
-int num_hierarchies;
-
 struct cgfs_files {
        char *name;
        uint32_t uid, gid;
@@ -384,7 +378,7 @@ static void print_subsystems(void)
 {
        int i;
 
-       fprintf(stderr, "hierarchies:");
+       fprintf(stderr, "hierarchies:\n");
        for (i = 0; i < num_hierarchies; i++) {
                if (hierarchies[i])
                        fprintf(stderr, " %d: %s\n", i, hierarchies[i]);
@@ -411,17 +405,25 @@ static bool in_comma_list(const char *needle, const char *haystack)
 }
 
 /* do we need to do any massaging here?  I'm not sure... */
-static char *find_mounted_controller(const char *controller)
+/* Return the mounted controller and store the corresponding open file descriptor
+ * referring to the controller mountpoint in the private lxcfs namespace in
+ * @cfd.
+ */
+static char *find_mounted_controller(const char *controller, int *cfd)
 {
        int i;
 
        for (i = 0; i < num_hierarchies; i++) {
                if (!hierarchies[i])
                        continue;
-               if (strcmp(hierarchies[i], controller) == 0)
+               if (strcmp(hierarchies[i], controller) == 0) {
+                       *cfd = fd_hierarchies[i];
                        return hierarchies[i];
-               if (in_comma_list(controller, hierarchies[i]))
+               }
+               if (in_comma_list(controller, hierarchies[i])) {
+                       *cfd = fd_hierarchies[i];
                        return hierarchies[i];
+               }
        }
 
        return NULL;
@@ -430,15 +432,16 @@ static char *find_mounted_controller(const char *controller)
 bool cgfs_set_value(const char *controller, const char *cgroup, const char *file,
                const char *value)
 {
+       int cfd;
        size_t len;
-       char *fnam, *tmpc = find_mounted_controller(controller);
+       char *fnam, *tmpc = find_mounted_controller(controller, &cfd);
 
        if (!tmpc)
                return false;
-       /* basedir / tmpc / cgroup / file \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + strlen(file) + 4;
+       /* BASEDIR / tmpc / cgroup / file \0 */
+       len = strlen(BASEDIR) + strlen(tmpc) + strlen(cgroup) + strlen(file) + 4;
        fnam = alloca(len);
-       snprintf(fnam, len, "%s/%s/%s/%s", basedir, tmpc, cgroup, file);
+       snprintf(fnam, len, "%s/%s/%s/%s", BASEDIR, tmpc, cgroup, file);
 
        return write_string(fnam, value);
 }
@@ -481,15 +484,16 @@ static void chown_all_cgroup_files(const char *dirname, uid_t uid, gid_t gid)
 
 int cgfs_create(const char *controller, const char *cg, uid_t uid, gid_t gid)
 {
+       int cfd;
        size_t len;
-       char *dirnam, *tmpc = find_mounted_controller(controller);
+       char *dirnam, *tmpc = find_mounted_controller(controller, &cfd);
 
        if (!tmpc)
                return -EINVAL;
-       /* basedir / tmpc / cg \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cg) + 3;
+       /* BASEDIR / tmpc / cg \0 */
+       len = strlen(BASEDIR) + strlen(tmpc) + strlen(cg) + 3;
        dirnam = alloca(len);
-       snprintf(dirnam, len, "%s/%s/%s", basedir,tmpc, cg);
+       snprintf(dirnam, len, "%s/%s/%s", BASEDIR,tmpc, cg);
 
        if (mkdir(dirnam, 0755) < 0)
                return -errno;
@@ -571,29 +575,31 @@ static bool recursive_rmdir(const char *dirname)
 
 bool cgfs_remove(const char *controller, const char *cg)
 {
+       int cfd;
        size_t len;
-       char *dirnam, *tmpc = find_mounted_controller(controller);
+       char *dirnam, *tmpc = find_mounted_controller(controller, &cfd);
 
        if (!tmpc)
                return false;
-       /* basedir / tmpc / cg \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cg) + 3;
+       /* BASEDIR / tmpc / cg \0 */
+       len = strlen(BASEDIR) + strlen(tmpc) + strlen(cg) + 3;
        dirnam = alloca(len);
-       snprintf(dirnam, len, "%s/%s/%s", basedir,tmpc, cg);
+       snprintf(dirnam, len, "%s/%s/%s", BASEDIR,tmpc, cg);
        return recursive_rmdir(dirnam);
 }
 
 bool cgfs_chmod_file(const char *controller, const char *file, mode_t mode)
 {
+       int cfd;
        size_t len;
-       char *pathname, *tmpc = find_mounted_controller(controller);
+       char *pathname, *tmpc = find_mounted_controller(controller, &cfd);
 
        if (!tmpc)
                return false;
-       /* basedir / tmpc / file \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(file) + 3;
+       /* BASEDIR / tmpc / file \0 */
+       len = strlen(BASEDIR) + strlen(tmpc) + strlen(file) + 3;
        pathname = alloca(len);
-       snprintf(pathname, len, "%s/%s/%s", basedir, tmpc, file);
+       snprintf(pathname, len, "%s/%s/%s", BASEDIR, tmpc, file);
        if (chmod(pathname, mode) < 0)
                return false;
        return true;
@@ -617,15 +623,16 @@ static int chown_tasks_files(const char *dirname, uid_t uid, gid_t gid)
 
 int cgfs_chown_file(const char *controller, const char *file, uid_t uid, gid_t gid)
 {
+       int cfd;
        size_t len;
-       char *pathname, *tmpc = find_mounted_controller(controller);
+       char *pathname, *tmpc = find_mounted_controller(controller, &cfd);
 
        if (!tmpc)
                return -EINVAL;
-       /* basedir / tmpc / file \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(file) + 3;
+       /* BASEDIR / tmpc / file \0 */
+       len = strlen(BASEDIR) + strlen(tmpc) + strlen(file) + 3;
        pathname = alloca(len);
-       snprintf(pathname, len, "%s/%s/%s", basedir, tmpc, file);
+       snprintf(pathname, len, "%s/%s/%s", BASEDIR, tmpc, file);
        if (chown(pathname, uid, gid) < 0)
                return -errno;
 
@@ -638,15 +645,16 @@ int cgfs_chown_file(const char *controller, const char *file, uid_t uid, gid_t g
 
 FILE *open_pids_file(const char *controller, const char *cgroup)
 {
+       int cfd;
        size_t len;
-       char *pathname, *tmpc = find_mounted_controller(controller);
+       char *pathname, *tmpc = find_mounted_controller(controller, &cfd);
 
        if (!tmpc)
                return NULL;
-       /* basedir / tmpc / cgroup / "cgroup.procs" \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + 4 + strlen("cgroup.procs");
+       /* BASEDIR / tmpc / cgroup / "cgroup.procs" \0 */
+       len = strlen(BASEDIR) + strlen(tmpc) + strlen(cgroup) + 4 + strlen("cgroup.procs");
        pathname = alloca(len);
-       snprintf(pathname, len, "%s/%s/%s/cgroup.procs", basedir, tmpc, cgroup);
+       snprintf(pathname, len, "%s/%s/%s/cgroup.procs", BASEDIR, tmpc, cgroup);
        return fopen(pathname, "w");
 }
 
@@ -654,45 +662,50 @@ static bool cgfs_iterate_cgroup(const char *controller, const char *cgroup, bool
                                 void ***list, size_t typesize,
                                 void* (*iterator)(const char*, const char*, const char*))
 {
+       int cfd, fd, ret;
        size_t len;
-       char *dirname, *tmpc = find_mounted_controller(controller);
+       char *cg, *tmpc;
        char pathname[MAXPATHLEN];
        size_t sz = 0, asz = 0;
-       struct dirent dirent, *direntp;
+       struct dirent *dirent;
        DIR *dir;
-       int ret;
 
+       tmpc = find_mounted_controller(controller, &cfd);
        *list = NULL;
        if (!tmpc)
                return false;
 
-       /* basedir / tmpc / cgroup \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + 3;
-       dirname = alloca(len);
-       snprintf(dirname, len, "%s/%s/%s", basedir, tmpc, cgroup);
+       /* Make sure we pass a relative path to openat(). */
+       len = strlen(cgroup) + 1 /* . */ + 1 /* \0 */;
+       cg = alloca(len);
+       ret = snprintf(cg, len, "%s%s", *cgroup == '/' ? "." : "", cgroup);
+       if (ret < 0 || (size_t)ret >= len) {
+               fprintf(stderr, "%s: pathname too long under %s\n", __func__, cgroup);
+               return false;
+       }
 
-       dir = opendir(dirname);
+       fd = openat(cfd, cg, O_DIRECTORY);
+       if (fd < 0)
+               return false;
+
+       dir = fdopendir(fd);
        if (!dir)
                return false;
 
-       while (!readdir_r(dir, &dirent, &direntp)) {
+       while ((dirent = readdir(dir))) {
                struct stat mystat;
-               int rc;
-
-               if (!direntp)
-                       break;
 
-               if (!strcmp(direntp->d_name, ".") ||
-                   !strcmp(direntp->d_name, ".."))
+               if (!strcmp(dirent->d_name, ".") ||
+                   !strcmp(dirent->d_name, ".."))
                        continue;
 
-               rc = snprintf(pathname, MAXPATHLEN, "%s/%s", dirname, direntp->d_name);
-               if (rc < 0 || rc >= MAXPATHLEN) {
-                       fprintf(stderr, "%s: pathname too long under %s\n", __func__, dirname);
+               ret = snprintf(pathname, MAXPATHLEN, "%s/%s", cg, dirent->d_name);
+               if (ret < 0 || ret >= MAXPATHLEN) {
+                       fprintf(stderr, "%s: pathname too long under %s\n", __func__, cg);
                        continue;
                }
 
-               ret = lstat(pathname, &mystat);
+               ret = fstatat(cfd, pathname, &mystat, AT_SYMLINK_NOFOLLOW);
                if (ret) {
                        fprintf(stderr, "%s: failed to stat %s: %s\n", __func__, pathname, strerror(errno));
                        continue;
@@ -709,12 +722,12 @@ static bool cgfs_iterate_cgroup(const char *controller, const char *cgroup, bool
                        } while  (!tmp);
                        *list = tmp;
                }
-               (*list)[sz] = (*iterator)(controller, cgroup, direntp->d_name);
+               (*list)[sz] = (*iterator)(controller, cg, dirent->d_name);
                (*list)[sz+1] = NULL;
                sz++;
        }
        if (closedir(dir) < 0) {
-               fprintf(stderr, "%s: failed closedir for %s: %s\n", __func__, dirname, strerror(errno));
+               fprintf(stderr, "%s: failed closedir for %s: %s\n", __func__, cgroup, strerror(errno));
                return false;
        }
        return true;
@@ -756,27 +769,34 @@ void free_keys(struct cgfs_files **keys)
 
 bool cgfs_get_value(const char *controller, const char *cgroup, const char *file, char **value)
 {
+       int ret, fd, cfd;
        size_t len;
-       char *fnam, *tmpc = find_mounted_controller(controller);
+       char *fnam, *tmpc = find_mounted_controller(controller, &cfd);
 
        if (!tmpc)
                return false;
-       /* basedir / tmpc / cgroup / file \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + strlen(file) + 4;
+       /* . + /cgroup + / + file + \0 */
+       len = strlen(cgroup) + strlen(file) + 3;
        fnam = alloca(len);
-       snprintf(fnam, len, "%s/%s/%s/%s", basedir, tmpc, cgroup, file);
+       ret = snprintf(fnam, len, "%s%s/%s", *cgroup == '/' ? "." : "", cgroup, file);
+       if (ret < 0 || (size_t)ret >= len)
+               return NULL;
+
+       fd = openat(cfd, fnam, O_RDONLY);
+       if (fd < 0)
+               return NULL;
 
-       *value = slurp_file(fnam);
+       *value = slurp_file(fnam, fd);
        return *value != NULL;
 }
 
 struct cgfs_files *cgfs_get_key(const char *controller, const char *cgroup, const char *file)
 {
+       int ret, cfd;
        size_t len;
-       char *fnam, *tmpc = find_mounted_controller(controller);
+       char *fnam, *tmpc = find_mounted_controller(controller, &cfd);
        struct stat sb;
        struct cgfs_files *newkey;
-       int ret;
 
        if (!tmpc)
                return false;
@@ -787,15 +807,15 @@ struct cgfs_files *cgfs_get_key(const char *controller, const char *cgroup, cons
        if (file && index(file, '/'))
                return NULL;
 
-       /* basedir / tmpc / cgroup / file \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + 3;
+       /* . + /cgroup + / + file + \0 */
+       len = strlen(cgroup) + 3;
        if (file)
                len += strlen(file) + 1;
        fnam = alloca(len);
-       snprintf(fnam, len, "%s/%s/%s%s%s", basedir, tmpc, cgroup,
-               file ? "/" : "", file ? file : "");
+       snprintf(fnam, len, "%s%s%s%s", *cgroup == '/' ? "." : "", cgroup,
+                file ? "/" : "", file ? file : "");
 
-       ret = stat(fnam, &sb);
+       ret = fstatat(cfd, fnam, &sb, 0);
        if (ret < 0)
                return NULL;
 
@@ -831,19 +851,23 @@ bool cgfs_list_keys(const char *controller, const char *cgroup, struct cgfs_file
 }
 
 bool is_child_cgroup(const char *controller, const char *cgroup, const char *f)
-{      size_t len;
-       char *fnam, *tmpc = find_mounted_controller(controller);
+{
+       int cfd;
+       size_t len;
+       char *fnam, *tmpc = find_mounted_controller(controller, &cfd);
        int ret;
        struct stat sb;
 
        if (!tmpc)
                return false;
-       /* basedir / tmpc / cgroup / f \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + strlen(f) + 4;
+       /* . + /cgroup + / + f + \0 */
+       len = strlen(cgroup) + strlen(f) + 3;
        fnam = alloca(len);
-       snprintf(fnam, len, "%s/%s/%s/%s", basedir, tmpc, cgroup, f);
+       ret = snprintf(fnam, len, "%s%s/%s", *cgroup == '/' ? "." : "", cgroup, f);
+       if (ret < 0 || (size_t)ret >= len)
+               return false;
 
-       ret = stat(fnam, &sb);
+       ret = fstatat(cfd, fnam, &sb, 0);
        if (ret < 0 || !S_ISDIR(sb.st_mode))
                return false;
        return true;
@@ -1179,13 +1203,14 @@ static void stripnewline(char *x)
 
 static char *get_pid_cgroup(pid_t pid, const char *contrl)
 {
+       int cfd;
        char fnam[PROCLEN];
        FILE *f;
        char *answer = NULL;
        char *line = NULL;
        size_t len = 0;
        int ret;
-       const char *h = find_mounted_controller(contrl);
+       const char *h = find_mounted_controller(contrl, &cfd);
        if (!h)
                return NULL;
 
@@ -1298,10 +1323,18 @@ static bool caller_is_in_ancestor(pid_t pid, const char *contrl, const char *cg,
        prune_init_slice(c2);
 
        /*
-        * callers pass in '/' for root cgroup, otherwise they pass
-        * in a cgroup without leading '/'
+        * callers pass in '/' or './' (openat()) for root cgroup, otherwise
+        * they pass in a cgroup without leading '/'
+        *
+        * The original line here was:
+        *      linecmp = *cg == '/' ? c2 : c2+1;
+        * TODO: I'm not sure why you'd want to increment when *cg != '/'?
+        *       Serge, do you know?
         */
-       linecmp = *cg == '/' ? c2 : c2+1;
+       if (*cg == '/' || !strncmp(cg, "./", 2))
+               linecmp = c2;
+       else
+               linecmp = c2 + 1;
        if (strncmp(linecmp, cg, strlen(linecmp)) != 0) {
                if (nextcg) {
                        *nextcg = get_next_cgroup_dir(linecmp, cg);
@@ -1324,7 +1357,7 @@ static bool caller_may_see_dir(pid_t pid, const char *contrl, const char *cg)
        char *c2, *task_cg;
        size_t target_len, task_len;
 
-       if (strcmp(cg, "/") == 0)
+       if (strcmp(cg, "/") == 0 || strcmp(cg, "./") == 0)
                return true;
 
        c2 = get_pid_cgroup(pid, contrl);
@@ -1791,8 +1824,12 @@ int cg_access(const char *path, int mode)
        if (!controller)
                return -EIO;
        cgroup = find_cgroup_in_path(path);
-       if (!cgroup)
-               return -EINVAL;
+       if (!cgroup) {
+               // access("/sys/fs/cgroup/systemd", mode) - rx allowed, w not
+               if ((mode & W_OK) == 0)
+                       return 0;
+               return -EACCES;
+       }
 
        get_cgdir_and_path(cgroup, &cgdir, &last);
        if (!last) {
@@ -1805,7 +1842,10 @@ int cg_access(const char *path, int mode)
 
        k = cgfs_get_key(controller, path1, path2);
        if (!k) {
-               ret = -EINVAL;
+               if ((mode & W_OK) == 0)
+                       ret = 0;
+               else
+                       ret = -EACCES;
                goto out;
        }
        free_key(k);
@@ -2858,7 +2898,7 @@ static int read_file(const char *path, char *buf, size_t size,
                return 0;
 
        while (getline(&line, &linelen, f) != -1) {
-               size_t l = snprintf(cache, cache_size, "%s", line);
+               ssize_t l = snprintf(cache, cache_size, "%s", line);
                if (l < 0) {
                        perror("Error writing to cache");
                        rv = 0;
@@ -2875,7 +2915,8 @@ static int read_file(const char *path, char *buf, size_t size,
        }
 
        d->size = total_len;
-       if (total_len > size ) total_len = size;
+       if (total_len > size)
+               total_len = size;
 
        /* read from off 0 */
        memcpy(buf, d->buf, total_len);
@@ -2996,7 +3037,7 @@ static int proc_meminfo_read(char *buf, size_t size, off_t offset,
                goto err;
 
        while (getline(&line, &linelen, f) != -1) {
-               size_t l;
+               ssize_t l;
                char *printme, lbuf[100];
 
                memset(lbuf, 0, 100);
@@ -3151,7 +3192,7 @@ static int proc_cpuinfo_read(char *buf, size_t size, off_t offset,
                goto err;
 
        while (getline(&line, &linelen, f) != -1) {
-               size_t l;
+               ssize_t l;
                if (firstline) {
                        firstline = false;
                        if (strstr(line, "IBM/S390") != NULL) {
@@ -3228,7 +3269,7 @@ static int proc_cpuinfo_read(char *buf, size_t size, off_t offset,
 
        if (is_s390x) {
                char *origcache = d->buf;
-               size_t l;
+               ssize_t l;
                do {
                        d->buf = malloc(d->buflen);
                } while (!d->buf);
@@ -3328,7 +3369,7 @@ static int proc_stat_read(char *buf, size_t size, off_t offset,
        }
 
        while (getline(&line, &linelen, f) != -1) {
-               size_t l;
+               ssize_t l;
                int cpu;
                char cpu_char[10]; /* That's a lot of cores */
                char *c;
@@ -3500,7 +3541,7 @@ static int proc_uptime_read(char *buf, size_t size, off_t offset,
        long int reaperage = getreaperage(fc->pid);
        unsigned long int busytime = get_reaper_busy(fc->pid), idletime;
        char *cache = d->buf;
-       size_t total_len = 0;
+       ssize_t total_len = 0;
 
 #if RELOADTEST
        iwashere();
@@ -3595,7 +3636,7 @@ static int proc_diskstats_read(char *buf, size_t size, off_t offset,
                goto err;
 
        while (getline(&line, &linelen, f) != -1) {
-               size_t l;
+               ssize_t l;
                char lbuf[256];
 
                i = sscanf(line, "%u %u %71s", &major, &minor, dev_name);
@@ -3678,7 +3719,8 @@ static int proc_swaps_read(char *buf, size_t size, off_t offset,
        char *memswlimit_str = NULL, *memlimit_str = NULL, *memusage_str = NULL, *memswusage_str = NULL,
              *memswlimit_default_str = NULL, *memswusage_default_str = NULL;
        unsigned long memswlimit = 0, memlimit = 0, memusage = 0, memswusage = 0, swap_total = 0, swap_free = 0;
-       size_t total_len = 0, rv = 0;
+       ssize_t total_len = 0, rv = 0;
+       ssize_t l = 0;
        char *cache = d->buf;
 
        if (offset) {
@@ -3754,12 +3796,13 @@ static int proc_swaps_read(char *buf, size_t size, off_t offset,
        }
 
        if (swap_total > 0) {
-               total_len += snprintf(d->buf + total_len, d->size - total_len,
-                                "none%*svirtual\t\t%lu\t%lu\t0\n", 36, " ",
-                                swap_total, swap_free);
+               l = snprintf(d->buf + total_len, d->size - total_len,
+                               "none%*svirtual\t\t%lu\t%lu\t0\n", 36, " ",
+                               swap_total, swap_free);
+               total_len += l;
        }
 
-       if (total_len < 0) {
+       if (total_len < 0 || l < 0) {
                perror("Error writing to cache");
                rv = 0;
                goto err;
@@ -3941,6 +3984,14 @@ static void __attribute__((constructor)) collect_subsystems(void)
                        goto out;
                *p2 = '\0';
 
+               /* With cgroupv2 /proc/self/cgroup can contain entries of the
+                * form: 0::/ This will cause lxcfs to fail the cgroup mounts
+                * because it parses out the empty string "" and later on passes
+                * it to mount(). Let's skip such entries.
+                */
+               if (!strcmp(p, ""))
+                       continue;
+
                if (!store_hierarchy(line, p))
                        goto out;
        }