Fix checking of parent directories

Taken from the justification in the launchpad bug:

To a task in freezer cgroup /a/b/c/d, it should appear that there are no
cgroups other than its descendents. Since this is a filesystem, we must have
the parent directories, but each parent cgroup should only contain the child
which the task can see.

So, when this task looks at /a/b, it should see only directory 'c' and no
files. Attempt to create /a/b/x should result in -EPERM, whether /a/b/x already
exists or not. Attempts to query /a/b/x should result in -ENOENT whether /a/b/x
exists or not. Opening /a/b/tasks should result in -ENOENT.

The caller_may_see_dir checks specifically whether a task may see a cgroup
directory - i.e. /a/b/x if opening /a/b/x/tasks, and /a/b/c/d if doing
opendir('/a/b/c/d').

caller_is_in_ancestor() will return true if the caller in /a/b/c/d looks at
/a/b/c/d/e. If the caller is in a child cgroup of the queried one - i.e. if the
task in /a/b/c/d queries /a/b, then *nextcg will container the next (the only)
directory which he can see in the path - 'c'.

Beyond this, regular DAC permissions should apply, with the
root-in-user-namespace privilege over its mapped uids being respected. The
fc_may_access check does this check for both directories and files.

This is CVE-2015-1342 (LP: #1508481)

Signed-off-by: Serge Hallyn <serge.hallyn@ubuntu.com>
 static int cg_rmdir(const char *path)
 {
 	struct fuse_context *fc = fuse_get_context();
	char *fpath = NULL, *cgdir = NULL, *controller, *next = NULL;
 	const char *cgroup;
 	int ret;
 
	if (!fc)
		return -EIO;

	controller = pick_controller_from_path(fc, path);
	if (!controller)
		return -EINVAL;

	cgroup = find_cgroup_in_path(path);
	if (!cgroup)
		return -EINVAL;

	get_cgdir_and_path(cgroup, &cgdir, &fpath);
	if (!fpath) {
		ret = -EINVAL;
 		goto out;
 	}
 
	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;
	}
	if (!caller_is_in_ancestor(fc->pid, controller, cgroup, NULL)) {
		ret = -EACCES;
		goto out;
	}

	if (!cgfs_remove(controller, cgroup)) {
		ret = -EINVAL;
		goto out;
	}

	ret = 0;
 
 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;
 	const char *cgroup;
 	int ret;
 
	if (!fc)
		return -EIO;

	controller = pick_controller_from_path(fc, path);
	if (!controller)
		return -EINVAL;

	cgroup = find_cgroup_in_path(path);
	if (!cgroup)
		return -EINVAL;

	get_cgdir_and_path(cgroup, &cgdir, &fpath);
	if (!fpath) {
		ret = -EINVAL;
 		goto out;
 	}
 
	fprintf(stderr, "rmdir: verifying access to %s:%s (req path %s)\n",
			controller, cgdir, path);
 	if (!fc_may_access(fc, controller, cgdir, NULL, O_WRONLY)) {
 		ret = -EACCES;
 		goto out;
	}
	if (!caller_is_in_ancestor(fc->pid, controller, cgroup, NULL)) {
		ret = -EACCES;
		goto out;
	}

	if (!cgfs_remove(controller, cgroup)) {
		ret = -EINVAL;
		goto out;
	}

	ret = 0;
 
 out:
 	free(cgdir);
 	return ret;
 }
