xbps-uchroot: support read-only bind mounts, misc improvements.
- Added -B src:dest (like -b) but this makes bind mounts in read-only mode. - Get rid of setfsuid(), it's unnecessary. - Make sure chrootdir is not '/', use realpath(). - Always set SECBIT_NOROOT, see capabilities(7). - Do not mount recursively, right now this only mounts /dev (ro), /dev/shm (rw), /sys (ro) and /proc (ro). Previously any mount below any specific mount were recursively mounted in chrootdir.
This commit is contained in:
		@@ -1,5 +1,5 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * Copyright (c) 2014-2015 Juan Romero Pardines.
 | 
			
		||||
 * Copyright (c) 2014-2020 Juan Romero Pardines.
 | 
			
		||||
 * All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Redistribution and use in source and binary forms, with or without
 | 
			
		||||
@@ -30,6 +30,7 @@
 | 
			
		||||
 * 	- This uses IPC/PID/UTS namespaces, nothing more.
 | 
			
		||||
 * 	- Disables namespace features if running inside containers.
 | 
			
		||||
 * 	- Supports overlayfs on a temporary directory or a tmpfs mount.
 | 
			
		||||
 * 	- Supports read-only bind mounts.
 | 
			
		||||
 */
 | 
			
		||||
#define _GNU_SOURCE
 | 
			
		||||
#define _XOPEN_SOURCE 700
 | 
			
		||||
