Skip to contentSkip to conversation

shiftfs: uid/gid shifting filesystem

PATCH In review James Bottomley 2 participants 3 messages Jun 15, 2018 – Jul 17, 2018

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

v3 cover shiftfs: uid/gid shifting filesystem

This is a repost of the v2 patch updated for the d_real changes

For those who want to test it out, there's a git tree here

git://git.kernel.org/pub/scm/linux/kernel/git/jejb/binfmt_misc.git

on the shiftfs-v3 branch

v2:

This is a rewrite of the original shiftfs code to make use of super block user namespaces.??I've also removed the mappings passed in as mount options in favour of using the mappings in s_user_ns.??The upshot is that it probably needs retesting for all the bugs people found, since there's a lot of new code, and the use case has changed.??Now, to use it, you have to mark the filesystems you want to be mountable inside a user namespace as root:

mount -t shiftfs -o mark <origin> <mark location>

The origin should be inaccessible to the unprivileged user, and the access to the <mark location> can be controlled by the usual filesystem permissions.??Once this is done, any user who can get access to the <mark location> can do (as the local user namespace root):

mount -t shiftfs <mark location> <somewhere in my local mount ns>

And they will be able to write at their user namespace shifts, but have the interior view of the uid/gid be what appears on the <origin>

In using the s_user_ns, a lot of the code actually simplified, because now our credential shifting code simply becomes use the <origin> s_user_ns and the shifted uid/gid.??The updated d_real() code from overlayfs is also used, so shiftfs now no-longer needs its own file operations.

---
[original blurb]

My use case for this is that I run a lot of unprivileged architectural emulation containers on my system using user namespaces.??Details here:

http://blog.hansenpartnership.com/unprivileged-build-containers/

They're mostly for building non-x86 stuff (like aarch64 and arm secure boot and mips images).??For builds, I have all the environments in my home directory with downshifted uids; however, sometimes I need to use them to administer real images that run on systems, meaning the uids are the usual privileged ones not the downshifted ones.??The only current choice I have is to start the emulation as root so the uid/gids match.??The reason for this filesystem is to use my standard unprivileged containers to maintain these images.??The way I do this is crack the image with a loop and then shift the uids before bringing up the container.??I usually loop mount into /var/tmp/images/, so it's owned by real root there:

jarvis:~ # ls -l /var/tmp/images/mips|head -4
total 0
drwxr-xr-x 1 root root 8192 May 12 08:33 bin
drwxr-xr-x 1 root root????6 May 12 08:33 boot
drwxr-xr-x 1 root root??167 May 12 08:33 dev

And I usually run my build containers with a uid_map of?

?????????0?????100000???????1000
??????1000???????1000??????????1
?????65534?????101000??????????1

(maps 0-999 shifted, then shifts nobody to 1000 and keeps my uid [1000] fixed so I can mount my home directory into the namespace) and something similar with gid_map. So I shift mount the mips image with

mount -t shiftfs -o idmap=0:100000:1000,uidmap=65534:101000:1,gidmap=0:100000:100,gidmap=10 1:100101:899,gidmap=65533:101000:2 /var/tmp/images/mips /home/jejb/containers/mips

and I now see it as

jejb at jarvis:~> ls -l containers/mips|head -4 total 0
drwxr-xr-x 1 100000 100000 8192 May 12 08:33 bin/ drwxr-xr-x 1 100000 100000????6 May 12 08:33 boot/ drwxr-xr-x 1 100000 100000??167 May 12 08:33 dev/

Like my usual unprivileged build roots and I can now use an unprivileged container to enter and administer the image.

It seems like a lot of container systems need to do something similar when they try and provide unprivileged access to standard images.? ?Right at the moment, the security mechanism only allows root in the host to use this, but it's not impossible to come up with a scheme for marking trees that can safely be shift mounted by unprivileged user namespaces.

James

---

James Bottomley (1):
shiftfs: uid/gid shifting bind mount

 fs/Kconfig                 |   8 +
 fs/Makefile                |   1 +
 fs/shiftfs.c               | 783 +++++++++++++++++++++++++++++++++++++++++++++
 include/uapi/linux/magic.h |   2 +
 4 files changed, 794 insertions(+)
 create mode 100644 fs/shiftfs.c
