1 // SPDX-License-Identifier: GPL-2.0
2 #define _GNU_SOURCE
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <sched.h>
7 #include <stdarg.h>
8 #include <stdbool.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <sys/mount.h>
13 #include <sys/stat.h>
14 #include <sys/types.h>
15 #include <sys/vfs.h>
16 #include <unistd.h>
17 
18 #ifndef MS_NOSYMFOLLOW
19 # define MS_NOSYMFOLLOW 256     /* Do not follow symlinks */
20 #endif
21 
22 #ifndef ST_NOSYMFOLLOW
23 # define ST_NOSYMFOLLOW 0x2000  /* Do not follow symlinks */
24 #endif
25 
26 #define DATA "/tmp/data"
27 #define LINK "/tmp/symlink"
28 #define TMP  "/tmp"
29 
30 static void die(char *fmt, ...)
31 {
32 	va_list ap;
33 
34 	va_start(ap, fmt);
35 	vfprintf(stderr, fmt, ap);
36 	va_end(ap);
37 	exit(EXIT_FAILURE);
38 }
39 
40 static void vmaybe_write_file(bool enoent_ok, char *filename, char *fmt,
41 		va_list ap)
42 {
43 	ssize_t written;
44 	char buf[4096];
45 	int buf_len;
46 	int fd;
47 
48 	buf_len = vsnprintf(buf, sizeof(buf), fmt, ap);
49 	if (buf_len < 0)
50 		die("vsnprintf failed: %s\n", strerror(errno));
51 
52 	if (buf_len >= sizeof(buf))
53 		die("vsnprintf output truncated\n");
54 
55 	fd = open(filename, O_WRONLY);
56 	if (fd < 0) {
57 		if ((errno == ENOENT) && enoent_ok)
58 			return;
59 		die("open of %s failed: %s\n", filename, strerror(errno));
60 	}
61 
62 	written = write(fd, buf, buf_len);
63 	if (written != buf_len) {
64 		if (written >= 0) {
65 			die("short write to %s\n", filename);
66 		} else {
67 			die("write to %s failed: %s\n",
68 				filename, strerror(errno));
69 		}
70 	}
71 
72 	if (close(fd) != 0)
73 		die("close of %s failed: %s\n", filename, strerror(errno));
74 }
75 
76 static void maybe_write_file(char *filename, char *fmt, ...)
77 {
78 	va_list ap;
79 
80 	va_start(ap, fmt);
81 	vmaybe_write_file(true, filename, fmt, ap);
82 	va_end(ap);
83 }
84 
85 static void write_file(char *filename, char *fmt, ...)
86 {
87 	va_list ap;
88 
89 	va_start(ap, fmt);
90 	vmaybe_write_file(false, filename, fmt, ap);
91 	va_end(ap);
92 }
93 
94 static void create_and_enter_ns(void)
95 {
96 	uid_t uid = getuid();
97 	gid_t gid = getgid();
98 
99 	if (unshare(CLONE_NEWUSER) != 0)
100 		die("unshare(CLONE_NEWUSER) failed: %s\n", strerror(errno));
101 
102 	maybe_write_file("/proc/self/setgroups", "deny");
103 	write_file("/proc/self/uid_map", "0 %d 1", uid);
104 	write_file("/proc/self/gid_map", "0 %d 1", gid);
105 
106 	if (setgid(0) != 0)
107 		die("setgid(0) failed %s\n", strerror(errno));
108 	if (setuid(0) != 0)
109 		die("setuid(0) failed %s\n", strerror(errno));
110 
111 	if (unshare(CLONE_NEWNS) != 0)
112 		die("unshare(CLONE_NEWNS) failed: %s\n", strerror(errno));
113 }
114 
115 static void setup_symlink(void)
116 {
117 	int data, err;
118 
119 	data = creat(DATA, O_RDWR);
120 	if (data < 0)
121 		die("creat failed: %s\n", strerror(errno));
122 
123 	err = symlink(DATA, LINK);
124 	if (err < 0)
125 		die("symlink failed: %s\n", strerror(errno));
126 
127 	if (close(data) != 0)
128 		die("close of %s failed: %s\n", DATA, strerror(errno));
129 }
130 
131 static void test_link_traversal(bool nosymfollow)
132 {
133 	int link;
134 
135 	link = open(LINK, 0, O_RDWR);
136 	if (nosymfollow) {
137 		if ((link != -1 || errno != ELOOP)) {
138 			die("link traversal unexpected result: %d, %s\n",
139 					link, strerror(errno));
140 		}
141 	} else {
142 		if (link < 0)
143 			die("link traversal failed: %s\n", strerror(errno));
144 
145 		if (close(link) != 0)
146 			die("close of link failed: %s\n", strerror(errno));
147 	}
148 }
149 
150 static void test_readlink(void)
151 {
152 	char buf[4096];
153 	ssize_t ret;
154 
155 	bzero(buf, sizeof(buf));
156 
157 	ret = readlink(LINK, buf, sizeof(buf));
158 	if (ret < 0)
159 		die("readlink failed: %s\n", strerror(errno));
160 	if (strcmp(buf, DATA) != 0)
161 		die("readlink strcmp failed: '%s' '%s'\n", buf, DATA);
162 }
163 
164 static void test_realpath(void)
165 {
166 	char *path = realpath(LINK, NULL);
167 
168 	if (!path)
169 		die("realpath failed: %s\n", strerror(errno));
170 	if (strcmp(path, DATA) != 0)
171 		die("realpath strcmp failed\n");
172 
173 	free(path);
174 }
175 
176 static void test_statfs(bool nosymfollow)
177 {
178 	struct statfs buf;
179 	int ret;
180 
181 	ret = statfs(TMP, &buf);
182 	if (ret)
183 		die("statfs failed: %s\n", strerror(errno));
184 
185 	if (nosymfollow) {
186 		if ((buf.f_flags & ST_NOSYMFOLLOW) == 0)
187 			die("ST_NOSYMFOLLOW not set on %s\n", TMP);
188 	} else {
189 		if ((buf.f_flags & ST_NOSYMFOLLOW) != 0)
190 			die("ST_NOSYMFOLLOW set on %s\n", TMP);
191 	}
192 }
193 
194 static void run_tests(bool nosymfollow)
195 {
196 	test_link_traversal(nosymfollow);
197 	test_readlink();
198 	test_realpath();
199 	test_statfs(nosymfollow);
200 }
201 
202 int main(int argc, char **argv)
203 {
204 	create_and_enter_ns();
205 
206 	if (mount("testing", TMP, "ramfs", 0, NULL) != 0)
207 		die("mount failed: %s\n", strerror(errno));
208 
209 	setup_symlink();
210 	run_tests(false);
211 
212 	if (mount("testing", TMP, "ramfs", MS_REMOUNT|MS_NOSYMFOLLOW, NULL) != 0)
213 		die("remount failed: %s\n", strerror(errno));
214 
215 	run_tests(true);
216 
217 	return EXIT_SUCCESS;
218 }
219