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.
|
* 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)
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user