Skip to content
138 changes: 112 additions & 26 deletions jail/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,31 @@ unsigned long detect_atime_flag(const char *mountpoint)
#define MOUNT_ATTR_NODIRATIME 0x00000080
#endif

/* MS_* -> MOUNT_ATTR_* for fsmount()/mount_setattr() */
static unsigned mountflags_to_attr(unsigned long mountflags)
{
unsigned attr = 0;

if (mountflags & MS_RDONLY)
attr |= MOUNT_ATTR_RDONLY;
if (mountflags & MS_NOSUID)
attr |= MOUNT_ATTR_NOSUID;
if (mountflags & MS_NODEV)
attr |= MOUNT_ATTR_NODEV;
if (mountflags & MS_NOEXEC)
attr |= MOUNT_ATTR_NOEXEC;
if (mountflags & MS_NODIRATIME)
attr |= MOUNT_ATTR_NODIRATIME;
if (mountflags & MS_NOATIME)
attr |= MOUNT_ATTR_NOATIME;
else if (mountflags & MS_STRICTATIME)
attr |= MOUNT_ATTR_STRICTATIME;
else
attr |= MOUNT_ATTR_RELATIME;

return attr;
}

int sys_openat2(int dfd, const char *path, struct open_how *how, size_t size)
{
return syscall(SYS_openat2, dfd, path, how, size);
Expand Down Expand Up @@ -312,6 +337,21 @@ int sys_move_mount(int from_dfd, const char *from_path, int to_dfd, const char *
return syscall(SYS_move_mount, from_dfd, from_path, to_dfd, to_path, flags);
}

int sys_fsopen(const char *fsname, unsigned flags)
{
return syscall(SYS_fsopen, fsname, flags);
}

int sys_fsconfig(int fd, unsigned cmd, const char *key, const void *value, int aux)
{
return syscall(SYS_fsconfig, fd, cmd, key, value, aux);
}

int sys_fsmount(int fd, unsigned flags, unsigned attr_flags)
{
return syscall(SYS_fsmount, fd, flags, attr_flags);
}

int sys_mount_setattr(int dfd, const char *path, unsigned flags, struct ujail_mount_attr *attr, size_t size)
{
return syscall(SYS_mount_setattr, dfd, path, flags, attr, size);
Expand Down Expand Up @@ -339,7 +379,7 @@ int mask_path_now(const char *path)
} else {
if (mount(JAIL_NOAFILE, path, "bind", MS_BIND, NULL))
return -1;
if (mount(JAIL_NOAFILE, path, "bind", MS_REMOUNT | MS_BIND | MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_RELATIME, NULL))
if (remount_readonly(path, MS_NOSUID | MS_NOEXEC | MS_NODEV))
return -1;
}

Expand Down Expand Up @@ -424,6 +464,27 @@ static unsigned long mountinfo_current_flags(const char *path)
return flags;
}

/*
* Self-bind @path and remount it read-only, preserving the flags already in
* effect. Mounts copied in by unshare(CLONE_NEWNS) under a userns that does
* not own them are MNT_LOCK_{NOSUID,NODEV,NOEXEC,ATIME}; a remount clearing
* any of those fails with EPERM.
*/
int remount_readonly(const char *path, unsigned long flags)
{
flags |= MS_REMOUNT | MS_BIND | MS_RDONLY | mountinfo_current_flags(path);

return mount(NULL, path, NULL, flags, NULL);
}

int bind_remount_readonly(const char *path, unsigned long flags)
{
if (mount(path, path, "bind", MS_BIND | (flags & MS_REC), NULL))
return -1;

return remount_readonly(path, flags);
}

static bool fs_userns;

