1 // SPDX-License-Identifier: GPL-2.0
2 #define _GNU_SOURCE
3 #include <stdio.h>
4 #include <errno.h>
5 #include <pwd.h>
6 #include <grp.h>
7 #include <string.h>
8 #include <syscall.h>
9 #include <sys/capability.h>
10 #include <sys/types.h>
11 #include <sys/mount.h>
12 #include <sys/prctl.h>
13 #include <sys/wait.h>
14 #include <stdlib.h>
15 #include <unistd.h>
16 #include <fcntl.h>
17 #include <stdbool.h>
18 #include <stdarg.h>
19
20 /*
21 * NOTES about this test:
22 * - requries libcap-dev to be installed on test system
23 * - requires securityfs to me mounted at /sys/kernel/security, e.g.:
24 * mount -n -t securityfs -o nodev,noexec,nosuid securityfs /sys/kernel/security
25 * - needs CONFIG_SECURITYFS and CONFIG_SAFESETID to be enabled
26 */
27
28 #ifndef CLONE_NEWUSER
29 # define CLONE_NEWUSER 0x10000000
30 #endif
31
32 #define ROOT_UGID 0
33 #define RESTRICTED_PARENT_UGID 1
34 #define ALLOWED_CHILD1_UGID 2
35 #define ALLOWED_CHILD2_UGID 3
36 #define NO_POLICY_UGID 4
37
38 #define UGID_POLICY_STRING "1:2\n1:3\n2:2\n3:3\n"
39
40 char* add_uid_whitelist_policy_file = "/sys/kernel/security/safesetid/uid_allowlist_policy";
41 char* add_gid_whitelist_policy_file = "/sys/kernel/security/safesetid/gid_allowlist_policy";
42
die(char * fmt,...)43 static void die(char *fmt, ...)
44 {
45 va_list ap;
46 va_start(ap, fmt);
47 vfprintf(stderr, fmt, ap);
48 va_end(ap);
49 exit(EXIT_FAILURE);
50 }
51
vmaybe_write_file(bool enoent_ok,char * filename,char * fmt,va_list ap)52 static bool vmaybe_write_file(bool enoent_ok, char *filename, char *fmt, va_list ap)
53 {
54 char buf[4096];
55 int fd;
56 ssize_t written;
57 int buf_len;
58
59 buf_len = vsnprintf(buf, sizeof(buf), fmt, ap);
60 if (buf_len < 0) {
61 printf("vsnprintf failed: %s\n",
62 strerror(errno));
63 return false;
64 }
65 if (buf_len >= sizeof(buf)) {
66 printf("vsnprintf output truncated\n");
67 return false;
68 }
69
70 fd = open(filename, O_WRONLY);
71 if (fd < 0) {
72 if ((errno == ENOENT) && enoent_ok)
73 return true;
74 return false;
75 }
76 written = write(fd, buf, buf_len);
77 if (written != buf_len) {
78 if (written >= 0) {
79 printf("short write to %s\n", filename);
80 return false;
81 } else {
82 printf("write to %s failed: %s\n",
83 filename, strerror(errno));
84 return false;
85 }
86 }
87 if (close(fd) != 0) {
88 printf("close of %s failed: %s\n",
89 filename, strerror(errno));
90 return false;
91 }
92 return true;
93 }
94
write_file(char * filename,char * fmt,...)95 static bool write_file(char *filename, char *fmt, ...)
96 {
97 va_list ap;
98 bool ret;
99
100 va_start(ap, fmt);
101 ret = vmaybe_write_file(false, filename, fmt, ap);
102 va_end(ap);
103
104 return ret;
105 }
106
ensure_user_exists(uid_t uid)107 static void ensure_user_exists(uid_t uid)
108 {
109 struct passwd p;
110
111 FILE *fd;
112 char name_str[10];
113
114 if (getpwuid(uid) == NULL) {
115 memset(&p,0x00,sizeof(p));
116 fd=fopen("/etc/passwd","a");
117 if (fd == NULL)
118 die("couldn't open file\n");
119 if (fseek(fd, 0, SEEK_END))
120 die("couldn't fseek\n");
121 snprintf(name_str, 10, "user %d", uid);
122 p.pw_name=name_str;
123 p.pw_uid=uid;
124 p.pw_gid=uid;
125 p.pw_gecos="Test account";
126 p.pw_dir="/dev/null";
127 p.pw_shell="/bin/false";
128 int value = putpwent(&p,fd);
129 if (value != 0)
130 die("putpwent failed\n");
131 if (fclose(fd))
132 die("fclose failed\n");
133 }
134 }
135
ensure_group_exists(gid_t gid)136 static void ensure_group_exists(gid_t gid)
137 {
138 struct group g;
139
140 FILE *fd;
141 char name_str[10];
142
143 if (getgrgid(gid) == NULL) {
144 memset(&g,0x00,sizeof(g));
145 fd=fopen("/etc/group","a");
146 if (fd == NULL)
147 die("couldn't open group file\n");
148 if (fseek(fd, 0, SEEK_END))
149 die("couldn't fseek group file\n");
150 snprintf(name_str, 10, "group %d", gid);
151 g.gr_name=name_str;
152 g.gr_gid=gid;
153 g.gr_passwd=NULL;
154 g.gr_mem=NULL;
155 int value = putgrent(&g,fd);
156 if (value != 0)
157 die("putgrent failed\n");
158 if (fclose(fd))
159 die("fclose failed\n");
160 }
161 }
162
ensure_securityfs_mounted(void)163 static void ensure_securityfs_mounted(void)
164 {
165 int fd = open(add_uid_whitelist_policy_file, O_WRONLY);
166 if (fd < 0) {
167 if (errno == ENOENT) {
168 // Need to mount securityfs
169 if (mount("securityfs", "/sys/kernel/security",
170 "securityfs", 0, NULL) < 0)
171 die("mounting securityfs failed\n");
172 } else {
173 die("couldn't find securityfs for unknown reason\n");
174 }
175 } else {
176 if (close(fd) != 0) {
177 die("close of %s failed: %s\n",
178 add_uid_whitelist_policy_file, strerror(errno));
179 }
180 }
181 }
182
write_uid_policies()183 static void write_uid_policies()
184 {
185 static char *policy_str = UGID_POLICY_STRING;
186 ssize_t written;
187 int fd;
188
189 fd = open(add_uid_whitelist_policy_file, O_WRONLY);
190 if (fd < 0)
191 die("can't open add_uid_whitelist_policy file\n");
192 written = write(fd, policy_str, strlen(policy_str));
193 if (written != strlen(policy_str)) {
194 if (written >= 0) {
195 die("short write to %s\n", add_uid_whitelist_policy_file);
196 } else {
197 die("write to %s failed: %s\n",
198 add_uid_whitelist_policy_file, strerror(errno));
199 }
200 }
201 if (close(fd) != 0) {
202 die("close of %s failed: %s\n",
203 add_uid_whitelist_policy_file, strerror(errno));
204 }
205 }
206
write_gid_policies()207 static void write_gid_policies()
208 {
209 static char *policy_str = UGID_POLICY_STRING;
210 ssize_t written;
211 int fd;
212
213 fd = open(add_gid_whitelist_policy_file, O_WRONLY);
214 if (fd < 0)
215 die("can't open add_gid_whitelist_policy file\n");
216 written = write(fd, policy_str, strlen(policy_str));
217 if (written != strlen(policy_str)) {
218 if (written >= 0) {
219 die("short write to %s\n", add_gid_whitelist_policy_file);
220 } else {
221 die("write to %s failed: %s\n",
222 add_gid_whitelist_policy_file, strerror(errno));
223 }
224 }
225 if (close(fd) != 0) {
226 die("close of %s failed: %s\n",
227 add_gid_whitelist_policy_file, strerror(errno));
228 }
229 }
230
231
test_userns(bool expect_success)232 static bool test_userns(bool expect_success)
233 {
234 uid_t uid;
235 char map_file_name[32];
236 size_t sz = sizeof(map_file_name);
237 pid_t cpid;
238 bool success;
239
240 uid = getuid();
241
242 int clone_flags = CLONE_NEWUSER;
243 cpid = syscall(SYS_clone, clone_flags, NULL);
244 if (cpid == -1) {
245 printf("clone failed");
246 return false;
247 }
248
249 if (cpid == 0) { /* Code executed by child */
250 // Give parent 1 second to write map file
251 sleep(1);
252 exit(EXIT_SUCCESS);
253 } else { /* Code executed by parent */
254 if(snprintf(map_file_name, sz, "/proc/%d/uid_map", cpid) < 0) {
255 printf("preparing file name string failed");
256 return false;
257 }
258 success = write_file(map_file_name, "0 %d 1", uid);
259 return success == expect_success;
260 }
261
262 printf("should not reach here");
263 return false;
264 }
265
test_setuid(uid_t child_uid,bool expect_success)266 static void test_setuid(uid_t child_uid, bool expect_success)
267 {
268 pid_t cpid, w;
269 int wstatus;
270
271 cpid = fork();
272 if (cpid == -1) {
273 die("fork\n");
274 }
275
276 if (cpid == 0) { /* Code executed by child */
277 if (setuid(child_uid) < 0)
278 exit(EXIT_FAILURE);
279 if (getuid() == child_uid)
280 exit(EXIT_SUCCESS);
281 else
282 exit(EXIT_FAILURE);
283 } else { /* Code executed by parent */
284 do {
285 w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED);
286 if (w == -1) {
287 die("waitpid\n");
288 }
289
290 if (WIFEXITED(wstatus)) {
291 if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
292 if (expect_success) {
293 return;
294 } else {
295 die("unexpected success\n");
296 }
297 } else {
298 if (expect_success) {
299 die("unexpected failure\n");
300 } else {
301 return;
302 }
303 }
304 } else if (WIFSIGNALED(wstatus)) {
305 if (WTERMSIG(wstatus) == 9) {
306 if (expect_success)
307 die("killed unexpectedly\n");
308 else
309 return;
310 } else {
311 die("unexpected signal: %d\n", wstatus);
312 }
313 } else {
314 die("unexpected status: %d\n", wstatus);
315 }
316 } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
317 }
318
319 die("should not reach here\n");
320 }
321
test_setgid(gid_t child_gid,bool expect_success)322 static void test_setgid(gid_t child_gid, bool expect_success)
323 {
324 pid_t cpid, w;
325 int wstatus;
326
327 cpid = fork();
328 if (cpid == -1) {
329 die("fork\n");
330 }
331
332 if (cpid == 0) { /* Code executed by child */
333 if (setgid(child_gid) < 0)
334 exit(EXIT_FAILURE);
335 if (getgid() == child_gid)
336 exit(EXIT_SUCCESS);
337 else
338 exit(EXIT_FAILURE);
339 } else { /* Code executed by parent */
340 do {
341 w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED);
342 if (w == -1) {
343 die("waitpid\n");
344 }
345
346 if (WIFEXITED(wstatus)) {
347 if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
348 if (expect_success) {
349 return;
350 } else {
351 die("unexpected success\n");
352 }
353 } else {
354 if (expect_success) {
355 die("unexpected failure\n");
356 } else {
357 return;
358 }
359 }
360 } else if (WIFSIGNALED(wstatus)) {
361 if (WTERMSIG(wstatus) == 9) {
362 if (expect_success)
363 die("killed unexpectedly\n");
364 else
365 return;
366 } else {
367 die("unexpected signal: %d\n", wstatus);
368 }
369 } else {
370 die("unexpected status: %d\n", wstatus);
371 }
372 } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
373 }
374
375 die("should not reach here\n");
376 }
377
test_setgroups(gid_t * child_groups,size_t len,bool expect_success)378 static void test_setgroups(gid_t* child_groups, size_t len, bool expect_success)
379 {
380 pid_t cpid, w;
381 int wstatus;
382 gid_t groupset[len];
383 int i, j;
384
385 cpid = fork();
386 if (cpid == -1) {
387 die("fork\n");
388 }
389
390 if (cpid == 0) { /* Code executed by child */
391 if (setgroups(len, child_groups) != 0)
392 exit(EXIT_FAILURE);
393 if (getgroups(len, groupset) != len)
394 exit(EXIT_FAILURE);
395 for (i = 0; i < len; i++) {
396 for (j = 0; j < len; j++) {
397 if (child_groups[i] == groupset[j])
398 break;
399 if (j == len - 1)
400 exit(EXIT_FAILURE);
401 }
402 }
403 exit(EXIT_SUCCESS);
404 } else { /* Code executed by parent */
405 do {
406 w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED);
407 if (w == -1) {
408 die("waitpid\n");
409 }
410
411 if (WIFEXITED(wstatus)) {
412 if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
413 if (expect_success) {
414 return;
415 } else {
416 die("unexpected success\n");
417 }
418 } else {
419 if (expect_success) {
420 die("unexpected failure\n");
421 } else {
422 return;
423 }
424 }
425 } else if (WIFSIGNALED(wstatus)) {
426 if (WTERMSIG(wstatus) == 9) {
427 if (expect_success)
428 die("killed unexpectedly\n");
429 else
430 return;
431 } else {
432 die("unexpected signal: %d\n", wstatus);
433 }
434 } else {
435 die("unexpected status: %d\n", wstatus);
436 }
437 } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
438 }
439
440 die("should not reach here\n");
441 }
442
443
ensure_users_exist(void)444 static void ensure_users_exist(void)
445 {
446 ensure_user_exists(ROOT_UGID);
447 ensure_user_exists(RESTRICTED_PARENT_UGID);
448 ensure_user_exists(ALLOWED_CHILD1_UGID);
449 ensure_user_exists(ALLOWED_CHILD2_UGID);
450 ensure_user_exists(NO_POLICY_UGID);
451 }
452
ensure_groups_exist(void)453 static void ensure_groups_exist(void)
454 {
455 ensure_group_exists(ROOT_UGID);
456 ensure_group_exists(RESTRICTED_PARENT_UGID);
457 ensure_group_exists(ALLOWED_CHILD1_UGID);
458 ensure_group_exists(ALLOWED_CHILD2_UGID);
459 ensure_group_exists(NO_POLICY_UGID);
460 }
461
drop_caps(bool setid_retained)462 static void drop_caps(bool setid_retained)
463 {
464 cap_value_t cap_values[] = {CAP_SETUID, CAP_SETGID};
465 cap_t caps;
466
467 caps = cap_get_proc();
468 if (setid_retained)
469 cap_set_flag(caps, CAP_EFFECTIVE, 2, cap_values, CAP_SET);
470 else
471 cap_clear(caps);
472 cap_set_proc(caps);
473 cap_free(caps);
474 }
475
main(int argc,char ** argv)476 int main(int argc, char **argv)
477 {
478 ensure_groups_exist();
479 ensure_users_exist();
480 ensure_securityfs_mounted();
481 write_uid_policies();
482 write_gid_policies();
483
484 if (prctl(PR_SET_KEEPCAPS, 1L))
485 die("Error with set keepcaps\n");
486
487 // First test to make sure we can write userns mappings from a non-root
488 // user that doesn't have any restrictions (as long as it has
489 // CAP_SETUID);
490 if (setgid(NO_POLICY_UGID) < 0)
491 die("Error with set gid(%d)\n", NO_POLICY_UGID);
492 if (setuid(NO_POLICY_UGID) < 0)
493 die("Error with set uid(%d)\n", NO_POLICY_UGID);
494 // Take away all but setid caps
495 drop_caps(true);
496 // Need PR_SET_DUMPABLE flag set so we can write /proc/[pid]/uid_map
497 // from non-root parent process.
498 if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0))
499 die("Error with set dumpable\n");
500 if (!test_userns(true)) {
501 die("test_userns failed when it should work\n");
502 }
503
504 // Now switch to a user/group with restrictions
505 if (setgid(RESTRICTED_PARENT_UGID) < 0)
506 die("Error with set gid(%d)\n", RESTRICTED_PARENT_UGID);
507 if (setuid(RESTRICTED_PARENT_UGID) < 0)
508 die("Error with set uid(%d)\n", RESTRICTED_PARENT_UGID);
509
510 test_setuid(ROOT_UGID, false);
511 test_setuid(ALLOWED_CHILD1_UGID, true);
512 test_setuid(ALLOWED_CHILD2_UGID, true);
513 test_setuid(NO_POLICY_UGID, false);
514
515 test_setgid(ROOT_UGID, false);
516 test_setgid(ALLOWED_CHILD1_UGID, true);
517 test_setgid(ALLOWED_CHILD2_UGID, true);
518 test_setgid(NO_POLICY_UGID, false);
519
520 gid_t allowed_supp_groups[2] = {ALLOWED_CHILD1_UGID, ALLOWED_CHILD2_UGID};
521 gid_t disallowed_supp_groups[2] = {ROOT_UGID, NO_POLICY_UGID};
522 test_setgroups(allowed_supp_groups, 2, true);
523 test_setgroups(disallowed_supp_groups, 2, false);
524
525 if (!test_userns(false)) {
526 die("test_userns worked when it should fail\n");
527 }
528
529 // Now take away all caps
530 drop_caps(false);
531 test_setuid(2, false);
532 test_setuid(3, false);
533 test_setuid(4, false);
534 test_setgid(2, false);
535 test_setgid(3, false);
536 test_setgid(4, false);
537
538 // NOTE: this test doesn't clean up users that were created in
539 // /etc/passwd or flush policies that were added to the LSM.
540 printf("test successful!\n");
541 return EXIT_SUCCESS;
542 }
543