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; }
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; }