void jail_fs_set_userns(bool enabled)
Expand Down Expand Up @@ -471,19 +532,25 @@ static int do_mount(const char *root, const char *orig_source, const char *targe
snprintf(new, sizeof(new), "%s%s", root, target?target:source);

if (is_mask) {
int err;

if (stat(new, &s))
return 0; /* doesn't exists, nothing to mask */

if (S_ISDIR(s.st_mode)) {/* use empty 0-sized tmpfs for directories */
if (mount("none", new, "tmpfs", MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_RELATIME, "size=0,mode=000"))
return error;
err = mount("none", new, "tmpfs", MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_RELATIME, "size=0,mode=000");
} else {
/* mount-bind 0-sized file having mode 000 */
if (mount(UJAIL_NOAFILE, new, "bind", MS_BIND, NULL))
return error;
err = mount(UJAIL_NOAFILE, new, "bind", MS_BIND, NULL);
if (!err)
err = remount_readonly(new, MS_NOSUID | MS_NOEXEC | MS_NODEV);
}

if (mount(UJAIL_NOAFILE, new, "bind", MS_REMOUNT | MS_BIND | MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_RELATIME, NULL))
return error;
if (err) {
if (error)
ERROR("failed to mask %s%s: %m\n", new,
S_ISDIR(s.st_mode) ? "" : " with " UJAIL_NOAFILE);
return error;
}

DEBUG("masked path %s\n", new);
Expand Down Expand Up @@ -744,6 +811,42 @@ int add_mount_fd(int fd, const char *target, int error)
return 0;
}

/*
* Mounting sysfs requires CAP_SYS_ADMIN in the userns owning the netns. A
* child with a userns from clone() that stays in the parent's netns cannot
* do it. Called in the parent before clone(): fsmount() every queued sysfs
* entry with its requested flags and leave the fd for do_mount_fd().
*/
int premount_sysfs(void)
{
struct mount *m;

list_for_each_entry(m, &mounts_order, list) {
int fsfd, mfd;

if (!m->filesystemtype || strcmp(m->filesystemtype, "sysfs") ||
m->source_fd >= 0)
continue;

fsfd = sys_fsopen("sysfs", FSOPEN_CLOEXEC);
if (fsfd < 0)
return -1;
if (sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) {
close(fsfd);
return -1;
}
mfd = sys_fsmount(fsfd, FSMOUNT_CLOEXEC, mountflags_to_attr(m->mountflags));
close(fsfd);
if (mfd < 0)
return -1;

m->source_fd = mfd;
DEBUG("pre-mounted sysfs for %s as fd:%d\n", m->target, mfd);
}

return 0;
}

int add_mount_volume(const char *source, const char *target, int error)
{
struct mount *m;
Expand Down Expand Up @@ -1305,26 +1408,9 @@ static int idmap_tree_fd(const char *source, int source_fd, int userns_fd, unsig
return -1;
}

attr.attr_set = MOUNT_ATTR_IDMAP;
if (mountflags & MS_RDONLY)
attr.attr_set |= MOUNT_ATTR_RDONLY;
if (mountflags & MS_NOSUID)
attr.attr_set |= MOUNT_ATTR_NOSUID;
if (mountflags & MS_NODEV)
attr.attr_set |= MOUNT_ATTR_NODEV;
if (mountflags & MS_NOEXEC)
attr.attr_set |= MOUNT_ATTR_NOEXEC;
if (mountflags & MS_NODIRATIME)
attr.attr_set |= MOUNT_ATTR_NODIRATIME;
if (mountflags & (MS_NOATIME | MS_RELATIME | MS_STRICTATIME)) {
attr.attr_set = MOUNT_ATTR_IDMAP | mountflags_to_attr(mountflags);
if (mountflags & (MS_NOATIME | MS_RELATIME | MS_STRICTATIME))
attr.attr_clr |= MOUNT_ATTR__ATIME;
if (mountflags & MS_NOATIME)
attr.attr_set |= MOUNT_ATTR_NOATIME;
else if (mountflags & MS_STRICTATIME)
attr.attr_set |= MOUNT_ATTR_STRICTATIME;
else
attr.attr_set |= MOUNT_ATTR_RELATIME;
}
attr.userns_fd = userns_fd;

if (sys_mount_setattr(treefd, "", setattr_flags, &attr, sizeof(attr)) < 0) {
Expand Down
25 changes: 25 additions & 0 deletions jail/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ int fs_mount_enable_idmap(const char *target, uint32_t uid, uint32_t gid);
char *resolve_mount_source(const char *source);
int add_mount_fd(int fd, const char *target, int error);
int mask_path_now(const char *path);
int remount_readonly(const char *path, unsigned long flags);
int bind_remount_readonly(const char *path, unsigned long flags);

/* open_tree()/mount_setattr() wrappers - no glibc wrappers yet.
* Fields must match the kernel's struct mount_attr layout exactly
Expand All @@ -56,6 +58,29 @@ int sys_open_tree(int dfd, const char *path, unsigned flags);
int sys_move_mount(int from_dfd, const char *from_path, int to_dfd,
const char *to_path, unsigned flags);
int sys_mount_setattr(int dfd, const char *path, unsigned flags, struct ujail_mount_attr *attr, size_t size);
int sys_fsopen(const char *fsname, unsigned flags);
int sys_fsconfig(int fd, unsigned cmd, const char *key, const void *value, int aux);
int sys_fsmount(int fd, unsigned flags, unsigned attr_flags);
int premount_sysfs(void);

#ifndef FSOPEN_CLOEXEC
#define FSOPEN_CLOEXEC 0x00000001
#endif
#ifndef FSMOUNT_CLOEXEC
#define FSMOUNT_CLOEXEC 0x00000001
#endif
#ifndef FSCONFIG_CMD_CREATE
#define FSCONFIG_CMD_CREATE 6
#endif
#ifndef MOUNT_ATTR_NOSUID
#define MOUNT_ATTR_NOSUID 0x00000002
#endif
#ifndef MOUNT_ATTR_NODEV
#define MOUNT_ATTR_NODEV 0x00000004
#endif
#ifndef MOUNT_ATTR_NOEXEC
#define MOUNT_ATTR_NOEXEC 0x00000008
#endif

#ifndef OPEN_TREE_CLONE
#define OPEN_TREE_CLONE 1
Expand Down
Loading
Loading