// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/wait.h>
#include <sys/vfs.h>
#include <sys/statvfs.h>
#include <sys/sysinfo.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <grp.h>
#include <stdbool.h>
#include <stdarg.h>
#include <linux/mount.h>
#include "../kselftest_harness.h"
#ifndef CLONE_NEWNS
#define CLONE_NEWNS 0x00020000
#endif
#ifndef CLONE_NEWUSER
#define CLONE_NEWUSER 0x10000000
#endif
#ifndef MS_REC
#define MS_REC 16384
#endif
#ifndef MS_RELATIME
#define MS_RELATIME (1 << 21)
#endif
#ifndef MS_STRICTATIME
#define MS_STRICTATIME (1 << 24)
#endif
#ifndef MOUNT_ATTR_RDONLY
#define MOUNT_ATTR_RDONLY 0x00000001
#endif
#ifndef MOUNT_ATTR_NOSUID
#define MOUNT_ATTR_NOSUID 0x00000002
#endif
#ifndef MOUNT_ATTR_NOEXEC
#define MOUNT_ATTR_NOEXEC 0x00000008
#endif
#ifndef MOUNT_ATTR_NODIRATIME
#define MOUNT_ATTR_NODIRATIME 0x00000080
#endif
#ifndef MOUNT_ATTR__ATIME
#define MOUNT_ATTR__ATIME 0x00000070
#endif
#ifndef MOUNT_ATTR_RELATIME
#define MOUNT_ATTR_RELATIME 0x00000000
#endif
#ifndef MOUNT_ATTR_NOATIME
#define MOUNT_ATTR_NOATIME 0x00000010
#endif
#ifndef MOUNT_ATTR_STRICTATIME
#define MOUNT_ATTR_STRICTATIME 0x00000020
#endif
#ifndef AT_RECURSIVE
#define AT_RECURSIVE 0x8000
#endif
#ifndef MS_SHARED
#define MS_SHARED (1 << 20)
#endif
#define DEFAULT_THREADS 4
#define ptr_to_int(p) ((int)((intptr_t)(p)))
#define int_to_ptr(u) ((void *)((intptr_t)(u)))
#ifndef __NR_mount_setattr
#if defined __alpha__
#define __NR_mount_setattr 552
#elif defined _MIPS_SIM
#if _MIPS_SIM == _MIPS_SIM_ABI32 /* o32 */
#define __NR_mount_setattr (442 + 4000)
#endif
#if _MIPS_SIM == _MIPS_SIM_NABI32 /* n32 */
#define __NR_mount_setattr (442 + 6000)
#endif
#if _MIPS_SIM == _MIPS_SIM_ABI64 /* n64 */
#define __NR_mount_setattr (442 + 5000)
#endif
#elif defined __ia64__
#define __NR_mount_setattr (442 + 1024)
#else
#define __NR_mount_setattr 442
#endif
#endif
#ifndef __NR_open_tree
#if defined __alpha__
#define __NR_open_tree 538
#elif defined _MIPS_SIM
#if _MIPS_SIM == _MIPS_SIM_ABI32 /* o32 */
#define __NR_open_tree 4428
#endif
#if _MIPS_SIM == _MIPS_SIM_NABI32 /* n32 */
#define __NR_open_tree 6428
#endif
#if _MIPS_SIM == _MIPS_SIM_ABI64 /* n64 */
#define __NR_open_tree 5428
#endif
#elif defined __ia64__
#define __NR_open_tree (428 + 1024)
#else
#define __NR_open_tree 428
#endif
#endif
#ifndef MOUNT_ATTR_IDMAP
#define MOUNT_ATTR_IDMAP 0x00100000
#endif
#ifndef MOUNT_ATTR_NOSYMFOLLOW
#define MOUNT_ATTR_NOSYMFOLLOW 0x00200000
#endif
static inline int sys_mount_setattr(int dfd, const char *path, unsigned int flags,
struct mount_attr *attr, size_t size)
{
return syscall(__NR_mount_setattr, dfd, path, flags, attr, size);
}
#ifndef OPEN_TREE_CLONE
#define OPEN_TREE_CLONE 1
#endif
#ifndef OPEN_TREE_CLOEXEC
#define OPEN_TREE_CLOEXEC O_CLOEXEC
#endif
#ifndef AT_RECURSIVE
#define AT_RECURSIVE 0x8000 /* Apply to the entire subtree */
#endif
static inline int sys_open_tree(int dfd, const char *filename, unsigned int flags)
{
return syscall(__NR_open_tree, dfd, filename, flags);
}
static ssize_t write_nointr(int fd, const void *buf, size_t count)
{
ssize_t ret;
do {
ret = write(fd, buf, count);
} while (ret < 0 && errno == EINTR);
return ret;
}
static int write_file(const char *path, const void *buf, size_t count)
{
int fd;
ssize_t ret;
fd = open(path, O_WRONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW);
if (fd < 0)
return -1;
ret = write_nointr(fd, buf, count);
close(fd);
if (ret < 0 || (size_t)ret != count)
return -1;
return 0;
}
static int create_and_enter_userns(void)
{
uid_t uid;
gid_t gid;
char map[100];
uid = getuid();
gid = getgid();
if (unshare(CLONE_NEWUSER))
return -1;
if (write_file("/proc/self/setgroups", "deny", sizeof("deny") - 1) &&
errno != ENOENT)
return -1;
snprintf(map, sizeof(map), "0 %d 1", uid);
if (write_file("/proc/self/uid_map", map, strlen(map)))
return -1;
snprintf(map, sizeof(map), "0 %d 1", gid);
if (write_file("/proc/self/gid_map", map, strlen(map)))
return -1;
if (setgid(0))
return -1;
if (setuid(0))
return -1;
return 0;
}
static int prepare_unpriv_mountns(void)
{
if (create_and_enter_userns())
return -1;
if (unshare(CLONE_NEWNS))
return -1;
if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0))
return -1;
return 0;
}
#ifndef ST_NOSYMFOLLOW
#define ST_NOSYMFOLLOW 0x2000 /* do not follow symlinks */
#endif
static int read_mnt_flags(const char *path)
{
int ret;
struct statvfs stat;
unsigned int mnt_flags;
ret = statvfs(path, &stat);
if (ret != 0)
return -EINVAL;
if (stat.f_flag & ~(ST_RDONLY | ST_NOSUID | ST_NODEV | ST_NOEXEC |
ST_NOATIME | ST_NODIRATIME | ST_RELATIME |
ST_SYNCHRONOUS | ST_MANDLOCK | ST_NOSYMFOLLOW))
return -EINVAL;
mnt_flags = 0;
if (stat.f_flag & ST_RDONLY)
mnt_flags |= MS_RDONLY;
if (stat.f_flag & ST_NOSUID)
mnt_flags |= MS_NOSUID;
if (stat.f_flag & ST_NODEV)
mnt_flags |= MS_NODEV;
if (stat.f_flag & ST_NOEXEC)
mnt_flags |= MS_NOEXEC;
if (stat.f_flag & ST_NOATIME)
mnt_flags |= MS_NOATIME;
if (stat.f_flag & ST_NODIRATIME)
mnt_flags |= MS_NODIRATIME;
if (stat.f_flag & ST_RELATIME)
mnt_flags |= MS_RELATIME;
if (stat.f_flag & ST_SYNCHRONOUS)
mnt_flags |= MS_SYNCHRONOUS;
if (stat.f_flag & ST_MANDLOCK)
mnt_flags |= ST_MANDLOCK;
if (stat.f_flag & ST_NOSYMFOLLOW)
mnt_flags |= ST_NOSYMFOLLOW;
return mnt_flags;
}
static char *get_field(char *src, int nfields)
{
int i;
char *p = src;
for (i = 0; i < nfields; i++) {
while (*p && *p != ' ' && *p != '\t')
p++;
if (!*p)
break;
p++;
}
return p;
}
static void null_endofword(char *word)
{
while (*word && *word != ' ' && *word != '\t')
word++;
*word = '\0';
}
static bool is_shared_mount(const char *path)
{
size_t len = 0;
char *line = NULL;
FILE *f = NULL;
f = fopen("/proc/self/mountinfo", "re");
if (!f)
return false;
while (getline(&line, &len, f) != -1) {
char *opts, *target;
target = get_field(line, 4);
if (!target)
continue;
opts = get_field(target, 2);
if (!opts)
continue;
null_endofword(target);
if (strcmp(target, path) != 0)
continue;
null_endofword(opts);
if (strstr(opts, "shared:"))
return true;
}
free(line);
fclose(f);
return false;
}
static void *mount_setattr_thread(void *data)
{
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID,
.attr_clr = 0,
.propagation = MS_SHARED,
};
if (sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)))
pthread_exit(int_to_ptr(-1));
pthread_exit(int_to_ptr(0));
}
/* Attempt to de-conflict with the selftests tree. */
#ifndef SKIP
#define SKIP(s, ...) XFAIL(s, ##__VA_ARGS__)
#endif
static bool mount_setattr_supported(void)
{
int ret;
ret = sys_mount_setattr(-EBADF, "", AT_EMPTY_PATH, NULL, 0);
if (ret < 0 && errno == ENOSYS)
return false;
return true;
}
FIXTURE(mount_setattr) {
};
#define NOSYMFOLLOW_TARGET "/mnt/A/AA/data"
#define NOSYMFOLLOW_SYMLINK "/mnt/A/AA/symlink"
FIXTURE_SETUP(mount_setattr)
{
int fd = -EBADF;
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
ASSERT_EQ(prepare_unpriv_mountns(), 0);
(void)umount2("/mnt", MNT_DETACH);
(void)umount2("/tmp", MNT_DETACH);
ASSERT_EQ(mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV,
"size=100000,mode=700"), 0);
ASSERT_EQ(mkdir("/tmp/B", 0777), 0);
ASSERT_EQ(mount("testing", "/tmp/B", "tmpfs", MS_NOATIME | MS_NODEV,
"size=100000,mode=700"), 0);
ASSERT_EQ(mkdir("/tmp/B/BB", 0777), 0);
ASSERT_EQ(mount("testing", "/tmp/B/BB", "tmpfs", MS_NOATIME | MS_NODEV,
"size=100000,mode=700"), 0);
ASSERT_EQ(mount("testing", "/mnt", "tmpfs", MS_NOATIME | MS_NODEV,
"size=100000,mode=700"), 0);
ASSERT_EQ(mkdir("/mnt/A", 0777), 0);
ASSERT_EQ(mount("testing", "/mnt/A", "tmpfs", MS_NOATIME | MS_NODEV,
"size=100000,mode=700"), 0);
ASSERT_EQ(mkdir("/mnt/A/AA", 0777), 0);
ASSERT_EQ(mount("/tmp", "/mnt/A/AA", NULL, MS_BIND | MS_REC, NULL), 0);
ASSERT_EQ(mkdir("/mnt/B", 0777), 0);
ASSERT_EQ(mount("testing", "/mnt/B", "ramfs",
MS_NOATIME | MS_NODEV | MS_NOSUID, 0), 0);
ASSERT_EQ(mkdir("/mnt/B/BB", 0777), 0);
ASSERT_EQ(mount("testing", "/tmp/B/BB", "devpts",
MS_RELATIME | MS_NOEXEC | MS_RDONLY, 0), 0);
fd = creat(NOSYMFOLLOW_TARGET, O_RDWR | O_CLOEXEC);
ASSERT_GT(fd, 0);
ASSERT_EQ(symlink(NOSYMFOLLOW_TARGET, NOSYMFOLLOW_SYMLINK), 0);
ASSERT_EQ(close(fd), 0);
}
FIXTURE_TEARDOWN(mount_setattr)
{
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
(void)umount2("/mnt/A", MNT_DETACH);
(void)umount2("/tmp", MNT_DETACH);
}
TEST_F(mount_setattr, invalid_attributes)
{
struct mount_attr invalid_attr = {
.attr_set = (1U << 31),
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr,
sizeof(invalid_attr)), 0);
invalid_attr.attr_set = 0;
invalid_attr.attr_clr = (1U << 31);
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr,
sizeof(invalid_attr)), 0);
invalid_attr.attr_clr = 0;
invalid_attr.propagation = (1U << 31);
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr,
sizeof(invalid_attr)), 0);
invalid_attr.attr_set = (1U << 31);
invalid_attr.attr_clr = (1U << 31);
invalid_attr.propagation = (1U << 31);
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr,
sizeof(invalid_attr)), 0);
ASSERT_NE(sys_mount_setattr(-1, "mnt/A", AT_RECURSIVE, &invalid_attr,
sizeof(invalid_attr)), 0);
}
TEST_F(mount_setattr, extensibility)
{
unsigned int old_flags = 0, new_flags = 0, expected_flags = 0;
char *s = "dummy";
struct mount_attr invalid_attr = {};
struct mount_attr_large {
struct mount_attr attr1;
struct mount_attr attr2;
struct mount_attr attr3;
} large_attr = {};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
old_flags = read_mnt_flags("/mnt/A");
ASSERT_GT(old_flags, 0);
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, NULL,
sizeof(invalid_attr)), 0);
ASSERT_EQ(errno, EFAULT);
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, (void *)s,
sizeof(invalid_attr)), 0);
ASSERT_EQ(errno, EINVAL);
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, 0), 0);
ASSERT_EQ(errno, EINVAL);
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr,
sizeof(invalid_attr) / 2), 0);
ASSERT_EQ(errno, EINVAL);
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr,
sizeof(invalid_attr) / 2), 0);
ASSERT_EQ(errno, EINVAL);
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE,
(void *)&large_attr, sizeof(large_attr)), 0);
large_attr.attr3.attr_set = MOUNT_ATTR_RDONLY;
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE,
(void *)&large_attr, sizeof(large_attr)), 0);
large_attr.attr3.attr_set = 0;
large_attr.attr1.attr_set = MOUNT_ATTR_RDONLY;
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE,
(void *)&large_attr, sizeof(large_attr)), 0);
expected_flags = old_flags;
expected_flags |= MS_RDONLY;
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, expected_flags);
}
TEST_F(mount_setattr, basic)
{
unsigned int old_flags = 0, new_flags = 0, expected_flags = 0;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOEXEC | MOUNT_ATTR_RELATIME,
.attr_clr = MOUNT_ATTR__ATIME,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
old_flags = read_mnt_flags("/mnt/A");
ASSERT_GT(old_flags, 0);
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", 0, &attr, sizeof(attr)), 0);
expected_flags = old_flags;
expected_flags |= MS_RDONLY;
expected_flags |= MS_NOEXEC;
expected_flags &= ~MS_NOATIME;
expected_flags |= MS_RELATIME;
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, old_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, old_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, old_flags);
}
TEST_F(mount_setattr, basic_recursive)
{
int fd;
unsigned int old_flags = 0, new_flags = 0, expected_flags = 0;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOEXEC | MOUNT_ATTR_RELATIME,
.attr_clr = MOUNT_ATTR__ATIME,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
old_flags = read_mnt_flags("/mnt/A");
ASSERT_GT(old_flags, 0);
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
expected_flags = old_flags;
expected_flags |= MS_RDONLY;
expected_flags |= MS_NOEXEC;
expected_flags &= ~MS_NOATIME;
expected_flags |= MS_RELATIME;
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, expected_flags);
memset(&attr, 0, sizeof(attr));
attr.attr_clr = MOUNT_ATTR_RDONLY;
attr.propagation = MS_SHARED;
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
expected_flags &= ~MS_RDONLY;
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, expected_flags);
ASSERT_EQ(is_shared_mount("/mnt/A"), true);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, expected_flags);
ASSERT_EQ(is_shared_mount("/mnt/A/AA"), true);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, expected_flags);
ASSERT_EQ(is_shared_mount("/mnt/A/AA/B"), true);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, expected_flags);
ASSERT_EQ(is_shared_mount("/mnt/A/AA/B/BB"), true);
fd = open("/mnt/A/AA/B/b", O_RDWR | O_CLOEXEC | O_CREAT | O_EXCL, 0777);
ASSERT_GE(fd, 0);
/*
* We're holding a fd open for writing so this needs to fail somewhere
* in the middle and the mount options need to be unchanged.
*/
attr.attr_set = MOUNT_ATTR_RDONLY;
ASSERT_LT(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, expected_flags);
ASSERT_EQ(is_shared_mount("/mnt/A"), true);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, expected_flags);
ASSERT_EQ(is_shared_mount("/mnt/A/AA"), true);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, expected_flags);
ASSERT_EQ(is_shared_mount("/mnt/A/AA/B"), true);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, expected_flags);
ASSERT_EQ(is_shared_mount("/mnt/A/AA/B/BB"), true);
EXPECT_EQ(close(fd), 0);
}
TEST_F(mount_setattr, mount_has_writers)
{
int fd, dfd;
unsigned int old_flags = 0, new_flags = 0;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOEXEC | MOUNT_ATTR_RELATIME,
.attr_clr = MOUNT_ATTR__ATIME,
.propagation = MS_SHARED,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
old_flags = read_mnt_flags("/mnt/A");
ASSERT_GT(old_flags, 0);
fd = open("/mnt/A/AA/B/b", O_RDWR | O_CLOEXEC | O_CREAT | O_EXCL, 0777);
ASSERT_GE(fd, 0);
/*
* We're holding a fd open to a mount somwhere in the middle so this
* needs to fail somewhere in the middle. After this the mount options
* need to be unchanged.
*/
ASSERT_LT(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, old_flags);
ASSERT_EQ(is_shared_mount("/mnt/A"), false);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, old_flags);
ASSERT_EQ(is_shared_mount("/mnt/A/AA"), false);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, old_flags);
ASSERT_EQ(is_shared_mount("/mnt/A/AA/B"), false);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, old_flags);
ASSERT_EQ(is_shared_mount("/mnt/A/AA/B/BB"), false);
dfd = open("/mnt/A/AA/B", O_DIRECTORY | O_CLOEXEC);
ASSERT_GE(dfd, 0);
EXPECT_EQ(fsync(dfd), 0);
EXPECT_EQ(close(dfd), 0);
EXPECT_EQ(fsync(fd), 0);
EXPECT_EQ(close(fd), 0);
/* All writers are gone so this should succeed. */
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
}
TEST_F(mount_setattr, mixed_mount_options)
{
unsigned int old_flags1 = 0, old_flags2 = 0, new_flags = 0, expected_flags = 0;
struct mount_attr attr = {
.attr_clr = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID | MOUNT_ATTR_NOEXEC | MOUNT_ATTR__ATIME,
.attr_set = MOUNT_ATTR_RELATIME,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
old_flags1 = read_mnt_flags("/mnt/B");
ASSERT_GT(old_flags1, 0);
old_flags2 = read_mnt_flags("/mnt/B/BB");
ASSERT_GT(old_flags2, 0);
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/B", AT_RECURSIVE, &attr, sizeof(attr)), 0);
expected_flags = old_flags2;
expected_flags &= ~(MS_RDONLY | MS_NOEXEC | MS_NOATIME | MS_NOSUID);
expected_flags |= MS_RELATIME;
new_flags = read_mnt_flags("/mnt/B");
ASSERT_EQ(new_flags, expected_flags);
expected_flags = old_flags2;
expected_flags &= ~(MS_RDONLY | MS_NOEXEC | MS_NOATIME | MS_NOSUID);
expected_flags |= MS_RELATIME;
new_flags = read_mnt_flags("/mnt/B/BB");
ASSERT_EQ(new_flags, expected_flags);
}
TEST_F(mount_setattr, time_changes)
{
unsigned int old_flags = 0, new_flags = 0, expected_flags = 0;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_NODIRATIME | MOUNT_ATTR_NOATIME,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
attr.attr_set = MOUNT_ATTR_STRICTATIME;
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
attr.attr_set = MOUNT_ATTR_STRICTATIME | MOUNT_ATTR_NOATIME;
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
attr.attr_set = MOUNT_ATTR_STRICTATIME | MOUNT_ATTR_NOATIME;
attr.attr_clr = MOUNT_ATTR__ATIME;
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
attr.attr_set = 0;
attr.attr_clr = MOUNT_ATTR_STRICTATIME;
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
attr.attr_clr = MOUNT_ATTR_NOATIME;
ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
old_flags = read_mnt_flags("/mnt/A");
ASSERT_GT(old_flags, 0);
attr.attr_set = MOUNT_ATTR_NODIRATIME | MOUNT_ATTR_NOATIME;
attr.attr_clr = MOUNT_ATTR__ATIME;
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
expected_flags = old_flags;
expected_flags |= MS_NOATIME;
expected_flags |= MS_NODIRATIME;
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, expected_flags);
memset(&attr, 0, sizeof(attr));
attr.attr_set &= ~MOUNT_ATTR_NOATIME;
attr.attr_set |= MOUNT_ATTR_RELATIME;
attr.attr_clr |= MOUNT_ATTR__ATIME;
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
expected_flags &= ~MS_NOATIME;
expected_flags |= MS_RELATIME;
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, expected_flags);
memset(&attr, 0, sizeof(attr));
attr.attr_set &= ~MOUNT_ATTR_RELATIME;
attr.attr_set |= MOUNT_ATTR_STRICTATIME;
attr.attr_clr |= MOUNT_ATTR__ATIME;
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
expected_flags &= ~MS_RELATIME;
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, expected_flags);
memset(&attr, 0, sizeof(attr));
attr.attr_set &= ~MOUNT_ATTR_STRICTATIME;
attr.attr_set |= MOUNT_ATTR_NOATIME;
attr.attr_clr |= MOUNT_ATTR__ATIME;
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
expected_flags |= MS_NOATIME;
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, expected_flags);
memset(&attr, 0, sizeof(attr));
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, expected_flags);
memset(&attr, 0, sizeof(attr));
attr.attr_clr = MOUNT_ATTR_NODIRATIME;
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
expected_flags &= ~MS_NODIRATIME;
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, expected_flags);
}
TEST_F(mount_setattr, multi_threaded)
{
int i, j, nthreads, ret = 0;
unsigned int old_flags = 0, new_flags = 0, expected_flags = 0;
pthread_attr_t pattr;
pthread_t threads[DEFAULT_THREADS];
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
old_flags = read_mnt_flags("/mnt/A");
ASSERT_GT(old_flags, 0);
/* Try to change mount options from multiple threads. */
nthreads = get_nprocs_conf();
if (nthreads > DEFAULT_THREADS)
nthreads = DEFAULT_THREADS;
pthread_attr_init(&pattr);
for (i = 0; i < nthreads; i++)
ASSERT_EQ(pthread_create(&threads[i], &pattr, mount_setattr_thread, NULL), 0);
for (j = 0; j < i; j++) {
void *retptr = NULL;
EXPECT_EQ(pthread_join(threads[j], &retptr), 0);
ret += ptr_to_int(retptr);
EXPECT_EQ(ret, 0);
}
pthread_attr_destroy(&pattr);
ASSERT_EQ(ret, 0);
expected_flags = old_flags;
expected_flags |= MS_RDONLY;
expected_flags |= MS_NOSUID;
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, expected_flags);
ASSERT_EQ(is_shared_mount("/mnt/A"), true);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, expected_flags);
ASSERT_EQ(is_shared_mount("/mnt/A/AA"), true);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, expected_flags);
ASSERT_EQ(is_shared_mount("/mnt/A/AA/B"), true);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, expected_flags);
ASSERT_EQ(is_shared_mount("/mnt/A/AA/B/BB"), true);
}
TEST_F(mount_setattr, wrong_user_namespace)
{
int ret;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_RDONLY,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
EXPECT_EQ(create_and_enter_userns(), 0);
ret = sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr));
ASSERT_LT(ret, 0);
ASSERT_EQ(errno, EPERM);
}
TEST_F(mount_setattr, wrong_mount_namespace)
{
int fd, ret;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_RDONLY,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
fd = open("/mnt/A", O_DIRECTORY | O_CLOEXEC);
ASSERT_GE(fd, 0);
ASSERT_EQ(unshare(CLONE_NEWNS), 0);
ret = sys_mount_setattr(fd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr));
ASSERT_LT(ret, 0);
ASSERT_EQ(errno, EINVAL);
}
FIXTURE(mount_setattr_idmapped) {
};
FIXTURE_SETUP(mount_setattr_idmapped)
{
int img_fd = -EBADF;
ASSERT_EQ(unshare(CLONE_NEWNS), 0);
ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0), 0);
(void)umount2("/mnt", MNT_DETACH);
(void)umount2("/tmp", MNT_DETACH);
ASSERT_EQ(mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV,
"size=100000,mode=700"), 0);
ASSERT_EQ(mkdir("/tmp/B", 0777), 0);
ASSERT_EQ(mknodat(-EBADF, "/tmp/B/b", S_IFREG | 0644, 0), 0);
ASSERT_EQ(chown("/tmp/B/b", 0, 0), 0);
ASSERT_EQ(mount("testing", "/tmp/B", "tmpfs", MS_NOATIME | MS_NODEV,
"size=100000,mode=700"), 0);
ASSERT_EQ(mkdir("/tmp/B/BB", 0777), 0);
ASSERT_EQ(mknodat(-EBADF, "/tmp/B/BB/b", S_IFREG | 0644, 0), 0);
ASSERT_EQ(chown("/tmp/B/BB/b", 0, 0), 0);
ASSERT_EQ(mount("testing", "/tmp/B/BB", "tmpfs", MS_NOATIME | MS_NODEV,
"size=100000,mode=700"), 0);
ASSERT_EQ(mount("testing", "/mnt", "tmpfs", MS_NOATIME | MS_NODEV,
"size=100000,mode=700"), 0);
ASSERT_EQ(mkdir("/mnt/A", 0777), 0);
ASSERT_EQ(mount("testing", "/mnt/A", "tmpfs", MS_NOATIME | MS_NODEV,
"size=100000,mode=700"), 0);
ASSERT_EQ(mkdir("/mnt/A/AA", 0777), 0);
ASSERT_EQ(mount("/tmp", "/mnt/A/AA", NULL, MS_BIND | MS_REC, NULL), 0);
ASSERT_EQ(mkdir("/mnt/B", 0777), 0);
ASSERT_EQ(mount("testing", "/mnt/B", "ramfs",
MS_NOATIME | MS_NODEV | MS_NOSUID, 0), 0);
ASSERT_EQ(mkdir("/mnt/B/BB", 0777), 0);
ASSERT_EQ(mount("testing", "/tmp/B/BB", "devpts",
MS_RELATIME | MS_NOEXEC | MS_RDONLY, 0), 0);
ASSERT_EQ(mkdir("/mnt/C", 0777), 0);
ASSERT_EQ(mkdir("/mnt/D", 0777), 0);
img_fd = openat(-EBADF, "/mnt/C/ext4.img", O_CREAT | O_WRONLY, 0600);
ASSERT_GE(img_fd, 0);
ASSERT_EQ(ftruncate(img_fd, 1024 * 2048), 0);
ASSERT_EQ(system("mkfs.ext4 -q /mnt/C/ext4.img"), 0);
ASSERT_EQ(system("mount -o loop -t ext4 /mnt/C/ext4.img /mnt/D/"), 0);
ASSERT_EQ(close(img_fd), 0);
}
FIXTURE_TEARDOWN(mount_setattr_idmapped)
{
(void)umount2("/mnt/A", MNT_DETACH);
(void)umount2("/tmp", MNT_DETACH);
}
/**
* Validate that negative fd values are rejected.
*/
TEST_F(mount_setattr_idmapped, invalid_fd_negative)
{
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
.userns_fd = -EBADF,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
ASSERT_NE(sys_mount_setattr(-1, "/", 0, &attr, sizeof(attr)), 0) {
TH_LOG("failure: created idmapped mount with negative fd");
}
}
/**
* Validate that excessively large fd values are rejected.
*/
TEST_F(mount_setattr_idmapped, invalid_fd_large)
{
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
.userns_fd = INT64_MAX,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
ASSERT_NE(sys_mount_setattr(-1, "/", 0, &attr, sizeof(attr)), 0) {
TH_LOG("failure: created idmapped mount with too large fd value");
}
}
/**
* Validate that closed fd values are rejected.
*/
TEST_F(mount_setattr_idmapped, invalid_fd_closed)
{
int fd;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
ASSERT_GE(fd, 0);
ASSERT_GE(close(fd), 0);
attr.userns_fd = fd;
ASSERT_NE(sys_mount_setattr(-1, "/", 0, &attr, sizeof(attr)), 0) {
TH_LOG("failure: created idmapped mount with closed fd");
}
}
/**
* Validate that the initial user namespace is rejected.
*/
TEST_F(mount_setattr_idmapped, invalid_fd_initial_userns)
{
int open_tree_fd = -EBADF;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
open_tree_fd = sys_open_tree(-EBADF, "/mnt/D",
AT_NO_AUTOMOUNT |
AT_SYMLINK_NOFOLLOW |
OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE);
ASSERT_GE(open_tree_fd, 0);
attr.userns_fd = open("/proc/1/ns/user", O_RDONLY | O_CLOEXEC);
ASSERT_GE(attr.userns_fd, 0);
ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
ASSERT_EQ(errno, EPERM);
ASSERT_EQ(close(attr.userns_fd), 0);
ASSERT_EQ(close(open_tree_fd), 0);
}
static int map_ids(pid_t pid, unsigned long nsid, unsigned long hostid,
unsigned long range)
{
char map[100], procfile[256];
snprintf(procfile, sizeof(procfile), "/proc/%d/uid_map", pid);
snprintf(map, sizeof(map), "%lu %lu %lu", nsid, hostid, range);
if (write_file(procfile, map, strlen(map)))
return -1;
snprintf(procfile, sizeof(procfile), "/proc/%d/gid_map", pid);
snprintf(map, sizeof(map), "%lu %lu %lu", nsid, hostid, range);
if (write_file(procfile, map, strlen(map)))
return -1;
return 0;
}
#define __STACK_SIZE (8 * 1024 * 1024)
static pid_t do_clone(int (*fn)(void *), void *arg, int flags)
{
void *stack;
stack = malloc(__STACK_SIZE);
if (!stack)
return -ENOMEM;
#ifdef __ia64__
return __clone2(fn, stack, __STACK_SIZE, flags | SIGCHLD, arg, NULL);
#else
return clone(fn, stack + __STACK_SIZE, flags | SIGCHLD, arg, NULL);
#endif
}
static int get_userns_fd_cb(void *data)
{
return kill(getpid(), SIGSTOP);
}
static int wait_for_pid(pid_t pid)
{
int status, ret;
again:
ret = waitpid(pid, &status, 0);
if (ret == -1) {
if (errno == EINTR)
goto again;
return -1;
}
if (!WIFEXITED(status))
return -1;
return WEXITSTATUS(status);
}
static int get_userns_fd(unsigned long nsid, unsigned long hostid, unsigned long range)
{
int ret;
pid_t pid;
char path[256];
pid = do_clone(get_userns_fd_cb, NULL, CLONE_NEWUSER);
if (pid < 0)
return -errno;
ret = map_ids(pid, nsid, hostid, range);
if (ret < 0)
return ret;
snprintf(path, sizeof(path), "/proc/%d/ns/user", pid);
ret = open(path, O_RDONLY | O_CLOEXEC);
kill(pid, SIGKILL);
wait_for_pid(pid);
return ret;
}
/**
* Validate that an attached mount in our mount namespace cannot be idmapped.
* (The kernel enforces that the mount's mount namespace and the caller's mount
* namespace match.)
*/
TEST_F(mount_setattr_idmapped, attached_mount_inside_current_mount_namespace)
{
int open_tree_fd = -EBADF;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
open_tree_fd = sys_open_tree(-EBADF, "/mnt/D",
AT_EMPTY_PATH |
AT_NO_AUTOMOUNT |
AT_SYMLINK_NOFOLLOW |
OPEN_TREE_CLOEXEC);
ASSERT_GE(open_tree_fd, 0);
attr.userns_fd = get_userns_fd(0, 10000, 10000);
ASSERT_GE(attr.userns_fd, 0);
ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
ASSERT_EQ(close(attr.userns_fd), 0);
ASSERT_EQ(close(open_tree_fd), 0);
}
/**
* Validate that idmapping a mount is rejected if the mount's mount namespace
* and our mount namespace don't match.
* (The kernel enforces that the mount's mount namespace and the caller's mount
* namespace match.)
*/
TEST_F(mount_setattr_idmapped, attached_mount_outside_current_mount_namespace)
{
int open_tree_fd = -EBADF;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
open_tree_fd = sys_open_tree(-EBADF, "/mnt/D",
AT_EMPTY_PATH |
AT_NO_AUTOMOUNT |
AT_SYMLINK_NOFOLLOW |
OPEN_TREE_CLOEXEC);
ASSERT_GE(open_tree_fd, 0);
ASSERT_EQ(unshare(CLONE_NEWNS), 0);
attr.userns_fd = get_userns_fd(0, 10000, 10000);
ASSERT_GE(attr.userns_fd, 0);
ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr,
sizeof(attr)), 0);
ASSERT_EQ(close(attr.userns_fd), 0);
ASSERT_EQ(close(open_tree_fd), 0);
}
/**
* Validate that an attached mount in our mount namespace can be idmapped.
*/
TEST_F(mount_setattr_idmapped, detached_mount_inside_current_mount_namespace)
{
int open_tree_fd = -EBADF;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
open_tree_fd = sys_open_tree(-EBADF, "/mnt/D",
AT_EMPTY_PATH |
AT_NO_AUTOMOUNT |
AT_SYMLINK_NOFOLLOW |
OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(open_tree_fd, 0);
/* Changing mount properties on a detached mount. */
attr.userns_fd = get_userns_fd(0, 10000, 10000);
ASSERT_GE(attr.userns_fd, 0);
ASSERT_EQ(sys_mount_setattr(open_tree_fd, "",
AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
ASSERT_EQ(close(attr.userns_fd), 0);
ASSERT_EQ(close(open_tree_fd), 0);
}
/**
* Validate that a detached mount not in our mount namespace can be idmapped.
*/
TEST_F(mount_setattr_idmapped, detached_mount_outside_current_mount_namespace)
{
int open_tree_fd = -EBADF;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
open_tree_fd = sys_open_tree(-EBADF, "/mnt/D",
AT_EMPTY_PATH |
AT_NO_AUTOMOUNT |
AT_SYMLINK_NOFOLLOW |
OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(open_tree_fd, 0);
ASSERT_EQ(unshare(CLONE_NEWNS), 0);
/* Changing mount properties on a detached mount. */
attr.userns_fd = get_userns_fd(0, 10000, 10000);
ASSERT_GE(attr.userns_fd, 0);
ASSERT_EQ(sys_mount_setattr(open_tree_fd, "",
AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
ASSERT_EQ(close(attr.userns_fd), 0);
ASSERT_EQ(close(open_tree_fd), 0);
}
/**
* Validate that currently changing the idmapping of an idmapped mount fails.
*/
TEST_F(mount_setattr_idmapped, change_idmapping)
{
int open_tree_fd = -EBADF;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
open_tree_fd = sys_open_tree(-EBADF, "/mnt/D",
AT_EMPTY_PATH |
AT_NO_AUTOMOUNT |
AT_SYMLINK_NOFOLLOW |
OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(open_tree_fd, 0);
attr.userns_fd = get_userns_fd(0, 10000, 10000);
ASSERT_GE(attr.userns_fd, 0);
ASSERT_EQ(sys_mount_setattr(open_tree_fd, "",
AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
ASSERT_EQ(close(attr.userns_fd), 0);
/* Change idmapping on a detached mount that is already idmapped. */
attr.userns_fd = get_userns_fd(0, 20000, 10000);
ASSERT_GE(attr.userns_fd, 0);
ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
ASSERT_EQ(close(attr.userns_fd), 0);
ASSERT_EQ(close(open_tree_fd), 0);
}
static bool expected_uid_gid(int dfd, const char *path, int flags,
uid_t expected_uid, gid_t expected_gid)
{
int ret;
struct stat st;
ret = fstatat(dfd, path, &st, flags);
if (ret < 0)
return false;
return st.st_uid == expected_uid && st.st_gid == expected_gid;
}
TEST_F(mount_setattr_idmapped, idmap_mount_tree_invalid)
{
int open_tree_fd = -EBADF;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
ASSERT_EQ(expected_uid_gid(-EBADF, "/tmp/B/b", 0, 0, 0), 0);
ASSERT_EQ(expected_uid_gid(-EBADF, "/tmp/B/BB/b", 0, 0, 0), 0);
open_tree_fd = sys_open_tree(-EBADF, "/mnt/A",
AT_RECURSIVE |
AT_EMPTY_PATH |
AT_NO_AUTOMOUNT |
AT_SYMLINK_NOFOLLOW |
OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(open_tree_fd, 0);
attr.userns_fd = get_userns_fd(0, 10000, 10000);
ASSERT_GE(attr.userns_fd, 0);
ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
ASSERT_EQ(close(attr.userns_fd), 0);
ASSERT_EQ(close(open_tree_fd), 0);
ASSERT_EQ(expected_uid_gid(-EBADF, "/tmp/B/b", 0, 0, 0), 0);
ASSERT_EQ(expected_uid_gid(-EBADF, "/tmp/B/BB/b", 0, 0, 0), 0);
ASSERT_EQ(expected_uid_gid(open_tree_fd, "B/b", 0, 0, 0), 0);
ASSERT_EQ(expected_uid_gid(open_tree_fd, "B/BB/b", 0, 0, 0), 0);
}
TEST_F(mount_setattr, mount_attr_nosymfollow)
{
int fd;
unsigned int old_flags = 0, new_flags = 0, expected_flags = 0;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_NOSYMFOLLOW,
};
if (!mount_setattr_supported())
SKIP(return, "mount_setattr syscall not supported");
fd = open(NOSYMFOLLOW_SYMLINK, O_RDWR | O_CLOEXEC);
ASSERT_GT(fd, 0);
ASSERT_EQ(close(fd), 0);
old_flags = read_mnt_flags("/mnt/A");
ASSERT_GT(old_flags, 0);
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
expected_flags = old_flags;
expected_flags |= ST_NOSYMFOLLOW;
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, expected_flags);
fd = open(NOSYMFOLLOW_SYMLINK, O_RDWR | O_CLOEXEC);
ASSERT_LT(fd, 0);
ASSERT_EQ(errno, ELOOP);
attr.attr_set &= ~MOUNT_ATTR_NOSYMFOLLOW;
attr.attr_clr |= MOUNT_ATTR_NOSYMFOLLOW;
ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
expected_flags &= ~ST_NOSYMFOLLOW;
new_flags = read_mnt_flags("/mnt/A");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B");
ASSERT_EQ(new_flags, expected_flags);
new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
ASSERT_EQ(new_flags, expected_flags);
fd = open(NOSYMFOLLOW_SYMLINK, O_RDWR | O_CLOEXEC);
ASSERT_GT(fd, 0);
ASSERT_EQ(close(fd), 0);
}
TEST_HARNESS_MAIN