Skip to content

Commit b71a019

Browse files
committed
nspawn: mount temporary visible procfs and sysfs instance
In order to mount procfs and sysfs in an unprivileged container the kernel requires that a fully visible instance is already present in the target mount namespace. Mount one here so the inner child can mount its own instances. Later we umount the temporary instances created here before we actually exec the payload. Since the rootfs is shared the umount will propagate into the container. Note, the inner child wouldn't be able to unmount the instances on its own since it doesn't own the originating mount namespace. IOW, the outer child needs to do this. So far nspawn didn't run into this issue because it used MS_MOVE which meant that the shadow mount tree pinned a procfs and sysfs instance which the kernel would find. The shadow mount tree is gone with proper pivot_root() semantics. Signed-off-by: Christian Brauner (Microsoft) <brauner@kernel.org>
1 parent 57c10a5 commit b71a019

3 files changed

Lines changed: 107 additions & 10 deletions

File tree

src/nspawn/nspawn-mount.c

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "mkdir-label.h"
1414
#include "mount-util.h"
1515
#include "mountpoint-util.h"
16+
#include "namespace-util.h"
1617
#include "nspawn-mount.h"
1718
#include "parse-util.h"
1819
#include "path-util.h"
@@ -510,6 +511,9 @@ int mount_sysfs(const char *dest, MountSettingsMask mount_settings) {
510511
MS_BIND|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT|extra_flags, NULL);
511512
}
512513

