Exemple #1
0
static int __nonuserns_sysctl_op(struct sysctl_req *req, size_t nr_req, int op)
{
	int dir, ret, exit_code = -1;;

	dir = open("/proc/sys", O_RDONLY, O_DIRECTORY);
	if (dir < 0) {
		pr_perror("Can't open sysctl dir");
		return -1;
	}

	while (nr_req--) {
		int fd, flags;

		if (op == CTL_READ)
			flags = O_RDONLY;
		else
			flags = O_WRONLY;

		fd = openat(dir, req->name, flags);
		if (fd < 0) {
			if (errno == ENOENT && (req->flags & CTL_FLAGS_OPTIONAL)) {
				req++;
				continue;
			}
			pr_perror("Can't open sysctl %s", req->name);
			goto out;
		}

		ret = do_sysctl_op(fd, req, op);
		if (ret)
			goto out;
		close(fd);
		req++;
	}

	exit_code = 0;
out:
	close(dir);
	return exit_code;
}
Exemple #2
0
static int __userns_sysctl_op(void *arg, int proc_fd, pid_t pid)
{
	int fd, ret = -1, dir, i, status, *fds = NULL;
	struct sysctl_userns_req *userns_req = arg;
	int op = userns_req->op;
	struct sysctl_req *req, **reqs = NULL;
	sigset_t blockmask, oldmask;
	pid_t worker;

	// fix up the pointer
	req = userns_req->reqs = (struct sysctl_req *) &userns_req[1];

	/* For files in the IPC/UTS namespaces, restoring is more complicated
	 * than for net. Unprivileged users cannot even open these files, so
	 * they must be opened by usernsd. However, the value in the kernel is
	 * changed for the IPC/UTS namespace that write()s to the open sysctl
	 * file (not who opened it). So, we must set the value from inside the
	 * usernsd caller's namespace. We:
	 *
	 * 1. unsd opens the sysctl files
	 * 2. forks a task
	 * 3. setns()es to the UTS/IPC namespace of the caller
	 * 4. write()s to the files and exits
	 */
	dir = open("/proc/sys", O_RDONLY, O_DIRECTORY);
	if (dir < 0) {
		pr_perror("Can't open sysctl dir");
		return -1;
	}

	fds = xmalloc(sizeof(int) * userns_req->nr_req);
	if (!fds)
		goto out;

	reqs = xmalloc(sizeof(struct sysctl_req) * userns_req->nr_req);
	if (!reqs)
		goto out;

	memset(fds, -1, sizeof(int) * userns_req->nr_req);

	for (i = 0; i < userns_req->nr_req; i++)  {
		int arg_len = sysctl_userns_arg_size(req->type);
		int name_len = strlen((char *) &req[1]) + 1;
		int total_len = sizeof(*req) + arg_len + name_len;
		int flags;

		/* fix up the pointers */
		req->name = (char *) &req[1];
		req->arg = req->name + name_len;

		if (((char *) req) + total_len >= ((char *) userns_req) + MAX_UNSFD_MSG_SIZE) {
			pr_err("bad sysctl req %s, too big: %d\n", req->name, total_len);
			goto out;
		}

		if (op == CTL_READ)
			flags = O_RDONLY;
		else
			flags = O_WRONLY;

		fd = openat(dir, req->name, flags);
		if (fd < 0) {
			if (errno == ENOENT && (req->flags & CTL_FLAGS_OPTIONAL))
				continue;
			pr_perror("Can't open sysctl %s", req->name);
			goto out;
		}

		/* save a pointer to the req, so we don't need to recompute its
		 * location
		 */
		reqs[i] = req;
		fds[i] = fd;

		req = (struct sysctl_req *) (((char *) req) + total_len);
	}

	/*
	 * Don't let the sigchld_handler() mess with us
	 * calling waitpid() on the exited worker. The
	 * same is done in cr_system().
	 */

	sigemptyset(&blockmask);
	sigaddset(&blockmask, SIGCHLD);
	sigprocmask(SIG_BLOCK, &blockmask, &oldmask);

	worker = fork();
	if (worker < 0)
		goto out;

	if (!worker) {
		int nsfd;
		const char *nsname = ns_to_string(userns_req->ns);

		BUG_ON(!nsname);
		nsfd = openat(proc_fd, nsname, O_RDONLY);
		if (nsfd < 0) {
			pr_perror("failed to open pid %d's ns %s", pid, nsname);
			exit(1);
		}

		if (setns(nsfd, 0) < 0) {
			pr_perror("failed to setns to %d's ns %s", pid, nsname);
			exit(1);
		}

		close(nsfd);

		for (i = 0; i < userns_req->nr_req; i++) {
			if (do_sysctl_op(fds[i], reqs[i], op) < 0)
				exit(1);
		}

		exit(0);
	}

	if (waitpid(worker, &status, 0) != worker) {
		pr_perror("worker didn't die?");
		kill(worker, SIGKILL);
		goto out;
	}
	sigprocmask(SIG_SETMASK, &oldmask, NULL);

	if (!WIFEXITED(status) || WEXITSTATUS(status)) {
		pr_err("worker failed: %d\n", status);
		goto out;
	}

	ret = 0;

out:
	if (fds) {
		for (i = 0; i < userns_req->nr_req; i++) {
			if (fds[i] < 0)
				break;
			close_safe(&fds[i]);
		}

		xfree(fds);
	}

	if (reqs)
		xfree(reqs);

	close_safe(&dir);

	return ret;
}