1501 lines
38 KiB
C
1501 lines
38 KiB
C
// 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
|