Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Merge branch 'nsfs-discovery'

Michael Kerrisk <<mtk.manpages@gmail.com> writes:

I would like to write code that discovers the namespace setup on a live
system. The NS_GET_PARENT and NS_GET_USERNS ioctl() operations added in
Linux 4.9 provide much of what I want, but there are still a couple of
small pieces missing. Those pieces are added with this patch series.

Here's an example program that makes use of the new ioctl() operations.

8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---
/* ns_capable.c

(C) 2016 Michael Kerrisk, <mtk.manpages@gmail.com>

Licensed under the GNU General Public License v2 or later.

Test whether a process (identified by PID) might (subject to LSM checks)
have capabilities in a namespace (identified by a /proc/PID/ns/xxx file).
*/

} while (0)

exit(EXIT_FAILURE); } while (0)

/* Display capabilities sets of process with specified PID */

static void
show_cap(pid_t pid)
{
cap_t caps;
char *cap_string;

caps = cap_get_pid(pid);
if (caps == NULL)
errExit("cap_get_proc");

cap_string = cap_to_text(caps, NULL);
if (cap_string == NULL)
errExit("cap_to_text");

printf("Capabilities: %s\n", cap_string);
}

/* Obtain the effective UID pf the process 'pid' by
scanning its /proc/PID/file */

static uid_t
get_euid_of_process(pid_t pid)
{
char path[PATH_MAX];
char line[1024];
int uid;

snprintf(path, sizeof(path), "/proc/%ld/status", (long) pid);

FILE *fp;
fp = fopen(path, "r");
if (fp == NULL)
errExit("fopen-/proc/PID/status");

for (;;) {
if (fgets(line, sizeof(line), fp) == NULL) {

/* Should never happen... */

fprintf(stderr, "Failure scanning %s\n", path);
exit(EXIT_FAILURE);
}

if (strstr(line, "Uid:") == line) {
sscanf(line, "Uid: %*d %d %*d %*d", &uid);
return uid;
}
}
}

int
main(int argc, char *argv[])
{
int ns_fd, userns_fd, pid_userns_fd;
int nstype;
int next_fd;
struct stat pid_stat;
struct stat target_stat;
char *pid_str;
pid_t pid;
char path[PATH_MAX];

if (argc < 2) {
fprintf(stderr, "Usage: %s PID [ns-file]\n", argv[0]);
fprintf(stderr, "\t'ns-file' is a /proc/PID/ns/xxxx file; "
"if omitted, use the namespace\n"
"\treferred to by standard input "
"(file descriptor 0)\n");
exit(EXIT_FAILURE);
}

pid_str = argv[1];
pid = atoi(pid_str);

if (argc <= 2) {
ns_fd = STDIN_FILENO;
} else {
ns_fd = open(argv[2], O_RDONLY);
if (ns_fd == -1)
errExit("open-ns-file");
}

/* Get the relevant user namespace FD, which is 'ns_fd' if 'ns_fd' refers
to a user namespace, otherwise the user namespace that owns 'ns_fd' */

nstype = ioctl(ns_fd, NS_GET_NSTYPE);
if (nstype == -1)
errExit("ioctl-NS_GET_NSTYPE");

if (nstype == CLONE_NEWUSER) {
userns_fd = ns_fd;
} else {
userns_fd = ioctl(ns_fd, NS_GET_USERNS);
if (userns_fd == -1)
errExit("ioctl-NS_GET_USERNS");
}

/* Obtain 'stat' info for the user namespace of the specified PID */

snprintf(path, sizeof(path), "/proc/%s/ns/user", pid_str);

pid_userns_fd = open(path, O_RDONLY);
if (pid_userns_fd == -1)
errExit("open-PID");

if (fstat(pid_userns_fd, &pid_stat) == -1)
errExit("fstat-PID");

/* Get 'stat' info for the target user namesapce */

if (fstat(userns_fd, &target_stat) == -1)
errExit("fstat-PID");

/* If the PID is in the target user namespace, then it has
whatever capabilities are in its sets. */

if (pid_stat.st_dev == target_stat.st_dev &&
pid_stat.st_ino == target_stat.st_ino) {
printf("PID is in target namespace\n");
printf("Subject to LSM checks, it has the following capabilities\n");

show_cap(pid);

exit(EXIT_SUCCESS);
}

/* Otherwise, we need to walk through the ancestors of the target
user namespace to see if PID is in an ancestor namespace */

for (;;) {
int f;

next_fd = ioctl(userns_fd, NS_GET_PARENT);

if (next_fd == -1) {

/* The error here should be EPERM... */

if (errno != EPERM)
errExit("ioctl-NS_GET_PARENT");

printf("PID is not in an ancestor namespace\n");
printf("It has no capabilities in the target namespace\n");

exit(EXIT_SUCCESS);
}

if (fstat(next_fd, &target_stat) == -1)
errExit("fstat-PID");

/* If the 'stat' info for this user namespace matches the 'stat'
* info for 'next_fd', then the PID is in an ancestor namespace */

if (pid_stat.st_dev == target_stat.st_dev &&
pid_stat.st_ino == target_stat.st_ino)
break;

/* Next time round, get the next parent */

f = userns_fd;
userns_fd = next_fd;
close(f);
}

/* At this point, we found that PID is in an ancestor of the target
user namespace, and 'userns_fd' refers to the immediate descendant
user namespace of PID in the chain of user namespaces from PID to
the target user namespace. If the effective UID of PID matches the
owner UID of descendant user namespace, then PID has all
capabilities in the descendant namespace(s); otherwise, it just has
the capabilities that are in its sets. */

uid_t owner_uid, uid;
if (ioctl(userns_fd, NS_GET_OWNER_UID, &owner_uid) == -1) {
perror("ioctl-NS_GET_OWNER_UID");
exit(EXIT_FAILURE);
}

uid = get_euid_of_process(pid);

printf("PID is in an ancestor namespace\n");
if (owner_uid == uid) {
printf("And its effective UID matches the owner "
"of the namespace\n");
printf("Subject to LSM checks, PID has all capabilities in "
"that namespace!\n");
} else {
printf("But its effective UID does not match the owner "
"of the namespace\n");
printf("Subject to LSM checks, it has the following capabilities\n");
show_cap(pid);
}

exit(EXIT_SUCCESS);
}
8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---

