[PATCH 3/3] cifs.upcall: add support for different PID namespaces
Enzo Matsumiya <[email protected]>
| Newsgroups | org.kernel.vger.linux-cifs |
|---|---|
| Message-ID | <[email protected]> |
Currently, applications are not able to access multiuser mounts from different PID namespace (e.g. docker/podman containers). That's because arg->pid is set in the kernel, who uses host PID NS (init_pid_ns) value, so cifs.upcall fails when opening/parsing /proc/<arg->pid>/* entries for proper setup (because that path doesn't exist on the target PID NS, or points to a different task). Add get_ns_pid() helper to parse the 'NSpid' field from /proc/<arg->pid>/status (before changing NS) in order to get the corresponding NS PID. * Note: nested/shared namespaces are unsupported for now Changes: - rename in_same_user_ns() to in_same_ns(); also, it now takes an @nsname string to check arbitrary namespace names - add 'struct pid_status_field' and parse_proc_pid_status() as a common helper for get_ns_pid() and get_uidgid() Signed-off-by: Enzo Matsumiya <[email protected]> --- cifs.upcall.c | 296 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 223 insertions(+), 73 deletions(-) diff --git a/cifs.upcall.c b/cifs.upcall.c index 3f56c69cae28..a233553ad824 100644 --- a/cifs.upcall.c +++ b/cifs.upcall.c @@ -302,26 +302,25 @@ static struct namespace_file { #define NS_PATH_MAXLEN (6 + 10 + 4 + 6 + 1) /** - * in_same_user_ns - return true if two processes are in the same user - * namespace. + * in_same_ns - Return true if two processes are in the same namespace. + * @nsname: namespace name * @pid_a: the pid of the first process * @pid_b: the pid of the second process * - * Works by comparing the inode numbers for /proc/<pid>/user. + * Works by comparing the inode numbers for /proc/<pid>/ns/@nsname. */ -static int -in_same_user_ns(pid_t pid_a, pid_t pid_b) +static int in_same_ns(const char *nsname, pid_t pid_a, pid_t pid_b) { char path[NS_PATH_MAXLEN]; ino_t a_ino, b_ino; struct stat st; - snprintf(path, sizeof(path), NS_PATH_FMT, pid_a, "user"); + snprintf(path, sizeof(path), NS_PATH_FMT, pid_a, nsname); if (stat(path, &st) != 0) return 0; a_ino = st.st_ino; - snprintf(path, sizeof(path), NS_PATH_FMT, pid_b, "user"); + snprintf(path, sizeof(path), NS_PATH_FMT, pid_b, nsname); if (stat(path, &st) != 0) return 0; b_ino = st.st_ino; @@ -353,7 +352,7 @@ switch_to_process_ns(pid_t pid) #ifdef CLONE_NEWUSER if (namespace_files[n].nstype == CLONE_NEWUSER - && in_same_user_ns(getpid(), pid)) { + && in_same_ns("user", getpid(), pid)) { /* Switching to the same user namespace is forbidden, because switching to a user namespace grants all capabilities in that namespace regardless of uid. */ @@ -1380,6 +1379,72 @@ static int ip_to_fqdn(const char *addrstr, char *host, size_t hostlen) /* max valid UID/GID is (UINT_MAX - 1) */ #define INVALID_UIDGID UINT_MAX +struct pid_status_field { + const char *name; + char *value; +}; + +/* Search for @fields[].name in /proc/pid/status, save matching lines in @fields[].value. */ +static int parse_proc_pid_status(pid_t pid, struct pid_status_field *fields, int n) +{ + char path[PROC_PID_PATH_MAXLEN] = {}, buf[256]; + FILE *fp = NULL; + int i, ret; + + errno = 0; + if (pid < 0 || !fields || n < 1) { + errno = EINVAL; + return -1; + } + + for (i = 0; i < n; i++) { + if (!fields[i].name) { + errno = EINVAL; + return -1; + } + } + + ret = snprintf(path, PROC_PID_PATH_MAXLEN, "/proc/%d/status", pid); + if (ret < 0 || ret >= PROC_PID_PATH_MAXLEN) { + if (!errno) + errno = ENAMETOOLONG; + return -1; + } + + fp = fopen(path, "r"); + if (!fp) + return -1; + + errno = 0; + ret = -1; + while (fgets(buf, 256, fp)) { + for (i = 0; i < n; i++) { + size_t len = strlen(fields[i].name); + + if (fields[i].value || strncmp(buf, fields[i].name, len)) + continue; + + fields[i].value = strdup(buf); + if (!fields[i].value) + goto out; + } + } +out: + fclose(fp); + + if (errno) + return -1; + + for (i = 0; i < n; i++) { + if (!fields[i].value) { + errno = ENODATA; + return -1; + } + } + + return 0; +} + /* * get_uidgid - Get @pid's (real) UID and/or GID. * @pid: process to get UID/GID from @@ -1397,94 +1462,161 @@ static int ip_to_fqdn(const char *addrstr, char *host, size_t hostlen) */ static int get_uidgid(pid_t pid, uid_t *uidp, gid_t *gidp) { - char path[PROC_PID_PATH_MAXLEN] = {}, buf[256]; - FILE *fp = NULL; + struct pid_status_field buf[2] = {}; + unsigned long long val; int ret; errno = 0; - if (pid < 0 || (!uidp && !gidp)) { + if (pid < 0 || !uidp || !gidp) { errno = EINVAL; return -1; } - if (uidp) - *uidp = INVALID_UIDGID; + *uidp = INVALID_UIDGID; + *gidp = INVALID_UIDGID; + buf[0].name = "Uid:"; + buf[1].name = "Gid:"; - if (gidp) - *gidp = INVALID_UIDGID; + ret = parse_proc_pid_status(pid, buf, 2); + if (ret) + goto out; - ret = snprintf(path, PROC_PID_PATH_MAXLEN, "/proc/%d/status", pid); - if (ret < 0 || ret >= PROC_PID_PATH_MAXLEN) { + /* + * Example line format (same for both Uid/Gid): + * "Uid:\t%u\t%u\t%u\t%u" + * + * Where the numbers represents: + * <real> <effective> <saved> <fsuid> + * + * We're only interested in the <real> value. + * + * (field names "Uid:"/"Gid:" parsed above, skip it) + */ + ret = sscanf(buf[0].value + 4, "%llu", &val); + if (ret != 1) { + ret = -1; if (!errno) - errno = ENAMETOOLONG; - return -1; + errno = ENODATA; + goto out; } - fp = fopen(path, "r"); - if (!fp) { + if (val >= UINT_MAX) { + ret = -1; + errno = EINVAL; + goto out; + } + + *uidp = (uid_t)val; + + ret = sscanf(buf[1].value + 4, "%llu", &val); + if (ret != 1) { + ret = -1; + if (!errno) + errno = EINVAL; + goto out; + } + + if (val >= UINT_MAX) { ret = -1; + errno = EINVAL; goto out; } - /* Parse /proc/pid/status fields */ + *gidp = (gid_t)val; + ret = 0; +out: + free(buf[0].value); + free(buf[1].value); + + if (ret) { + syslog(LOG_DEBUG, "%s(pid=%d): %s", __func__, pid, strerror(errno)); + *uidp = INVALID_UIDGID; + *gidp = INVALID_UIDGID; + } + + return ret; +} + +/* cf. kernel's include/threads.h */ +#define PID_MAX (4 * 1024 * 1024) +#define INVALID_PID ((pid_t)-1) + +/* + * get_ns_pid - Map host @pid to target-NS PID. + * @pid: host (kernel) PID + * @nspidp: pointer to store mapped NS PID + * + * Note: because /proc/pid/status information is based on the namespace the reader is coming from, + * this function must be called _before_ setns() in order to get the PID from the correct PID NS. + * + * Return: 0 on success, -1 otherwise (errno set). + * + * On errors, *@nspidp is set to INVALID_PID. + */ +static int get_ns_pid(pid_t pid, pid_t *nspidp) +{ + struct pid_status_field buf = {}; + pid_t host_pid, ns_pid; + int ret; + errno = 0; - ret = -1; - while (fgets(buf, 256, fp)) { - unsigned long long val; + if (pid < 0 || !nspidp) { + errno = EINVAL; + return -1; + } - errno = ENODATA; - if ((!uidp || strncmp(buf, "Uid:", 4)) && (!gidp || strncmp(buf, "Gid:", 4))) - continue; + if (in_same_ns("pid", getpid(), pid)) { + *nspidp = pid; + return 0; + } - errno = 0; + *nspidp = INVALID_PID; + buf.name = "NSpid:"; - /* - * Example line format (same for both Uid/Gid): - * "Uid:\t%u\t%u\%u\%u" - * - * Where the numbers represents: - * <real> <effective> <saved> <fsuid> - * - * We're only interested in the <real> value. - * - * (field names "Uid:"/"Gid:" parsed above, skip it) - */ - ret = sscanf(&buf[0] + 4, "%llu", &val); - if (ret != 1) { - ret = -1; - if (errno) - break; - continue; - } + ret = parse_proc_pid_status(pid, &buf, 1); + if (ret) + goto out; + /* + * NSpid field format: + * "NSpid:\t%d\t%d" + * + * Where the numbers represents: + * (for NS process seen from host NS): + * <host PID> <NS pid> + * + * (for NS process seen from same NS): + * <NS/host pid> + * + * We're only interested in the second value. + * But we still scan the first one so we can check host_pid == @pid. + * + * Note: sscanf() returning 1 is a bug because it means this is being called + * _after_ setns(). Beware. + */ + ret = sscanf(buf.value + 6, "%d %d", &host_pid, &ns_pid); + free(buf.value); + if (ret != 2) { ret = -1; - if (val >= UINT_MAX) { + if (!errno) errno = EINVAL; - break; - } - - if (uidp && !strncmp(buf, "Uid:", 4)) - *uidp = (uid_t)val; - else - *gidp = (gid_t)val; + goto out; + } - if ((!uidp || *uidp != INVALID_UIDGID) && (!gidp || *gidp != INVALID_UIDGID)) { - errno = 0; - ret = 0; - break; - } + if (host_pid < 0 || host_pid >= PID_MAX || host_pid != pid || ns_pid < 0 || + ns_pid >= PID_MAX) { + errno = EINVAL; + ret = -1; + goto out; } -out: - if (fp) - fclose(fp); + *nspidp = ns_pid; + errno = 0; + ret = 0; +out: if (ret) { syslog(LOG_DEBUG, "%s(pid=%d): %s", __func__, pid, strerror(errno)); - if (uidp) - *uidp = INVALID_UIDGID; - - if (gidp) - *gidp = INVALID_UIDGID; + *nspidp = INVALID_PID; } return ret; @@ -1687,10 +1819,28 @@ int main(const int argc, char *const argv[]) * filesystem and have the correct network configuration. */ if (arg->upcall_target == UPTARGET_APP || arg->upcall_target == UPTARGET_UNSPECIFIED) { - syslog(LOG_INFO, "upcall_target=app, switching namespaces to application thread"); + pid_t pid = arg->pid; + arg->upcall_target = UPTARGET_APP; - same_ns = in_same_user_ns(getpid(), arg->pid); - rc = switch_to_process_ns(arg->pid); + same_ns = in_same_ns("user", getpid(), pid); + if (!same_ns) + syslog(LOG_INFO, "upcall_target=app, switching namespaces to application thread"); +#ifdef CLONE_NEWPID + /* + * arg->pid holds host PID (gotten from kernel). + * If we're going to switch PID namespaces, we need to map the PID from the correct + * target PID NS before switching, so UID/GID are also fetched from the correct NS. + */ + rc = get_ns_pid(arg->pid, &pid); + if (rc) { + syslog(LOG_ERR, "get_ns_pid: %s", strerror(errno)); + rc = 1; + goto out; + } + + arg->pid = pid; +#endif /* CLONE_NEWPID */ + rc = switch_to_process_ns(pid); if (rc == -1) { syslog(LOG_ERR, "unable to switch to process namespace: %s", strerror(errno)); rc = 1; @@ -1722,8 +1872,8 @@ int main(const int argc, char *const argv[]) /* * For any other case, we need to get UID/GID from procfs. * - * FIXME: this only works if we haven't switched PID namespaces. - * If we did, /proc/arg->pid/ might not exist, or worse, point to something else. + * If we switched PID namespaces, arg->pid already points to the correct + * NS PID value. */ rc = get_uidgid(arg->pid, &uid, &gid); if (rc) { -- 2.54.0