Signature · 6 lines
2.13.7

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#1 lore raw

PATCH v3 1/1 shiftfs: uid/gid shifting bind mount

This allows any subtree to be uid/gid shifted and bound elsewhere. It does this by operating simlarly to overlayfs. Its primary use is for shifting the underlying uids of filesystems used to support unpriviliged (uid shifted) containers. The usual use case here is that the container is operating with an uid shifted unprivileged root but sometimes needs to make use of or work with a filesystem image that has root at real uid 0.

The mechanism is to allow any subordinate mount namespace to mount a shiftfs filesystem (by marking it FS_USERNS_MOUNT) but only allowing it to mount marked subtrees (using the -o mark option as root). Once mounted, the subtree is mapped via the super block user namespace so that the interior ids of the mounting user namespace are the ids written to the filesystem.

---

v3 - update to 4.14 (d_real changes)
v1 - based on original shiftfs with uid mappings now done via s_user_ns v2 - fix revalidation of dentries
add inode aliasing

---
 fs/Kconfig                 |   8 +
 fs/Makefile                |   1 +
 fs/shiftfs.c               | 783 +++++++++++++++++++++++++++++++++++++++++++++
 include/uapi/linux/magic.h |   2 +
 4 files changed, 794 insertions(+)
 create mode 100644 fs/shiftfs.c
fs/Kconfig +8 -0
index 7aee6d699fd6..8b9c2b8566a9 100644
@@ -105,6 +105,14 @@ source "fs/autofs4/Kconfig"
 source "fs/fuse/Kconfig"
 source "fs/overlayfs/Kconfig"
 
+config SHIFT_FS
+	tristate "UID/GID shifting overlay filesystem for containers"
+	help
+	  This filesystem can overlay any mounted filesystem and shift
+	  the uid/gid the files appear at.  The idea is that
+	  unprivileged containers can use this to mount root volumes
+	  using this technique.
+
 menu "Caches"
 
 source "fs/fscache/Kconfig"
fs/Makefile +1 -0
index 7bbaca9c67b1..2aa3ad47a286 100644
@@ -128,3 +128,4 @@ obj-y				+= exofs/ # Multiple modules
 obj-$(CONFIG_CEPH_FS)		+= ceph/
 obj-$(CONFIG_PSTORE)		+= pstore/
 obj-$(CONFIG_EFIVAR_FS)		+= efivarfs/
+obj-$(CONFIG_SHIFT_FS)		+= shiftfs.o
fs/shiftfs.c new file +783 -0

783 lines not on this page · raw message

include/uapi/linux/magic.h +2 -0
index e439565df838..1b3db66384a2 100644
@@ -88,4 +88,6 @@
 #define BALLOON_KVM_MAGIC	0x13661366
 #define ZSMALLOC_MAGIC		0x58295829
 
+#define SHIFTFS_MAGIC		0x6a656a62
+
 #endif /* __LINUX_MAGIC_H__ */
Signature · 6 lines
2.13.7

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#2 lore raw
In reply to James Bottomley

+Cc David

Quoted 18 lines · James Bottomley

This is a repost of the v2 patch updated for the d_real changes

For those who want to test it out, there's a git tree here

git://git.kernel.org/pub/scm/linux/kernel/git/jejb/binfmt_misc.git

on the shiftfs-v3 branch

v2:

This is a rewrite of the original shiftfs code to make use of super block user namespaces.??I've also removed the mappings passed in as mount options in favour of using the mappings in s_user_ns.??The upshot is that it probably needs retesting for all the bugs people found, since there's a lot of new code, and the use case has changed.??Now, to use it, you have to mark the filesystems you want to be mountable inside a user namespace as root:

Quoted 8 lines

mount -t shiftfs -o mark <origin> <mark location>

The origin should be inaccessible to the unprivileged user, and the access to the <mark location> can be controlled by the usual filesystem permissions.??Once this is done, any user who can get access to the <mark location> can do (as the local user namespace root):

