blob: 46a56b722f3ece6d7b25f0bbb0c13ff2657f6591 [file]
// SPDX-License-Identifier: GPL-2.0
/*
* propagate_umount() slides a surviving overmount off a mount that is being
* unmounted and unmounts that mount right after. If a file descriptor keeps
* the unmounted mount alive its ->overmount is left pointing at the moved
* mount, which is freed on its own schedule. move_mount(MOVE_MOUNT_BENEATH)
* with such a file descriptor as the target walks ->overmount in
* topmost_overmount() before it checks that the target is still mounted and
* must not step into the freed mount.
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include "../wrappers.h"
#include "../../kselftest_harness.h"
#ifndef MOVE_MOUNT_BENEATH
#define MOVE_MOUNT_BENEATH 0x00000200
#endif
#ifndef FSCONFIG_CMD_CREATE
#define FSCONFIG_CMD_CREATE 6
#endif
#define DIR_LEN 64
#define PATH_LEN 128
static int write_file(const char *path, const char *s)
{
ssize_t n = -1;
int fd;
fd = open(path, O_WRONLY | O_CLOEXEC);
if (fd >= 0) {
n = write(fd, s, strlen(s));
close(fd);
}
return n == (ssize_t)strlen(s) ? 0 : -1;
}
/* Become root in a new user namespace with a private mount namespace. */
static int enter_userns(void)
{
uid_t uid = getuid();
gid_t gid = getgid();
char map[32];
if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
return -1;
if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT)
return -1;
snprintf(map, sizeof(map), "0 %d 1", uid);
if (write_file("/proc/self/uid_map", map))
return -1;
snprintf(map, sizeof(map), "0 %d 1", gid);
if (write_file("/proc/self/gid_map", map))
return -1;
if (setgid(0) || setuid(0))
return -1;
return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL);
}
/* A detached tmpfs mount to serve as the source of a move. */
static int detached_tmpfs(void)
{
int sfd, mfd;
sfd = sys_fsopen("tmpfs", 0);
if (sfd < 0)
return -1;
if (sys_fsconfig(sfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) {
close(sfd);
return -1;
}
mfd = sys_fsmount(sfd, 0, 0);
close(sfd);
return mfd;
}
FIXTURE(overmount_reparent) {
char dir[DIR_LEN];
};
FIXTURE_SETUP(overmount_reparent)
{
snprintf(self->dir, sizeof(self->dir), "/tmp/overmount_reparent.XXXXXX");
ASSERT_NE(mkdtemp(self->dir), NULL);
if (enter_userns()) {
rmdir(self->dir);
SKIP(return, "test requires user namespaces");
}
}
FIXTURE_TEARDOWN(overmount_reparent)
{
char p[PATH_LEN];
snprintf(p, sizeof(p), "%s/b", self->dir);
umount2(p, MNT_DETACH);
snprintf(p, sizeof(p), "%s/a", self->dir);
umount2(p, MNT_DETACH);
}
/*
* A shared mount base_a is bind-mounted to base_b as its peer. A mount X on
* base_a/mp propagates a copy X_b onto base_b/mp. A file descriptor pins X_b,
* then X_b is made private and an overmount is stacked on its root.
*
* A lazy unmount of X propagates to X_b: X_b is committed to the unmount while
* its overmount survives, so propagate_umount() reparents the overmount onto
* base_b and unmounts X_b, which the file descriptor keeps alive. Unmounting
* the reparented overmount frees it. X_b->overmount now dangles unless
* mnt_change_mountpoint() reset it.
*
* move_mount(MOVE_MOUNT_BENEATH) through the file descriptor makes
* do_lock_mount() follow X_b->overmount in topmost_overmount() before it
* notices that X_b is no longer mounted. With the pointer reset the move fails
* cleanly with ENOENT; otherwise it reads the freed overmount.
*/
TEST_F(overmount_reparent, dead_overmount_holder_not_followed)
{
char a[PATH_LEN], b[PATH_LEN], amp[PATH_LEN], bmp[PATH_LEN];
int fd, mfd, ret;
snprintf(a, sizeof(a), "%s/a", self->dir);
snprintf(b, sizeof(b), "%s/b", self->dir);
ASSERT_EQ(mkdir(a, 0755), 0);
ASSERT_EQ(mkdir(b, 0755), 0);
ASSERT_EQ(mount("tmpfs", a, "tmpfs", 0, NULL), 0);
ASSERT_EQ(mount(NULL, a, NULL, MS_SHARED, NULL), 0);
ASSERT_EQ(mount(a, b, NULL, MS_BIND, NULL), 0);
snprintf(amp, sizeof(amp), "%s/a/mp", self->dir);
snprintf(bmp, sizeof(bmp), "%s/b/mp", self->dir);
/* base_a and base_b share one superblock, so this dir is in both. */
ASSERT_EQ(mkdir(amp, 0755), 0);
/* X on base_a/mp propagates a copy X_b onto base_b/mp. */
ASSERT_EQ(mount("tmpfs", amp, "tmpfs", 0, NULL), 0);
/* Pin X_b before anything is stacked on top of it. */
fd = open(bmp, O_PATH | O_CLOEXEC);
ASSERT_GE(fd, 0);
/* Keep the overmount local to X_b. */
ASSERT_EQ(mount(NULL, bmp, NULL, MS_PRIVATE, NULL), 0);
/* The overmount on X_b's root: X_b->overmount points at it. */
ASSERT_EQ(mount("tmpfs", bmp, "tmpfs", 0, NULL), 0);
/* Reparents the overmount onto base_b and unmounts X_b. */
ASSERT_EQ(umount2(amp, MNT_DETACH), 0);
/* Free the reparented overmount. */
ASSERT_EQ(umount2(bmp, MNT_DETACH), 0);
usleep(100000);
mfd = detached_tmpfs();
ASSERT_GE(mfd, 0);
ret = sys_move_mount(mfd, "", fd, "",
MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH |
MOVE_MOUNT_BENEATH);
EXPECT_EQ(ret, -1);
EXPECT_EQ(errno, ENOENT);
close(mfd);
close(fd);
}
TEST_HARNESS_MAIN