1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * F_SET_RW_HINT is refused on an immutable inode. Nothing is ever written
4 * to it and it may be shared with everybody, like a namespace file or the
5 * root of an empty mount namespace.
6 */
7 #define _GNU_SOURCE
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <sched.h>
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <unistd.h>
16 #include <sys/ioctl.h>
17 #include <sys/stat.h>
18 #include <sys/wait.h>
19
20 #include "../kselftest_harness.h"
21
22 /* <linux/fs.h> and <linux/fcntl.h> don't mix with the libc headers */
23 #ifndef FS_IOC_GETFLAGS
24 #define FS_IOC_GETFLAGS _IOR('f', 1, long)
25 #define FS_IOC_SETFLAGS _IOW('f', 2, long)
26 #endif
27 #ifndef FS_IMMUTABLE_FL
28 #define FS_IMMUTABLE_FL 0x00000010
29 #endif
30 #ifndef F_LINUX_SPECIFIC_BASE
31 #define F_LINUX_SPECIFIC_BASE 1024
32 #endif
33 #ifndef F_GET_RW_HINT
34 #define F_GET_RW_HINT (F_LINUX_SPECIFIC_BASE + 11)
35 #define F_SET_RW_HINT (F_LINUX_SPECIFIC_BASE + 12)
36 #endif
37 #ifndef RWH_WRITE_LIFE_SHORT
38 #define RWH_WRITE_LIFE_SHORT 2
39 #endif
40 #ifndef UNSHARE_EMPTY_MNTNS
41 #define UNSHARE_EMPTY_MNTNS 0x00100000
42 #endif
43
set_hint(int fd,uint64_t hint)44 static int set_hint(int fd, uint64_t hint)
45 {
46 return fcntl(fd, F_SET_RW_HINT, &hint);
47 }
48
get_hint(int fd)49 static long get_hint(int fd)
50 {
51 uint64_t hint;
52
53 if (fcntl(fd, F_GET_RW_HINT, &hint))
54 return -1;
55 return hint;
56 }
57
TEST(immutable_file)58 TEST(immutable_file)
59 {
60 char path[] = "/tmp/rw_hint.XXXXXX";
61 int fd, flags;
62
63 if (geteuid())
64 SKIP(return, "test requires root");
65
66 fd = mkstemp(path);
67 ASSERT_GE(fd, 0);
68 unlink(path);
69 ASSERT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), 0);
70 EXPECT_EQ(get_hint(fd), RWH_WRITE_LIFE_SHORT);
71
72 if (ioctl(fd, FS_IOC_GETFLAGS, &flags)) {
73 close(fd);
74 SKIP(return, "no file attributes on this filesystem");
75 }
76 flags |= FS_IMMUTABLE_FL;
77 ASSERT_EQ(ioctl(fd, FS_IOC_SETFLAGS, &flags), 0);
78 EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), -1);
79 EXPECT_EQ(errno, EPERM);
80 flags &= ~FS_IMMUTABLE_FL;
81 ASSERT_EQ(ioctl(fd, FS_IOC_SETFLAGS, &flags), 0);
82 EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), 0);
83 close(fd);
84 }
85
TEST(namespace_file)86 TEST(namespace_file)
87 {
88 int fd;
89
90 if (geteuid())
91 SKIP(return, "test requires root");
92
93 fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
94 ASSERT_GE(fd, 0);
95 EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), -1);
96 EXPECT_EQ(errno, EPERM);
97 close(fd);
98 }
99
TEST(empty_mntns_root)100 TEST(empty_mntns_root)
101 {
102 int status;
103 pid_t pid;
104
105 if (geteuid())
106 SKIP(return, "test requires root");
107
108 pid = fork();
109 ASSERT_GE(pid, 0);
110 if (pid == 0) {
111 int fd;
112
113 if (unshare(UNSHARE_EMPTY_MNTNS))
114 _exit(errno == EINVAL ? 100 : 1);
115 fd = open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
116 if (fd < 0)
117 _exit(2);
118 if (set_hint(fd, RWH_WRITE_LIFE_SHORT) == 0)
119 _exit(3);
120 _exit(errno == EPERM ? 0 : 4);
121 }
122 ASSERT_EQ(waitpid(pid, &status, 0), pid);
123 ASSERT_TRUE(WIFEXITED(status));
124 if (WEXITSTATUS(status) == 100)
125 SKIP(return, "UNSHARE_EMPTY_MNTNS not supported");
126 EXPECT_EQ(WEXITSTATUS(status), 0);
127 }
128
129 TEST_HARNESS_MAIN
130