1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) Huawei Technologies Co., Ltd., 2015
4 * Copyright (C) 2022 SUSE LLC Andrea Cervesato <[email protected]>
5 */
6
7 /*\
8 * [Description]
9 *
10 * Verify that /proc/PID/uid_map and /proc/PID/gid_map contains three values
11 * separated by white space:
12 *
13 * ID-inside-ns ID-outside-ns length
14 *
15 * ID-outside-ns is interpreted according to which process is opening the file.
16 *
17 * If the process opening the file is in the same user namespace as the process
18 * PID, then ID-outside-ns is defined with respect to the parent user namespace.
19 *
20 * If the process opening the file is in a different user namespace, then
21 * ID-outside-ns is defined with respect to the user namespace of the process
22 * opening the file.
23 *
24 * The string "deny" would be written to /proc/self/setgroups before GID
25 * check if setgroups is allowed, see kernel commits:
26 *
27 * - 9cc46516ddf4 ("userns: Add a knob to disable setgroups on a per user namespace basis")
28 * - 66d2f338ee4c ("userns: Allow setting gid_maps without privilege when setgroups is disabled")
29 */
30
31 #define _GNU_SOURCE
32
33 #include <stdio.h>
34 #include <stdbool.h>
35 #include "tst_test.h"
36 #include "lapi/sched.h"
37 #include "common.h"
38
39 #define CHILD1UID 0
40 #define CHILD1GID 0
41 #define CHILD2UID 200
42 #define CHILD2GID 200
43 #define UID_MAP 0
44 #define GID_MAP 1
45
child_fn1(void)46 static void child_fn1(void)
47 {
48 TST_CHECKPOINT_WAIT(0);
49 }
50
child_fn2(int cpid1,int parentuid,int parentgid)51 static void child_fn2(int cpid1, int parentuid, int parentgid)
52 {
53 int uid, gid;
54 char cpid1uidpath[BUFSIZ];
55 char cpid1gidpath[BUFSIZ];
56 int idinsidens, idoutsidens, length;
57
58 TST_CHECKPOINT_WAIT(1);
59
60 uid = geteuid();
61 gid = getegid();
62
63 TST_EXP_EQ_LI(uid, CHILD2UID);
64 TST_EXP_EQ_LI(gid, CHILD2GID);
65
66 /* Get the uid parameters of the child_fn2 process */
67 SAFE_FILE_SCANF("/proc/self/uid_map", "%d %d %d", &idinsidens, &idoutsidens, &length);
68
69 /* map file format:ID-inside-ns ID-outside-ns length
70 * If the process opening the file is in the same user namespace as
71 * the process PID, then ID-outside-ns is defined with respect to the
72 * parent user namespace
73 */
74 tst_res(TINFO, "child2 checks /proc/cpid2/uid_map");
75
76 if (idinsidens != CHILD2UID || idoutsidens != parentuid)
77 tst_res(TFAIL, "unexpected: namespace ID inside=%d outside=%d", idinsidens, idoutsidens);
78 else
79 tst_res(TPASS, "expected namespaces IDs");
80
81 sprintf(cpid1uidpath, "/proc/%d/uid_map", cpid1);
82 SAFE_FILE_SCANF(cpid1uidpath, "%d %d %d", &idinsidens, &idoutsidens, &length);
83
84 /* If the process opening the file is in a different user namespace,
85 * then ID-outside-ns is defined with respect to the user namespace
86 * of the process opening the file
87 */
88 tst_res(TINFO, "child2 checks /proc/cpid1/uid_map");
89
90 if (idinsidens != CHILD1UID || idoutsidens != CHILD2UID)
91 tst_res(TFAIL, "unexpected: namespace ID inside=%d outside=%d", idinsidens, idoutsidens);
92 else
93 tst_res(TPASS, "expected namespaces IDs");
94
95 sprintf(cpid1gidpath, "/proc/%d/gid_map", cpid1);
96 SAFE_FILE_SCANF("/proc/self/gid_map", "%d %d %d", &idinsidens, &idoutsidens, &length);
97
98 tst_res(TINFO, "child2 checks /proc/cpid2/gid_map");
99
100 if (idinsidens != CHILD2GID || idoutsidens != parentgid)
101 tst_res(TFAIL, "unexpected: namespace ID inside=%d outside=%d", idinsidens, idoutsidens);
102 else
103 tst_res(TPASS, "expected namespaces IDs");
104
105 SAFE_FILE_SCANF(cpid1gidpath, "%d %d %d", &idinsidens, &idoutsidens, &length);
106
107 tst_res(TINFO, "child1 checks /proc/cpid1/gid_map");
108
109 if (idinsidens != CHILD1GID || idoutsidens != CHILD2GID)
110 tst_res(TFAIL, "unexpected: namespace ID inside=%d outside=%d", idinsidens, idoutsidens);
111 else
112 tst_res(TPASS, "expected namespaces IDs");
113
114 TST_CHECKPOINT_WAKE(0);
115 TST_CHECKPOINT_WAKE(1);
116 }
117
run(void)118 static void run(void)
119 {
120 const struct tst_clone_args args = {
121 .flags = CLONE_NEWUSER,
122 .exit_signal = SIGCHLD,
123 };
124 pid_t cpid1, cpid2;
125 uid_t parentuid;
126 gid_t parentgid;
127 char path[BUFSIZ];
128 int fd;
129
130 parentuid = geteuid();
131 parentgid = getegid();
132
133 cpid1 = SAFE_CLONE(&args);
134 if (!cpid1) {
135 child_fn1();
136 return;
137 }
138
139 cpid2 = SAFE_CLONE(&args);
140 if (!cpid2) {
141 child_fn2(cpid1, parentuid, parentgid);
142 return;
143 }
144
145 if (access("/proc/self/setgroups", F_OK) == 0) {
146 sprintf(path, "/proc/%d/setgroups", cpid1);
147
148 fd = SAFE_OPEN(path, O_WRONLY, 0644);
149 SAFE_WRITE(SAFE_WRITE_ALL, fd, "deny", 4);
150 SAFE_CLOSE(fd);
151
152 /* If the setgroups file has the value "deny",
153 * then the setgroups(2) system call can't
154 * subsequently be reenabled (by writing "allow" to
155 * the file) in this user namespace. (Attempts to
156 * do so will fail with the error EPERM.)
157 */
158
159 tst_res(TINFO, "Check if setgroups can be re-enabled");
160
161 fd = SAFE_OPEN(path, O_WRONLY, 0644);
162 TST_EXP_FAIL2(write(fd, "allow", 5), EPERM);
163 SAFE_CLOSE(fd);
164
165 sprintf(path, "/proc/%d/setgroups", cpid2);
166
167 fd = SAFE_OPEN(path, O_WRONLY, 0644);
168 SAFE_WRITE(SAFE_WRITE_ALL, fd, "deny", 4);
169 SAFE_CLOSE(fd);
170 }
171
172 updatemap(cpid1, UID_MAP, CHILD1UID, parentuid);
173 updatemap(cpid2, UID_MAP, CHILD2UID, parentuid);
174
175 updatemap(cpid1, GID_MAP, CHILD1GID, parentgid);
176 updatemap(cpid2, GID_MAP, CHILD2GID, parentgid);
177
178 TST_CHECKPOINT_WAKE_AND_WAIT(1);
179 }
180
181 static struct tst_test test = {
182 .test_all = run,
183 .needs_root = 1,
184 .forks_child = 1,
185 .needs_checkpoints = 1,
186 .needs_kconfigs = (const char *[]) {
187 "CONFIG_USER_NS",
188 NULL,
189 },
190 };
191