@@ -77,6 +78,7 @@ struct bindmnt {
 | 
			
		||||
	SIMPLEQ_ENTRY(bindmnt) entries;
 | 
			
		||||
	char *src;
 | 
			
		||||
	const char *dest;
 | 
			
		||||
	bool ro;	/* readonly */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static char *tmpdir;
 | 
			
		||||
@@ -87,10 +89,11 @@ static SIMPLEQ_HEAD(bindmnt_head, bindmnt) bindmnt_queue =
 | 
			
		||||
static void __attribute__((noreturn))
 | 
			
		||||
usage(const char *p)
 | 
			
		||||
{
 | 
			
		||||
	printf("Usage: %s [-b src:dest] [-O -t -o <opts>] [--] <dir> <cmd> [<cmdargs>]\n\n"
 | 
			
		||||
	    "-b src:dest Bind mounts <src> into <dir>/<dest> (may be specified multiple times)\n"
 | 
			
		||||
	printf("Usage: %s [-[B|b] src:dest] [-O -t -o <opts>] [--] <dir> <cmd> [<cmdargs>]\n\n"
 | 
			
		||||
	    "-B src:dest Bind mounts <src> into <dir>/<dest> (read-only)\n"
 | 
			
		||||
	    "-b src:dest Bind mounts <src> into <dir>/<dest> (read-write)\n"
 | 
			
		||||
	    "-O          Creates a tempdir and mounts <dir> read-only via overlayfs\n"
 | 
			
		||||
	    "-t          Creates tempdir and mounts it on tmpfs (for use with -O)\n"
 | 
			
		||||
	    "-t          Creates a tempdir and mounts <dir> on tmpfs (for use with -O)\n"
 | 
			
		||||
	    "-o opts     Options to be passed to the tmpfs mount (for use with -t)\n", p);
 | 
			
		||||
	exit(EXIT_FAILURE);
 | 
			
		||||
}
 | 
			
		||||
@@ -135,7 +138,7 @@ walk_dir(const char *path,
 | 
			
		||||
	struct stat sb;
 | 
			
		||||
	const char *p;
 | 
			
		||||
	char tmp_path[PATH_MAX] = {0};
 | 
			
		||||
	int rv, i;
 | 
			
		||||
	int rv = 0, i;
 | 
			
		||||
 | 
			
		||||
	i = scandir(path, &list, NULL, alphasort);
 | 
			
		||||
	if (i == -1) {
 | 
			
		||||
@@ -206,7 +209,7 @@ sighandler_cleanup(int signum)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
add_bindmount(char *bm)
 | 
			
		||||
add_bindmount(const char *bm, bool ro)
 | 
			
		||||
{
 | 
			
		||||
	struct bindmnt *bmnt;
 | 
			
		||||
	char *b, *src, *dest;
 | 
			
		||||
@@ -229,34 +232,39 @@ add_bindmount(char *bm)
 | 
			
		||||
 | 
			
		||||
	bmnt->src = src;
 | 
			
		||||
	bmnt->dest = dest;
 | 
			
		||||
	bmnt->ro = ro;
 | 
			
		||||
	SIMPLEQ_INSERT_TAIL(&bindmnt_queue, bmnt, entries);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
fsuid_chdir(uid_t uid, const char *path)
 | 
			
		||||
static void
 | 
			
		||||
bindmount(const char *chrootdir, const char *dir, const char *dest)
 | 
			
		||||
{
 | 
			
		||||
	int saveerrno, rv;
 | 
			
		||||
	char mountdir[PATH_MAX-1];
 | 
			
		||||
	int flags = MS_BIND|MS_PRIVATE;
 | 
			
		||||
 | 
			
		||||
	(void)setfsuid(uid);
 | 
			
		||||
	rv = chdir(path);
 | 
			
		||||
	saveerrno = errno;
 | 
			
		||||
	(void)setfsuid(0);
 | 
			
		||||
	errno = saveerrno;
 | 
			
		||||
	snprintf(mountdir, sizeof(mountdir), "%s%s", chrootdir, dest ? dest : dir);
 | 
			
		||||
 | 
			
		||||
	return rv;
 | 
			
		||||
	if (chdir(dir) == -1)
 | 
			
		||||
		die("Couldn't chdir to %s", dir);
 | 
			
		||||
	if (mount(".", mountdir, NULL, flags, NULL) == -1)
 | 
			
		||||
		die("Failed to bind mount %s at %s", dir, mountdir);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
bindmount(uid_t ruid, const char *chrootdir, const char *dir, const char *dest)
 | 
			
		||||
remount_rdonly(const char *chrootdir, const char *dir, const char *dest, bool ro)
 | 
			
		||||
{
 | 
			
		||||
	char mountdir[PATH_MAX-1];
 | 
			
		||||
	int flags = MS_REMOUNT|MS_BIND|MS_RDONLY;
 | 
			
		||||
 | 
			
		||||
	snprintf(mountdir, sizeof(mountdir), "%s/%s", chrootdir, dest ? dest : dir);
 | 
			
		||||
	if (!ro)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	if (fsuid_chdir(ruid, dir) == -1)
 | 
			
		||||
	snprintf(mountdir, sizeof(mountdir), "%s%s", chrootdir, dest ? dest : dir);
 | 
			
		||||
 | 
			
		||||
	if (chdir(dir) == -1)
 | 
			
		||||
		die("Couldn't chdir to %s", dir);
 | 
			
		||||
	if (mount(".", mountdir, NULL, MS_BIND|MS_REC|MS_PRIVATE, NULL) == -1)
 | 
			
		||||
		die("Failed to bind mount %s at %s", dir, mountdir);
 | 
			
		||||
	if (mount(".", mountdir, NULL, flags, NULL) == -1)
 | 
			
		||||
		die("Failed to remount read-only %s at %s", dir, mountdir);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *
 | 
			
		||||
@@ -315,8 +323,8 @@ main(int argc, char **argv)
 | 
			
		||||
	struct sigaction sa;
 | 
			
		||||
	uid_t ruid, euid, suid;
 | 
			
		||||
	gid_t rgid, egid, sgid;
 | 
			
		||||
	const char *chrootdir, *tmpfs_opts, *cmd, *argv0;
 | 
			
		||||
	char **cmdargs, *b, mountdir[PATH_MAX-1];
 | 
			
		||||
	const char *rootdir, *tmpfs_opts, *cmd, *argv0;
 | 
			
		||||
	char **cmdargs, *b, *chrootdir, mountdir[PATH_MAX-1];
 | 
			
		||||
	int c, clone_flags, container_flags, child_status = 0;
 | 
			
		||||
	pid_t child;
 | 
			
		||||
	bool overlayfs = false;
 | 
			
		||||
@@ -324,10 +332,10 @@ main(int argc, char **argv)
 | 
			
		||||
		{ NULL, 0, NULL, 0 }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	tmpfs_opts = chrootdir = cmd = NULL;
 | 
			
		||||
	tmpfs_opts = rootdir = cmd = NULL;
 | 
			
		||||
	argv0 = argv[0];
 | 
			
		||||
 | 
			
		||||
	while ((c = getopt_long(argc, argv, "Oto:b:V", longopts, NULL)) != -1) {
 | 
			
		||||
	while ((c = getopt_long(argc, argv, "Oto:B:b:V", longopts, NULL)) != -1) {
 | 
			
		||||
		switch (c) {
 | 
			
		||||
		case 'O':
 | 
			
		||||
			overlayfs = true;
 | 
			
		||||
@@ -338,10 +346,15 @@ main(int argc, char **argv)
 | 
			
		||||
		case 'o':
 | 
			
		||||
			tmpfs_opts = optarg;
 | 
			
		||||
			break;
 | 
			
		||||
		case 'B':
 | 
			
		||||
			if (optarg == NULL || *optarg == '\0')
 | 
			
		||||
				break;
 | 
			
		||||
			add_bindmount(optarg, true);
 | 
			
		||||
			break;
 | 
			
		||||
		case 'b':
 | 
			
		||||
			if (optarg == NULL || *optarg == '\0')
 | 
			
		||||
				break;
 | 
			
		||||
			add_bindmount(optarg);
 | 
			
		||||
			add_bindmount(optarg, false);
 | 
			
		||||
			break;
 | 
			
		||||
		case 'V':
 | 
			
		||||
			printf("%s\n", XBPS_RELVER);
 | 
			
		||||
@@ -357,22 +370,20 @@ main(int argc, char **argv)
 | 
			
		||||
	if (argc < 2)
 | 
			
		||||
		usage(argv0);
 | 
			
		||||
 | 
			
		||||
	chrootdir = argv[0];
 | 
			
		||||
	rootdir = argv[0];
 | 
			
		||||
	cmd = argv[1];
 | 
			
		||||
	cmdargs = argv + 1;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	/* Make chrootdir absolute */
 | 
			
		||||
	chrootdir = realpath(rootdir, NULL);
 | 
			
		||||
	if (!chrootdir)
 | 
			
		||||
		die("realpath rootdir");
 | 
			
		||||
 | 
			
		||||
	/* Never allow chrootdir == / */
 | 
			
		||||
	if (strcmp(chrootdir, "/") == 0)
 | 
			
		||||
		die("/ is not allowed to be used as chrootdir");
 | 
			
		||||
 | 
			
		||||
	/* Make chrootdir absolute */
 | 
			
		||||
	if (chrootdir[0] != '/') {
 | 
			
		||||
		char cwd[PATH_MAX-1];
 | 
			
		||||
		if (getcwd(cwd, sizeof(cwd)) == NULL)
 | 
			
		||||
			die("getcwd");
 | 
			
		||||
		chrootdir = xbps_xasprintf("%s/%s", cwd, chrootdir);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (getresgid(&rgid, &egid, &sgid) == -1)
 | 
			
		||||
		die("getresgid");
 | 
			
		||||
 | 
			
		||||
@@ -413,9 +424,10 @@ main(int argc, char **argv)
 | 
			
		||||
		 */
 | 
			
		||||
		if (prctl(PR_SET_NO_NEW_PRIVS, 1) == -1 && errno != EINVAL) {
 | 
			
		||||
			die("prctl PR_SET_NO_NEW_PRIVS");
 | 
			
		||||
		} else if (prctl (PR_SET_SECUREBITS,
 | 
			
		||||
		}
 | 
			
		||||
		if (prctl(PR_SET_SECUREBITS,
 | 
			
		||||
			SECBIT_NOROOT|SECBIT_NOROOT_LOCKED) == -1) {
 | 
			
		||||
			die("prctl SECBIT_NOROOT");
 | 
			
		||||
			die("prctl PR_SET_SECUREBITS");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* mount as private, systemd mounts it as shared by default */
 | 
			
		||||
@@ -429,23 +441,26 @@ main(int argc, char **argv)
 | 
			
		||||
 | 
			
		||||
		/* mount /proc */
 | 
			
		||||
		snprintf(mountdir, sizeof(mountdir), "%s/proc", chrootdir);
 | 
			
		||||
		if (mount("proc", mountdir, "proc", MS_MGC_VAL|MS_PRIVATE, NULL) == -1) {
 | 
			
		||||
		if (mount("proc", mountdir, "proc",
 | 
			
		||||
			  MS_MGC_VAL|MS_PRIVATE|MS_RDONLY, NULL) == -1) {
 | 
			
		||||
			/* try bind mount */
 | 
			
		||||
			bindmount(ruid, chrootdir, "/proc", NULL);
 | 
			
		||||
			add_bindmount("/proc:/proc", true);
 | 
			
		||||
		}
 | 
			
		||||
		/* bind mount /sys, /dev (ro) and /dev/shm (rw) */
 | 
			
		||||
		add_bindmount("/sys:/sys", true);
 | 
			
		||||
		add_bindmount("/dev:/dev", true);
 | 
			
		||||
		add_bindmount("/dev/shm:/dev/shm", false);
 | 
			
		||||
 | 
			
		||||
		/* bind mount /sys */
 | 
			
		||||
		bindmount(ruid, chrootdir, "/sys", NULL);
 | 
			
		||||
 | 
			
		||||
		/* bind mount /dev */
 | 
			
		||||
		bindmount(ruid, chrootdir, "/dev", NULL);
 | 
			
		||||
 | 
			
		||||
		/* bind mount all user specified mnts */
 | 
			
		||||
		/* bind mount all specified mnts */
 | 
			
		||||
		SIMPLEQ_FOREACH(bmnt, &bindmnt_queue, entries)
 | 
			
		||||
			bindmount(ruid, chrootdir, bmnt->src, bmnt->dest);
 | 
			
		||||
			bindmount(chrootdir, bmnt->src, bmnt->dest);
 | 
			
		||||
 | 
			
		||||
		/* remount bind mounts as read-only if set */
 | 
			
		||||
		SIMPLEQ_FOREACH(bmnt, &bindmnt_queue, entries)
 | 
			
		||||
			remount_rdonly(chrootdir, bmnt->src, bmnt->dest, bmnt->ro);
 | 
			
		||||
 | 
			
		||||
		/* move chrootdir to / and chroot to it */
 | 
			
		||||
		if (fsuid_chdir(ruid, chrootdir) == -1)
 | 
			
		||||
		if (chdir(chrootdir) == -1)
 | 
			
		||||
			die("Failed to chdir to %s", chrootdir);
 | 
			
		||||
 | 
			
		||||
		if (mount(".", ".", NULL, MS_BIND|MS_PRIVATE, NULL) == -1)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
.Dd June 12, 2019
 | 
			
		||||
.Dd April 14, 2020
 | 
			
		||||
.Dt XBPS-UCHROOT 1
 | 
			
		||||
.Sh NAME
 | 
			
		||||
.Nm xbps-uchroot
 | 
			
		||||
@@ -12,11 +12,13 @@
 | 
			
		||||
.Sh DESCRIPTION
 | 
			
		||||
The
 | 
			
		||||
.Nm
 | 
			
		||||
utility allows users to chroot and bind mount required pseudo-filesystems
 | 
			
		||||
(/dev, /proc and /sys) in the target
 | 
			
		||||
utility allows users to chroot and automatically bind mount required
 | 
			
		||||
pseudo-filesystems (/dev, /dev/shm, /proc and /sys) as well as
 | 
			
		||||
user specified directories in the target
 | 
			
		||||
.Ar CHROOTDIR
 | 
			
		||||
to execute
 | 
			
		||||
.Ar COMMAND .
 | 
			
		||||
.Pp
 | 
			
		||||
The
 | 
			
		||||
.Nm
 | 
			
		||||
utility uses by default Linux namespaces to isolate IPC, PIDs and mounts to
 | 
			
		||||
@@ -24,6 +26,18 @@ the calling process. If running in a OpenVZ container, these namespace features
 | 
			
		||||
are simply disabled.
 | 
			
		||||
.Sh OPTIONS
 | 
			
		||||
.Bl -tag -width -x
 | 
			
		||||
.It Fl B Ar src:dest
 | 
			
		||||
Bind mounts
 | 
			
		||||
.Ar src
 | 
			
		||||
into
 | 
			
		||||
.Ar CHROOTDIR/dest
 | 
			
		||||
in read-only mode.
 | 
			
		||||
This option may be specified multiple times.
 | 
			
		||||
Please note that both
 | 
			
		||||
.Ar src
 | 
			
		||||
and
 | 
			
		||||
.Ar dest
 | 
			
		||||
must be absolute paths and must exist.
 | 
			
		||||
.It Fl b Ar src:dest
 | 
			
		||||
Bind mounts
 | 
			
		||||
.Ar src
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user