1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0
2*4882a593Smuzhiyun #define _GNU_SOURCE
3*4882a593Smuzhiyun #include <stdio.h>
4*4882a593Smuzhiyun #include <errno.h>
5*4882a593Smuzhiyun #include <pwd.h>
6*4882a593Smuzhiyun #include <string.h>
7*4882a593Smuzhiyun #include <syscall.h>
8*4882a593Smuzhiyun #include <sys/capability.h>
9*4882a593Smuzhiyun #include <sys/types.h>
10*4882a593Smuzhiyun #include <sys/mount.h>
11*4882a593Smuzhiyun #include <sys/prctl.h>
12*4882a593Smuzhiyun #include <sys/wait.h>
13*4882a593Smuzhiyun #include <stdlib.h>
14*4882a593Smuzhiyun #include <unistd.h>
15*4882a593Smuzhiyun #include <fcntl.h>
16*4882a593Smuzhiyun #include <stdbool.h>
17*4882a593Smuzhiyun #include <stdarg.h>
18*4882a593Smuzhiyun
19*4882a593Smuzhiyun #ifndef CLONE_NEWUSER
20*4882a593Smuzhiyun # define CLONE_NEWUSER 0x10000000
21*4882a593Smuzhiyun #endif
22*4882a593Smuzhiyun
23*4882a593Smuzhiyun #define ROOT_USER 0
24*4882a593Smuzhiyun #define RESTRICTED_PARENT 1
25*4882a593Smuzhiyun #define ALLOWED_CHILD1 2
26*4882a593Smuzhiyun #define ALLOWED_CHILD2 3
27*4882a593Smuzhiyun #define NO_POLICY_USER 4
28*4882a593Smuzhiyun
29*4882a593Smuzhiyun char* add_whitelist_policy_file = "/sys/kernel/security/safesetid/add_whitelist_policy";
30*4882a593Smuzhiyun
die(char * fmt,...)31*4882a593Smuzhiyun static void die(char *fmt, ...)
32*4882a593Smuzhiyun {
33*4882a593Smuzhiyun va_list ap;
34*4882a593Smuzhiyun va_start(ap, fmt);
35*4882a593Smuzhiyun vfprintf(stderr, fmt, ap);
36*4882a593Smuzhiyun va_end(ap);
37*4882a593Smuzhiyun exit(EXIT_FAILURE);
38*4882a593Smuzhiyun }
39*4882a593Smuzhiyun
vmaybe_write_file(bool enoent_ok,char * filename,char * fmt,va_list ap)40*4882a593Smuzhiyun static bool vmaybe_write_file(bool enoent_ok, char *filename, char *fmt, va_list ap)
41*4882a593Smuzhiyun {
42*4882a593Smuzhiyun char buf[4096];
43*4882a593Smuzhiyun int fd;
44*4882a593Smuzhiyun ssize_t written;
45*4882a593Smuzhiyun int buf_len;
46*4882a593Smuzhiyun
47*4882a593Smuzhiyun buf_len = vsnprintf(buf, sizeof(buf), fmt, ap);
48*4882a593Smuzhiyun if (buf_len < 0) {
49*4882a593Smuzhiyun printf("vsnprintf failed: %s\n",
50*4882a593Smuzhiyun strerror(errno));
51*4882a593Smuzhiyun return false;
52*4882a593Smuzhiyun }
53*4882a593Smuzhiyun if (buf_len >= sizeof(buf)) {
54*4882a593Smuzhiyun printf("vsnprintf output truncated\n");
55*4882a593Smuzhiyun return false;
56*4882a593Smuzhiyun }
57*4882a593Smuzhiyun
58*4882a593Smuzhiyun fd = open(filename, O_WRONLY);
59*4882a593Smuzhiyun if (fd < 0) {
60*4882a593Smuzhiyun if ((errno == ENOENT) && enoent_ok)
61*4882a593Smuzhiyun return true;
62*4882a593Smuzhiyun return false;
63*4882a593Smuzhiyun }
64*4882a593Smuzhiyun written = write(fd, buf, buf_len);
65*4882a593Smuzhiyun if (written != buf_len) {
66*4882a593Smuzhiyun if (written >= 0) {
67*4882a593Smuzhiyun printf("short write to %s\n", filename);
68*4882a593Smuzhiyun return false;
69*4882a593Smuzhiyun } else {
70*4882a593Smuzhiyun printf("write to %s failed: %s\n",
71*4882a593Smuzhiyun filename, strerror(errno));
72*4882a593Smuzhiyun return false;
73*4882a593Smuzhiyun }
74*4882a593Smuzhiyun }
75*4882a593Smuzhiyun if (close(fd) != 0) {
76*4882a593Smuzhiyun printf("close of %s failed: %s\n",
77*4882a593Smuzhiyun filename, strerror(errno));
78*4882a593Smuzhiyun return false;
79*4882a593Smuzhiyun }
80*4882a593Smuzhiyun return true;
81*4882a593Smuzhiyun }
82*4882a593Smuzhiyun
write_file(char * filename,char * fmt,...)83*4882a593Smuzhiyun static bool write_file(char *filename, char *fmt, ...)
84*4882a593Smuzhiyun {
85*4882a593Smuzhiyun va_list ap;
86*4882a593Smuzhiyun bool ret;
87*4882a593Smuzhiyun
88*4882a593Smuzhiyun va_start(ap, fmt);
89*4882a593Smuzhiyun ret = vmaybe_write_file(false, filename, fmt, ap);
90*4882a593Smuzhiyun va_end(ap);
91*4882a593Smuzhiyun
92*4882a593Smuzhiyun return ret;
93*4882a593Smuzhiyun }
94*4882a593Smuzhiyun
ensure_user_exists(uid_t uid)95*4882a593Smuzhiyun static void ensure_user_exists(uid_t uid)
96*4882a593Smuzhiyun {
97*4882a593Smuzhiyun struct passwd p;
98*4882a593Smuzhiyun
99*4882a593Smuzhiyun FILE *fd;
100*4882a593Smuzhiyun char name_str[10];
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun if (getpwuid(uid) == NULL) {
103*4882a593Smuzhiyun memset(&p,0x00,sizeof(p));
104*4882a593Smuzhiyun fd=fopen("/etc/passwd","a");
105*4882a593Smuzhiyun if (fd == NULL)
106*4882a593Smuzhiyun die("couldn't open file\n");
107*4882a593Smuzhiyun if (fseek(fd, 0, SEEK_END))
108*4882a593Smuzhiyun die("couldn't fseek\n");
109*4882a593Smuzhiyun snprintf(name_str, 10, "%d", uid);
110*4882a593Smuzhiyun p.pw_name=name_str;
111*4882a593Smuzhiyun p.pw_uid=uid;
112*4882a593Smuzhiyun p.pw_gecos="Test account";
113*4882a593Smuzhiyun p.pw_dir="/dev/null";
114*4882a593Smuzhiyun p.pw_shell="/bin/false";
115*4882a593Smuzhiyun int value = putpwent(&p,fd);
116*4882a593Smuzhiyun if (value != 0)
117*4882a593Smuzhiyun die("putpwent failed\n");
118*4882a593Smuzhiyun if (fclose(fd))
119*4882a593Smuzhiyun die("fclose failed\n");
120*4882a593Smuzhiyun }
121*4882a593Smuzhiyun }
122*4882a593Smuzhiyun
ensure_securityfs_mounted(void)123*4882a593Smuzhiyun static void ensure_securityfs_mounted(void)
124*4882a593Smuzhiyun {
125*4882a593Smuzhiyun int fd = open(add_whitelist_policy_file, O_WRONLY);
126*4882a593Smuzhiyun if (fd < 0) {
127*4882a593Smuzhiyun if (errno == ENOENT) {
128*4882a593Smuzhiyun // Need to mount securityfs
129*4882a593Smuzhiyun if (mount("securityfs", "/sys/kernel/security",
130*4882a593Smuzhiyun "securityfs", 0, NULL) < 0)
131*4882a593Smuzhiyun die("mounting securityfs failed\n");
132*4882a593Smuzhiyun } else {
133*4882a593Smuzhiyun die("couldn't find securityfs for unknown reason\n");
134*4882a593Smuzhiyun }
135*4882a593Smuzhiyun } else {
136*4882a593Smuzhiyun if (close(fd) != 0) {
137*4882a593Smuzhiyun die("close of %s failed: %s\n",
138*4882a593Smuzhiyun add_whitelist_policy_file, strerror(errno));
139*4882a593Smuzhiyun }
140*4882a593Smuzhiyun }
141*4882a593Smuzhiyun }
142*4882a593Smuzhiyun
write_policies(void)143*4882a593Smuzhiyun static void write_policies(void)
144*4882a593Smuzhiyun {
145*4882a593Smuzhiyun static char *policy_str =
146*4882a593Smuzhiyun "1:2\n"
147*4882a593Smuzhiyun "1:3\n"
148*4882a593Smuzhiyun "2:2\n"
149*4882a593Smuzhiyun "3:3\n";
150*4882a593Smuzhiyun ssize_t written;
151*4882a593Smuzhiyun int fd;
152*4882a593Smuzhiyun
153*4882a593Smuzhiyun fd = open(add_whitelist_policy_file, O_WRONLY);
154*4882a593Smuzhiyun if (fd < 0)
155*4882a593Smuzhiyun die("cant open add_whitelist_policy file\n");
156*4882a593Smuzhiyun written = write(fd, policy_str, strlen(policy_str));
157*4882a593Smuzhiyun if (written != strlen(policy_str)) {
158*4882a593Smuzhiyun if (written >= 0) {
159*4882a593Smuzhiyun die("short write to %s\n", add_whitelist_policy_file);
160*4882a593Smuzhiyun } else {
161*4882a593Smuzhiyun die("write to %s failed: %s\n",
162*4882a593Smuzhiyun add_whitelist_policy_file, strerror(errno));
163*4882a593Smuzhiyun }
164*4882a593Smuzhiyun }
165*4882a593Smuzhiyun if (close(fd) != 0) {
166*4882a593Smuzhiyun die("close of %s failed: %s\n",
167*4882a593Smuzhiyun add_whitelist_policy_file, strerror(errno));
168*4882a593Smuzhiyun }
169*4882a593Smuzhiyun }
170*4882a593Smuzhiyun
test_userns(bool expect_success)171*4882a593Smuzhiyun static bool test_userns(bool expect_success)
172*4882a593Smuzhiyun {
173*4882a593Smuzhiyun uid_t uid;
174*4882a593Smuzhiyun char map_file_name[32];
175*4882a593Smuzhiyun size_t sz = sizeof(map_file_name);
176*4882a593Smuzhiyun pid_t cpid;
177*4882a593Smuzhiyun bool success;
178*4882a593Smuzhiyun
179*4882a593Smuzhiyun uid = getuid();
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun int clone_flags = CLONE_NEWUSER;
182*4882a593Smuzhiyun cpid = syscall(SYS_clone, clone_flags, NULL);
183*4882a593Smuzhiyun if (cpid == -1) {
184*4882a593Smuzhiyun printf("clone failed");
185*4882a593Smuzhiyun return false;
186*4882a593Smuzhiyun }
187*4882a593Smuzhiyun
188*4882a593Smuzhiyun if (cpid == 0) { /* Code executed by child */
189*4882a593Smuzhiyun // Give parent 1 second to write map file
190*4882a593Smuzhiyun sleep(1);
191*4882a593Smuzhiyun exit(EXIT_SUCCESS);
192*4882a593Smuzhiyun } else { /* Code executed by parent */
193*4882a593Smuzhiyun if(snprintf(map_file_name, sz, "/proc/%d/uid_map", cpid) < 0) {
194*4882a593Smuzhiyun printf("preparing file name string failed");
195*4882a593Smuzhiyun return false;
196*4882a593Smuzhiyun }
197*4882a593Smuzhiyun success = write_file(map_file_name, "0 0 1", uid);
198*4882a593Smuzhiyun return success == expect_success;
199*4882a593Smuzhiyun }
200*4882a593Smuzhiyun
201*4882a593Smuzhiyun printf("should not reach here");
202*4882a593Smuzhiyun return false;
203*4882a593Smuzhiyun }
204*4882a593Smuzhiyun
test_setuid(uid_t child_uid,bool expect_success)205*4882a593Smuzhiyun static void test_setuid(uid_t child_uid, bool expect_success)
206*4882a593Smuzhiyun {
207*4882a593Smuzhiyun pid_t cpid, w;
208*4882a593Smuzhiyun int wstatus;
209*4882a593Smuzhiyun
210*4882a593Smuzhiyun cpid = fork();
211*4882a593Smuzhiyun if (cpid == -1) {
212*4882a593Smuzhiyun die("fork\n");
213*4882a593Smuzhiyun }
214*4882a593Smuzhiyun
215*4882a593Smuzhiyun if (cpid == 0) { /* Code executed by child */
216*4882a593Smuzhiyun if (setuid(child_uid) < 0)
217*4882a593Smuzhiyun exit(EXIT_FAILURE);
218*4882a593Smuzhiyun if (getuid() == child_uid)
219*4882a593Smuzhiyun exit(EXIT_SUCCESS);
220*4882a593Smuzhiyun else
221*4882a593Smuzhiyun exit(EXIT_FAILURE);
222*4882a593Smuzhiyun } else { /* Code executed by parent */
223*4882a593Smuzhiyun do {
224*4882a593Smuzhiyun w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED);
225*4882a593Smuzhiyun if (w == -1) {
226*4882a593Smuzhiyun die("waitpid\n");
227*4882a593Smuzhiyun }
228*4882a593Smuzhiyun
229*4882a593Smuzhiyun if (WIFEXITED(wstatus)) {
230*4882a593Smuzhiyun if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
231*4882a593Smuzhiyun if (expect_success) {
232*4882a593Smuzhiyun return;
233*4882a593Smuzhiyun } else {
234*4882a593Smuzhiyun die("unexpected success\n");
235*4882a593Smuzhiyun }
236*4882a593Smuzhiyun } else {
237*4882a593Smuzhiyun if (expect_success) {
238*4882a593Smuzhiyun die("unexpected failure\n");
239*4882a593Smuzhiyun } else {
240*4882a593Smuzhiyun return;
241*4882a593Smuzhiyun }
242*4882a593Smuzhiyun }
243*4882a593Smuzhiyun } else if (WIFSIGNALED(wstatus)) {
244*4882a593Smuzhiyun if (WTERMSIG(wstatus) == 9) {
245*4882a593Smuzhiyun if (expect_success)
246*4882a593Smuzhiyun die("killed unexpectedly\n");
247*4882a593Smuzhiyun else
248*4882a593Smuzhiyun return;
249*4882a593Smuzhiyun } else {
250*4882a593Smuzhiyun die("unexpected signal: %d\n", wstatus);
251*4882a593Smuzhiyun }
252*4882a593Smuzhiyun } else {
253*4882a593Smuzhiyun die("unexpected status: %d\n", wstatus);
254*4882a593Smuzhiyun }
255*4882a593Smuzhiyun } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
256*4882a593Smuzhiyun }
257*4882a593Smuzhiyun
258*4882a593Smuzhiyun die("should not reach here\n");
259*4882a593Smuzhiyun }
260*4882a593Smuzhiyun
ensure_users_exist(void)261*4882a593Smuzhiyun static void ensure_users_exist(void)
262*4882a593Smuzhiyun {
263*4882a593Smuzhiyun ensure_user_exists(ROOT_USER);
264*4882a593Smuzhiyun ensure_user_exists(RESTRICTED_PARENT);
265*4882a593Smuzhiyun ensure_user_exists(ALLOWED_CHILD1);
266*4882a593Smuzhiyun ensure_user_exists(ALLOWED_CHILD2);
267*4882a593Smuzhiyun ensure_user_exists(NO_POLICY_USER);
268*4882a593Smuzhiyun }
269*4882a593Smuzhiyun
drop_caps(bool setid_retained)270*4882a593Smuzhiyun static void drop_caps(bool setid_retained)
271*4882a593Smuzhiyun {
272*4882a593Smuzhiyun cap_value_t cap_values[] = {CAP_SETUID, CAP_SETGID};
273*4882a593Smuzhiyun cap_t caps;
274*4882a593Smuzhiyun
275*4882a593Smuzhiyun caps = cap_get_proc();
276*4882a593Smuzhiyun if (setid_retained)
277*4882a593Smuzhiyun cap_set_flag(caps, CAP_EFFECTIVE, 2, cap_values, CAP_SET);
278*4882a593Smuzhiyun else
279*4882a593Smuzhiyun cap_clear(caps);
280*4882a593Smuzhiyun cap_set_proc(caps);
281*4882a593Smuzhiyun cap_free(caps);
282*4882a593Smuzhiyun }
283*4882a593Smuzhiyun
main(int argc,char ** argv)284*4882a593Smuzhiyun int main(int argc, char **argv)
285*4882a593Smuzhiyun {
286*4882a593Smuzhiyun ensure_users_exist();
287*4882a593Smuzhiyun ensure_securityfs_mounted();
288*4882a593Smuzhiyun write_policies();
289*4882a593Smuzhiyun
290*4882a593Smuzhiyun if (prctl(PR_SET_KEEPCAPS, 1L))
291*4882a593Smuzhiyun die("Error with set keepcaps\n");
292*4882a593Smuzhiyun
293*4882a593Smuzhiyun // First test to make sure we can write userns mappings from a user
294*4882a593Smuzhiyun // that doesn't have any restrictions (as long as it has CAP_SETUID);
295*4882a593Smuzhiyun if (setuid(NO_POLICY_USER) < 0)
296*4882a593Smuzhiyun die("Error with set uid(%d)\n", NO_POLICY_USER);
297*4882a593Smuzhiyun if (setgid(NO_POLICY_USER) < 0)
298*4882a593Smuzhiyun die("Error with set gid(%d)\n", NO_POLICY_USER);
299*4882a593Smuzhiyun
300*4882a593Smuzhiyun // Take away all but setid caps
301*4882a593Smuzhiyun drop_caps(true);
302*4882a593Smuzhiyun
303*4882a593Smuzhiyun // Need PR_SET_DUMPABLE flag set so we can write /proc/[pid]/uid_map
304*4882a593Smuzhiyun // from non-root parent process.
305*4882a593Smuzhiyun if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0))
306*4882a593Smuzhiyun die("Error with set dumpable\n");
307*4882a593Smuzhiyun
308*4882a593Smuzhiyun if (!test_userns(true)) {
309*4882a593Smuzhiyun die("test_userns failed when it should work\n");
310*4882a593Smuzhiyun }
311*4882a593Smuzhiyun
312*4882a593Smuzhiyun if (setuid(RESTRICTED_PARENT) < 0)
313*4882a593Smuzhiyun die("Error with set uid(%d)\n", RESTRICTED_PARENT);
314*4882a593Smuzhiyun if (setgid(RESTRICTED_PARENT) < 0)
315*4882a593Smuzhiyun die("Error with set gid(%d)\n", RESTRICTED_PARENT);
316*4882a593Smuzhiyun
317*4882a593Smuzhiyun test_setuid(ROOT_USER, false);
318*4882a593Smuzhiyun test_setuid(ALLOWED_CHILD1, true);
319*4882a593Smuzhiyun test_setuid(ALLOWED_CHILD2, true);
320*4882a593Smuzhiyun test_setuid(NO_POLICY_USER, false);
321*4882a593Smuzhiyun
322*4882a593Smuzhiyun if (!test_userns(false)) {
323*4882a593Smuzhiyun die("test_userns worked when it should fail\n");
324*4882a593Smuzhiyun }
325*4882a593Smuzhiyun
326*4882a593Smuzhiyun // Now take away all caps
327*4882a593Smuzhiyun drop_caps(false);
328*4882a593Smuzhiyun test_setuid(2, false);
329*4882a593Smuzhiyun test_setuid(3, false);
330*4882a593Smuzhiyun test_setuid(4, false);
331*4882a593Smuzhiyun
332*4882a593Smuzhiyun // NOTE: this test doesn't clean up users that were created in
333*4882a593Smuzhiyun // /etc/passwd or flush policies that were added to the LSM.
334*4882a593Smuzhiyun return EXIT_SUCCESS;
335*4882a593Smuzhiyun }
336