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:
Juan RP 2020-04-14 19:48:50 +02:00
parent ae87662547
commit b9fb5e1cc8
2 changed files with 79 additions and 50 deletions

View File

@ -1,5 +1,5 @@
/*- /*-
* Copyright (c) 2014-2015 Juan Romero Pardines. * Copyright (c) 2014-2020 Juan Romero Pardines.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -30,6 +30,7 @@
* - This uses IPC/PID/UTS namespaces, nothing more. * - This uses IPC/PID/UTS namespaces, nothing more.
* - Disables namespace features if running inside containers. * - Disables namespace features if running inside containers.
* - Supports overlayfs on a temporary directory or a tmpfs mount. * - Supports overlayfs on a temporary directory or a tmpfs mount.
* - Supports read-only bind mounts.
*/ */
#define _GNU_SOURCE #define _GNU_SOURCE
#define _XOPEN_SOURCE 700 #define _XOPEN_SOURCE 700
@ -77,6 +78,7 @@ struct bindmnt {
SIMPLEQ_ENTRY(bindmnt) entries; SIMPLEQ_ENTRY(bindmnt) entries;
char *src; char *src;
const char *dest; const char *dest;
bool ro; /* readonly */
}; };
static char *tmpdir; static char *tmpdir;
@ -87,10 +89,11 @@ static SIMPLEQ_HEAD(bindmnt_head, bindmnt) bindmnt_queue =
static void __attribute__((noreturn)) static void __attribute__((noreturn))
usage(const char *p) usage(const char *p)
{ {
printf("Usage: %s [-b src:dest] [-O -t -o <opts>] [--] <dir> <cmd> [<cmdargs>]\n\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> (may be specified multiple times)\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" "-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); "-o opts Options to be passed to the tmpfs mount (for use with -t)\n", p);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -135,7 +138,7 @@ walk_dir(const char *path,
struct stat sb; struct stat sb;
const char *p; const char *p;
char tmp_path[PATH_MAX] = {0}; char tmp_path[PATH_MAX] = {0};
int rv, i; int rv = 0, i;
i = scandir(path, &list, NULL, alphasort); i = scandir(path, &list, NULL, alphasort);
if (i == -1) { if (i == -1) {
@ -206,7 +209,7 @@ sighandler_cleanup(int signum)
} }
static void static void
add_bindmount(char *bm) add_bindmount(const char *bm, bool ro)
{ {
struct bindmnt *bmnt; struct bindmnt *bmnt;
char *b, *src, *dest; char *b, *src, *dest;
@ -229,34 +232,39 @@ add_bindmount(char *bm)
bmnt->src = src; bmnt->src = src;
bmnt->dest = dest; bmnt->dest = dest;
bmnt->ro = ro;
SIMPLEQ_INSERT_TAIL(&bindmnt_queue, bmnt, entries); SIMPLEQ_INSERT_TAIL(&bindmnt_queue, bmnt, entries);
} }
static int static void
fsuid_chdir(uid_t uid, const char *path) 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); snprintf(mountdir, sizeof(mountdir), "%s%s", chrootdir, dest ? dest : dir);
rv = chdir(path);
saveerrno = errno;
(void)setfsuid(0);
errno = saveerrno;
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 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]; 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); die("Couldn't chdir to %s", dir);
if (mount(".", mountdir, NULL, MS_BIND|MS_REC|MS_PRIVATE, NULL) == -1) if (mount(".", mountdir, NULL, flags, NULL) == -1)
die("Failed to bind mount %s at %s", dir, mountdir); die("Failed to remount read-only %s at %s", dir, mountdir);
} }
static char * static char *
@ -315,8 +323,8 @@ main(int argc, char **argv)
struct sigaction sa; struct sigaction sa;
uid_t ruid, euid, suid; uid_t ruid, euid, suid;
gid_t rgid, egid, sgid; gid_t rgid, egid, sgid;
const char *chrootdir, *tmpfs_opts, *cmd, *argv0; const char *rootdir, *tmpfs_opts, *cmd, *argv0;
char **cmdargs, *b, mountdir[PATH_MAX-1]; char **cmdargs, *b, *chrootdir, mountdir[PATH_MAX-1];
int c, clone_flags, container_flags, child_status = 0; int c, clone_flags, container_flags, child_status = 0;
pid_t child; pid_t child;
bool overlayfs = false; bool overlayfs = false;
@ -324,10 +332,10 @@ main(int argc, char **argv)
{ NULL, 0, NULL, 0 } { NULL, 0, NULL, 0 }
}; };
tmpfs_opts = chrootdir = cmd = NULL; tmpfs_opts = rootdir = cmd = NULL;
argv0 = argv[0]; 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) { switch (c) {
case 'O': case 'O':
overlayfs = true; overlayfs = true;
@ -338,10 +346,15 @@ main(int argc, char **argv)
case 'o': case 'o':
tmpfs_opts = optarg; tmpfs_opts = optarg;
break; break;
case 'B':
if (optarg == NULL || *optarg == '\0')
break;
add_bindmount(optarg, true);
break;
case 'b': case 'b':
if (optarg == NULL || *optarg == '\0') if (optarg == NULL || *optarg == '\0')
break; break;
add_bindmount(optarg); add_bindmount(optarg, false);
break; break;
case 'V': case 'V':
printf("%s\n", XBPS_RELVER); printf("%s\n", XBPS_RELVER);
@ -357,22 +370,20 @@ main(int argc, char **argv)
if (argc < 2) if (argc < 2)
usage(argv0); usage(argv0);
chrootdir = argv[0]; rootdir = argv[0];
cmd = argv[1]; cmd = argv[1];
cmdargs = argv + 1; cmdargs = argv + 1;
/* Make chrootdir absolute */
chrootdir = realpath(rootdir, NULL);
if (!chrootdir)
die("realpath rootdir");
/* Never allow chrootdir == / */ /* Never allow chrootdir == / */
if (strcmp(chrootdir, "/") == 0) if (strcmp(chrootdir, "/") == 0)
die("/ is not allowed to be used as chrootdir"); 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) if (getresgid(&rgid, &egid, &sgid) == -1)
die("getresgid"); die("getresgid");
@ -413,9 +424,10 @@ main(int argc, char **argv)
*/ */
if (prctl(PR_SET_NO_NEW_PRIVS, 1) == -1 && errno != EINVAL) { if (prctl(PR_SET_NO_NEW_PRIVS, 1) == -1 && errno != EINVAL) {
die("prctl PR_SET_NO_NEW_PRIVS"); die("prctl PR_SET_NO_NEW_PRIVS");
} else if (prctl (PR_SET_SECUREBITS, }
if (prctl(PR_SET_SECUREBITS,
SECBIT_NOROOT|SECBIT_NOROOT_LOCKED) == -1) { 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 */ /* mount as private, systemd mounts it as shared by default */
@ -429,23 +441,26 @@ main(int argc, char **argv)
/* mount /proc */ /* mount /proc */
snprintf(mountdir, sizeof(mountdir), "%s/proc", chrootdir); 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 */ /* 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 */ /* bind mount all specified mnts */
bindmount(ruid, chrootdir, "/sys", NULL);
/* bind mount /dev */
bindmount(ruid, chrootdir, "/dev", NULL);
/* bind mount all user specified mnts */
SIMPLEQ_FOREACH(bmnt, &bindmnt_queue, entries) 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 */ /* move chrootdir to / and chroot to it */
if (fsuid_chdir(ruid, chrootdir) == -1) if (chdir(chrootdir) == -1)
die("Failed to chdir to %s", chrootdir); die("Failed to chdir to %s", chrootdir);
if (mount(".", ".", NULL, MS_BIND|MS_PRIVATE, NULL) == -1) if (mount(".", ".", NULL, MS_BIND|MS_PRIVATE, NULL) == -1)

View File

@ -1,4 +1,4 @@
.Dd June 12, 2019 .Dd April 14, 2020
.Dt XBPS-UCHROOT 1 .Dt XBPS-UCHROOT 1
.Sh NAME .Sh NAME
.Nm xbps-uchroot .Nm xbps-uchroot
@ -12,11 +12,13 @@
.Sh DESCRIPTION .Sh DESCRIPTION
The The
.Nm .Nm
utility allows users to chroot and bind mount required pseudo-filesystems utility allows users to chroot and automatically bind mount required
(/dev, /proc and /sys) in the target pseudo-filesystems (/dev, /dev/shm, /proc and /sys) as well as
user specified directories in the target
.Ar CHROOTDIR .Ar CHROOTDIR
to execute to execute
.Ar COMMAND . .Ar COMMAND .
.Pp
The The
.Nm .Nm
utility uses by default Linux namespaces to isolate IPC, PIDs and mounts to 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. are simply disabled.
.Sh OPTIONS .Sh OPTIONS
.Bl -tag -width -x .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 .It Fl b Ar src:dest
Bind mounts Bind mounts
.Ar src .Ar src