mount -t shiftfs <mark location> <somewhere in my local mount ns>

David, I wanted to pull you in here based on something you said on the most recent filesystem context thread (thought it would make more sense here rather than piggypacking on that already massive thread).

Quoted 19 lines

I want to be able to add support for a bunch of things:

(1) UID, GID and Project ID mapping/translation. I want to be able to install a translation table of some sort on the superblock to translate source identifiers (which may be foreign numeric UIDs/GIDs, text names, GUIDs) into system identifiers. This needs to be done before the superblock is published[*].

Note that this may, for example, involve using the context and the superblock held therein to issue an RPC to a server to look up translations.

[*] By "published" I mean made available through mount so that other
userspace processes can access it by path.

Maybe specifying a translation range element with something like:

write(fd, "t uid <srcuid> <nsuid> <count>");

Quoted 8 lines

The translation information also needs to propagate over an automount in some circumstances.

(2) Namespace configuration. I want to be able to tell the superblock creation process what namespaces should be applied when it created (in particular the userns and netns) for containerisation purposes, e.g.:

write(fd, "n user=<fd> net=<fd>");

There's some obvious overlap between shiftfs and (1), but also important differences. Primarily that shiftfs tries to make something that looks like a bind mount rather than applying the mappings to new superblocks for arbitrary filesystems.

I've already been playing with shiftfs on top of the filesystem context patches, because I thought it would allow getting rid of the intermediate "mark" mount described above. I have a hacky proof of concept implementation that I've pushed to the shiftfs-fscontext branch of

git://git.kernel.org/pub/scm/linux/kernel/git/sforshee/linux.git

Basically the idea is that the more privileged "host" context can create the fs fd and set the source on it to "bless" a subtree for id shifted mounting, and the less privileged "client" context can use the fd to do the mount (test program below). But I had to mess with fc->user_ns to ensure s_user_ns gets set correctly, and it would likely be nicer to do something more like (2) above.

The idea is that we need the more privileged "host" context to bless the subtree being id shifted before actually executing the mount in the less privileged "client" context. I'm doing this by having the host set the source, then have the client use the fd to create the superblock (my test program is below). This leads to some undesirable changing of the fs contexts user ns in shiftfs (so that s_user_ns is the client's namespace), which could likely be eliminated by doing something like what's described in (2) and having the super block created on the host side.

However, maybe these things are similar enough to settle on a common solution, such as supporting id mapping at the vfsmount level.

Seth

---

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/wait.h>
#include <limits.h>

#define __NR_move_mount 336
#define __NR_fsopen 337
#define __NR_fsmount 338

#define FSOPEN_CLOEXEC 0x00000001
#define FSMOUNT_CLOEXEC 0x00000001
#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004

static int move_mount(int from_dfd, const char *from_pathname,
int to_dfd, const char *to_pathname, unsigned int flags)

{
	return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname,
		       flags);
}

static int fsopen(const char *fs_name, unsigned int flags)

{
	return syscall(__NR_fsopen, fs_name, flags);
}

static int fsmount(int fsfd, unsigned int flags, unsigned int ms_flags)

{
	return syscall(__NR_fsmount, fsfd, flags, ms_flags);
}

static void write_idmap(char *path, char *map)

{
	int fd;
	size_t map_len;

	map_len = strlen(map);
	fd = open(path, O_RDWR);
	if (fd == -1) {
		perror("open");
		exit(1);
	}

	if (write(fd, map, map_len) != map_len) {
		perror("write");
		exit(1);
	}
}

#define CHILD_STACK_SIZE (1024 * 1024)
static char child_stack[CHILD_STACK_SIZE];

struct child_args {
	int inp[2];
	int outp[2];
	int fsfd;
	char *dest;
	uid_t uid;
	uid_t gid;
};

static int child_func(void *arg)