Michael Kerrisk (2):
nsfs: Add an ioctl() to return the namespace type
nsfs: Add an ioctl() to return owner UID of a userns

fs/nsfs.c | 13 +++++++++++++
include/uapi/linux/nsfs.h | 9 +++++++--
2 files changed, 20 insertions(+), 2 deletions(-)

+20 -2
+13
fs/nsfs.c
··· 7 7 #include <linux/seq_file.h> 8 8 #include <linux/user_namespace.h> 9 9 #include <linux/nsfs.h> 10 + #include <linux/uaccess.h> 10 11 11 12 static struct vfsmount *nsfs_mnt; 12 13 ··· 164 163 static long ns_ioctl(struct file *filp, unsigned int ioctl, 165 164 unsigned long arg) 166 165 { 166 + struct user_namespace *user_ns; 167 167 struct ns_common *ns = get_proc_ns(file_inode(filp)); 168 + uid_t __user *argp; 169 + uid_t uid; 168 170 169 171 switch (ioctl) { 170 172 case NS_GET_USERNS: ··· 176 172 if (!ns->ops->get_parent) 177 173 return -EINVAL; 178 174 return open_related_ns(ns, ns->ops->get_parent); 175 + case NS_GET_NSTYPE: 176 + return ns->ops->type; 177 + case NS_GET_OWNER_UID: 178 + if (ns->ops->type != CLONE_NEWUSER) 179 + return -EINVAL; 180 + user_ns = container_of(ns, struct user_namespace, ns); 181 + argp = (uid_t __user *) arg; 182 + uid = from_kuid_munged(current_user_ns(), user_ns->owner); 183 + return put_user(uid, argp); 179 184 default: 180 185 return -ENOTTY; 181 186 }
+7 -2
include/uapi/linux/nsfs.h
··· 6 6 #define NSIO 0xb7 7 7 8 8 /* Returns a file descriptor that refers to an owning user namespace */ 9 - #define NS_GET_USERNS _IO(NSIO, 0x1) 9 + #define NS_GET_USERNS _IO(NSIO, 0x1) 10 10 /* Returns a file descriptor that refers to a parent namespace */ 11 - #define NS_GET_PARENT _IO(NSIO, 0x2) 11 + #define NS_GET_PARENT _IO(NSIO, 0x2) 12 + /* Returns the type of namespace (CLONE_NEW* value) referred to by 13 + file descriptor */ 14 + #define NS_GET_NSTYPE _IO(NSIO, 0x3) 15 + /* Get owner UID (in the caller's user namespace) for a user namespace */ 16 + #define NS_GET_OWNER_UID _IO(NSIO, 0x4) 12 17 13 18 #endif /* __LINUX_NSFS_H */