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:
parent
ae87662547
commit
b9fb5e1cc8
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user