514+
#define PROC_DEFAULT_MOUNT_FLAGS (MS_NOSUID|MS_NOEXEC|MS_NODEV)
515+
#define SYS_DEFAULT_MOUNT_FLAGS (MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV)
516+
513517
int mount_all(const char *dest,
514518
MountSettingsMask mount_settings,
515519
uid_t uid_shift,
@@ -538,7 +542,7 @@ int mount_all(const char *dest,
538542

539543
static const MountPoint mount_table[] = {
540544
/* First we list inner child mounts (i.e. mounts applied *after* entering user namespacing) */
541-
{ "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
545+
{ "proc", "/proc", "proc", NULL, PROC_DEFAULT_MOUNT_FLAGS,
542546
MOUNT_FATAL|MOUNT_IN_USERNS|MOUNT_MKDIR|MOUNT_FOLLOW_SYMLINKS }, /* we follow symlinks here since not following them requires /proc/ already being mounted, which we don't have here. */
543547

544548
{ "/proc/sys", "/proc/sys", NULL, NULL, MS_BIND,
@@ -576,7 +580,7 @@ int mount_all(const char *dest,
576580
MOUNT_FATAL|MOUNT_APPLY_TMPFS_TMP|MOUNT_MKDIR },
577581
{ "tmpfs", "/sys", "tmpfs", "mode=555" TMPFS_LIMITS_SYS, MS_NOSUID|MS_NOEXEC|MS_NODEV,
578582
MOUNT_FATAL|MOUNT_APPLY_APIVFS_NETNS|MOUNT_MKDIR },
579-
{ "sysfs", "/sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV,
583+
{ "sysfs", "/sys", "sysfs", NULL, SYS_DEFAULT_MOUNT_FLAGS,
580584
MOUNT_FATAL|MOUNT_APPLY_APIVFS_RO|MOUNT_MKDIR }, /* skipped if above was mounted */
581585
{ "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
582586
MOUNT_FATAL|MOUNT_MKDIR }, /* skipped if above was mounted */
@@ -1336,3 +1340,60 @@ int setup_pivot_root(const char *directory, const char *pivot_root_new, const ch
13361340

13371341
return r;
13381342
}
1343+
1344+
#define NSPAWN_PRIVATE_FULLY_VISIBLE_PROCFS "/run/host/proc"
1345+
#define NSPAWN_PRIVATE_FULLY_VISIBLE_SYSFS "/run/host/sys"
1346+
1347+
int pin_fully_visible_fs(void) {
1348+
int r;
1349+
1350+
(void) mkdir_p(NSPAWN_PRIVATE_FULLY_VISIBLE_PROCFS, 0755);
1351+
(void) mkdir_p(NSPAWN_PRIVATE_FULLY_VISIBLE_SYSFS, 0755);
1352+
1353+
r = mount_follow_verbose(LOG_ERR, "proc", NSPAWN_PRIVATE_FULLY_VISIBLE_PROCFS, "proc", PROC_DEFAULT_MOUNT_FLAGS, NULL);
1354+
if (r < 0)
1355+
return r;
1356+
1357+
r = mount_follow_verbose(LOG_ERR, "sysfs", NSPAWN_PRIVATE_FULLY_VISIBLE_SYSFS, "sysfs", SYS_DEFAULT_MOUNT_FLAGS, NULL);
1358+
if (r < 0)
1359+
return r;
1360+
1361+
return 0;
1362+
}
1363+
1364+
static int do_wipe_fully_visible_fs(void) {
1365+
if (umount2(NSPAWN_PRIVATE_FULLY_VISIBLE_PROCFS, MNT_DETACH) < 0)
1366+
return log_error_errno(errno, "Failed to unmount temporary proc: %m");
1367+
1368+
if (rmdir(NSPAWN_PRIVATE_FULLY_VISIBLE_PROCFS) < 0)
1369+
return log_error_errno(errno, "Failed to remove temporary proc mountpoint: %m");
1370+
1371+
if (umount2(NSPAWN_PRIVATE_FULLY_VISIBLE_SYSFS, MNT_DETACH) < 0)
1372+
return log_error_errno(errno, "Failed to unmount temporary sys: %m");
1373+
1374+
if (rmdir(NSPAWN_PRIVATE_FULLY_VISIBLE_SYSFS) < 0)
1375+
return log_error_errno(errno, "Failed to remove temporary sys mountpoint: %m");
1376+
1377+
return 0;
1378+
}
1379+
1380+
int wipe_fully_visible_fs(int mntns_fd) {
1381+
_cleanup_close_ int orig_mntns_fd = -EBADF;
1382+
int r, rr;
1383+
1384+
r = namespace_open(0, NULL, &orig_mntns_fd, NULL, NULL, NULL);
1385+
if (r < 0)
1386+
return log_error_errno(r, "Failed to pin originating mount namespace: %m");
1387+
1388+
r = namespace_enter(-EBADF, mntns_fd, -EBADF, -EBADF, -EBADF);
1389+
if (r < 0)
1390+
return log_error_errno(r, "Failed to enter mount namespace: %m");
1391+
1392+
rr = do_wipe_fully_visible_fs();
1393+
1394+
r = namespace_enter(-EBADF, orig_mntns_fd, -EBADF, -EBADF, -EBADF);
1395+
if (r < 0)
1396+
return log_error_errno(r, "Failed to enter original mount namespace: %m");
1397+
1398+
return rr;
1399+
}

src/nspawn/nspawn-mount.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,5 @@ int pivot_root_parse(char **pivot_root_new, char **pivot_root_old, const char *s
6767
int setup_pivot_root(const char *directory, const char *pivot_root_new, const char *pivot_root_old);
6868

6969
int tmpfs_patch_options(const char *options,uid_t uid_shift, const char *selinux_apifs_context, char **ret);
70+
int pin_fully_visible_fs(void);
71+
int wipe_fully_visible_fs(int mntns_fd);

src/nspawn/nspawn.c

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3632,7 +3632,7 @@ static int outer_child(
36323632

36333633
_cleanup_(bind_user_context_freep) BindUserContext *bind_user_context = NULL;
36343634
_cleanup_strv_free_ char **os_release_pairs = NULL;
3635-
_cleanup_close_ int fd = -1;
3635+
_cleanup_close_ int fd = -1, mntns_fd = -EBADF;
36363636
bool idmap = false;
36373637
const char *p;
36383638
pid_t pid;
@@ -3697,6 +3697,15 @@ static int outer_child(
36973697
return r;
36983698

36993699
if (arg_userns_mode != USER_NAMESPACE_NO) {
3700+
r = namespace_open(0, NULL, &mntns_fd, NULL, NULL, NULL);
3701+
if (r < 0)
3702+
return log_error_errno(r, "Failed to pin outer mount namespace: %m");
3703+
3704+
l = send_one_fd(notify_socket, mntns_fd, 0);
3705+
if (l < 0)
3706+
return log_error_errno(l, "Failed to send outer mount namespace fd: %m");
3707+
mntns_fd = safe_close(mntns_fd);
3708+
37003709
/* Let the parent know which UID shift we read from the image */
37013710
l = send(uid_shift_socket, &arg_uid_shift, sizeof(arg_uid_shift), MSG_NOSIGNAL);
37023711
if (l < 0)
@@ -3974,6 +3983,20 @@ static int outer_child(
39743983
if (r < 0)
39753984
return log_error_errno(r, "Failed to move root directory: %m");
39763985

3986+
if (arg_userns_mode != USER_NAMESPACE_NO) {
3987+
/* In order to mount procfs and sysfs in an unprivileged container the kernel
3988+
* requires that a fully visible instance is already present in the target mount
3989+
* namespace. Mount one here so the inner child can mount its own instances. Later
3990+
* we umount the temporary instances created here before we actually exec the
3991+
* payload. Since the rootfs is shared the umount will propagate into the container.
3992+
* Note, the inner child wouldn't be able to unmount the instances on its own since
3993+
* it doesn't own the originating mount namespace. IOW, the outer child needs to do
3994+
* this. */
3995+
r = pin_fully_visible_fs();
3996+
if (r < 0)
3997+
return r;
3998+
}
3999+
39774000
fd = setup_notify_child();
39784001
if (fd < 0)
39794002
return fd;
@@ -4731,12 +4754,12 @@ static int run_container(
47314754
rtnl_socket_pair[2] = { -1, -1 },
47324755
pid_socket_pair[2] = { -1, -1 },
47334756
uuid_socket_pair[2] = { -1, -1 },
4734-
notify_socket_pair[2] = { -1, -1 },
4757+
fd_socket_pair[2] = { -EBADF, -EBADF },
47354758
uid_shift_socket_pair[2] = { -1, -1 },
47364759
master_pty_socket_pair[2] = { -1, -1 },
47374760
unified_cgroup_hierarchy_socket_pair[2] = { -1, -1};
47384761

4739-
_cleanup_close_ int notify_socket = -1;
4762+
_cleanup_close_ int notify_socket = -1, mntns_fd = -EBADF;
47404763
_cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL;
47414764
_cleanup_(sd_event_source_unrefp) sd_event_source *notify_event_source = NULL;
47424765
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
@@ -4783,7 +4806,7 @@ static int run_container(
47834806
if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, uuid_socket_pair) < 0)
47844807
return log_error_errno(errno, "Failed to create id socket pair: %m");
47854808

4786-
if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, notify_socket_pair) < 0)
4809+
if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, fd_socket_pair) < 0)
47874810
return log_error_errno(errno, "Failed to create notify socket pair: %m");
47884811

47894812
if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, master_pty_socket_pair) < 0)
@@ -4836,7 +4859,7 @@ static int run_container(
48364859
rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]);
48374860
pid_socket_pair[0] = safe_close(pid_socket_pair[0]);
48384861
uuid_socket_pair[0] = safe_close(uuid_socket_pair[0]);
4839-
notify_socket_pair[0] = safe_close(notify_socket_pair[0]);
4862+
fd_socket_pair[0] = safe_close(fd_socket_pair[0]);
48404863
master_pty_socket_pair[0] = safe_close(master_pty_socket_pair[0]);
48414864
uid_shift_socket_pair[0] = safe_close(uid_shift_socket_pair[0]);
48424865
unified_cgroup_hierarchy_socket_pair[0] = safe_close(unified_cgroup_hierarchy_socket_pair[0]);
@@ -4850,7 +4873,7 @@ static int run_container(
48504873
secondary,
48514874
pid_socket_pair[1],
48524875
uuid_socket_pair[1],
4853-
notify_socket_pair[1],
4876+
fd_socket_pair[1],
48544877
kmsg_socket_pair[1],
48554878
rtnl_socket_pair[1],
48564879
uid_shift_socket_pair[1],
@@ -4872,12 +4895,16 @@ static int run_container(
48724895
rtnl_socket_pair[1] = safe_close(rtnl_socket_pair[1]);
48734896
pid_socket_pair[1] = safe_close(pid_socket_pair[1]);
48744897
uuid_socket_pair[1] = safe_close(uuid_socket_pair[1]);
4875-
notify_socket_pair[1] = safe_close(notify_socket_pair[1]);
4898+
fd_socket_pair[1] = safe_close(fd_socket_pair[1]);
48764899
master_pty_socket_pair[1] = safe_close(master_pty_socket_pair[1]);
48774900
uid_shift_socket_pair[1] = safe_close(uid_shift_socket_pair[1]);
48784901
unified_cgroup_hierarchy_socket_pair[1] = safe_close(unified_cgroup_hierarchy_socket_pair[1]);
48794902

48804903
if (arg_userns_mode != USER_NAMESPACE_NO) {
4904+
mntns_fd = receive_one_fd(fd_socket_pair[0], 0);
4905+
if (mntns_fd < 0)
4906+
return log_error_errno(mntns_fd, "Failed to receive mount namespace fd from outer child: %m");
4907+
48814908
/* The child just let us know the UID shift it might have read from the image. */
48824909
l = recv(uid_shift_socket_pair[0], &arg_uid_shift, sizeof arg_uid_shift, 0);
48834910
if (l < 0)
@@ -4954,7 +4981,7 @@ static int run_container(
49544981
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading container machined ID.");
49554982

49564983
/* We also retrieve the socket used for notifications generated by outer child */
4957-
notify_socket = receive_one_fd(notify_socket_pair[0], 0);
4984+
notify_socket = receive_one_fd(fd_socket_pair[0], 0);
49584985
if (notify_socket < 0)
49594986
return log_error_errno(notify_socket,
49604987
"Failed to receive notification socket from the outer child: %m");
@@ -5139,6 +5166,13 @@ static int run_container(
51395166
if (r < 0)
51405167
return r;
51415168

5169+
if (arg_userns_mode != USER_NAMESPACE_NO) {
5170+
r = wipe_fully_visible_fs(mntns_fd);
5171+
if (r < 0)
5172+
return r;
5173+
mntns_fd = safe_close(mntns_fd);
5174+
}
5175+
51425176
/* Let the child know that we are ready and wait that the child is completely ready now. */
51435177
if (!barrier_place_and_sync(&barrier)) /* #5 */
51445178
return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Child died too early.");

0 commit comments

Comments
 (0)