{
	struct child_args *args = arg;
	char ch;
	int fsfd = args->fsfd, mfd;
	int ret;

	close(args->inp[1]);
	close(args->outp[0]);

	/* Change to uid/gid for root in the user ns */
	if (setgid(args->gid) == -1) {
		perror("setgid");
		exit(-1);
	}
	if (setuid(args->uid) == -1) {
		perror("setgid");
		exit(-1);
	}

	if (unshare(CLONE_NEWNS | CLONE_NEWUSER) == -1) {
		perror("unshare");
		exit(1);
	}

	/* Close write pipe to signal unshare has completed */
	close(args->outp[1]);

	/* Wait for uid/gid maps to be written */
	if (read(args->inp[0], &ch, 1) != 0) {
		perror("read");
		exit(1);
	}
	close(args->inp[0]);

	/* Now we are root in user ns; proceed with mount */
	ret = write(fsfd, "x create", 9);
	if (ret == -1) {
		perror("write \"x create\"");
		exit(1);
	}

	mfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0);
	if (mfd < 0) {
		perror("fsmount");
		exit(1);
	}

	ret = move_mount(mfd, "", AT_FDCWD, args->dest, MOVE_MOUNT_F_EMPTY_PATH);
	if (ret < 0) {
		perror("move_mount");
		exit(1);
	}

	close(fsfd);
	close(mfd);

	execl("/bin/sh", "/bin/sh", NULL);
	perror("execl");
	exit(1);
}

int main(int argc, char *argv[])

{
	char *src, *dest;
	uid_t root_uid;
	gid_t root_gid;
	int fsfd;
	char buf[PATH_MAX + 2];
	int len, ret;
	struct child_args args;
	pid_t child_pid;
	int *inp, *outp;
	char map_buf[100];
	char map_path[PATH_MAX];
	char ch;

	if (argc != 5) {
		printf("Usage: %s <src> <dest> <root_uid> <root_gid>\n", argv[0]);
		exit(1);
	}

	src = argv[1];
	dest = argv[2];
	root_uid = atoi(argv[3]);
	root_gid = atoi(argv[4]);

	fsfd = fsopen("shiftfs", FSOPEN_CLOEXEC);
	if (fsfd < 0) {
		perror("fsopen");
		exit(1);
	}

	/*
	 * Set source subtree; we do it on this side of clone(2) so that
	 * the kernel can check for permissions wrt src. The rest of the
	 * mount will happen in the child process after unsharing the
	 * user/mount namespaces.
	 */
	len = snprintf(buf, sizeof(buf), "s %s", src);
	if (len >= sizeof(buf)) {
		fprintf(stderr, "src too large\n");
		exit(1);
	}
	ret = write(fsfd, buf, len);
	if (ret == -1) {
		perror("write \"s src\"");
		exit(1);
	}

	if (pipe(args.inp) == -1) {
		perror("pipe");
		exit(1);
	}
	if (pipe(args.outp) == -1) {
		perror("pipe");
		exit(1);
	}
	args.fsfd = fsfd;
	args.dest = dest;
	args.uid = root_uid;
	args.gid = root_gid;
	child_pid = clone(child_func, child_stack + CHILD_STACK_SIZE, SIGCHLD, &args);

	/* Pipe directions reversed wrt child */
	inp = args.outp;
	outp = args.inp;
	close(inp[1]);
	close(outp[0]);

	/* Wait for child to set ids and unshare */
	if (read(inp[0], &ch, 1) != 0) {
		perror("read");
		exit(1);
	}

	snprintf(map_buf, sizeof(map_buf), "0 %ld 1", (long)root_uid);
	snprintf(map_path, sizeof(map_path), "/proc/%ld/uid_map",
		 (long)child_pid);
	write_idmap(map_path, map_buf);

	snprintf(map_buf, sizeof(map_buf), "0 %ld 1", (long)root_gid);
	snprintf(map_path, sizeof(map_path), "/proc/%ld/gid_map",
		 (long)child_pid);
	write_idmap(map_path, map_buf);

	/* Signal child that id maps have been updated */
	close(outp[1]);

	if (waitpid(child_pid, NULL, 0) == -1) {
		perror("waitpid");
		exit(1);
	}

	exit(0);
}

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html