The Pedigree Project 0.1
proc.c
1#define _GNU_SOURCE
2#include <dirent.h>
3#include <fcntl.h>
4#include <grp.h>
5#include <pthread.h>
6#include <sched.h>
7#include <string.h>
8#include <unistd.h>
9
10#include "contract.h"
11#include <sys/fsuid.h>
12#include <sys/mman.h>
13#include <sys/prctl.h>
14
15struct task_view {
16 int command[2], report[2];
17 int held, result;
18};
20 pid_t tid;
21 struct ns_identity self, caller;
22};
23static void* task_view_worker(void* argument) {
24 struct task_view* state = argument;
25 struct view_report report = {.tid = gettid()};
26 state->result = 1;
27 if (unshare(CLONE_NEWUTS) || ns_set("proc-worker", "proc-worker") ||
28 ns_path_identity("/proc/self/ns/uts", &report.self) ||
29 ns_path_identity("/proc/thread-self/ns/uts", &report.caller))
30 return NULL;
31 state->held = open("/proc/thread-self/ns/uts", O_RDONLY | O_CLOEXEC);
32 if (state->held < 0 || ns_write(state->report[1], &report, sizeof(report)) ||
33 ns_receive(state->command[0], 'Q'))
34 return NULL;
35 state->result = 0;
36 return NULL;
37}
38
39static int proc_views(void) {
40 int failed = 0, started = 0, joined = 0, saved = -1, old_view = -1;
41 pthread_t worker;
42 struct task_view state = {.command = {-1, -1}, .report = {-1, -1}, .held = -1};
43 struct view_report report;
44 struct ns_identity leader, path, held;
45 char worker_path[96], leader_path[96], ns_directory[96], link[80];
46 DIR* directory = NULL;
47 void* readonly = MAP_FAILED;
48 CHECK(ns_set("proc-leader", "proc-leader") == 0);
49 saved = open("/proc/thread-self/ns/uts", O_RDONLY | O_CLOEXEC);
50 CHECK(saved >= 0 && ns_fd_identity(saved, &leader) == 0);
51 snprintf(leader_path, sizeof(leader_path), "/proc/%d/ns/uts", getpid());
52 CHECK(ns_path_identity(leader_path, &path) == 0 && ns_same(path, leader));
53 snprintf(ns_directory, sizeof(ns_directory), "/proc/%d/ns", getpid());
54 directory = opendir(ns_directory);
55 CHECK(directory != NULL);
56 int found = 0;
57 for (struct dirent* entry; (entry = readdir(directory));)
58 found += !strcmp(entry->d_name, "uts");
59 CHECK(found == 1);
60 closedir(directory);
61 directory = NULL;
62 CHECK(pipe(state.command) == 0 && pipe(state.report) == 0);
63 CHECK(pthread_create(&worker, NULL, task_view_worker, &state) == 0);
64 started = 1;
65 CHECK(ns_read(state.report[0], &report, sizeof(report)) == 0);
66 CHECK(ns_same(report.self, leader) && !ns_same(report.caller, leader));
67 snprintf(worker_path, sizeof(worker_path), "/proc/%d/task/%d/ns/uts", getpid(), report.tid);
68 CHECK(ns_path_identity(worker_path, &path) == 0 && ns_same(path, report.caller));
69 snprintf(ns_directory, sizeof(ns_directory), "/proc/%d/task/%d/ns", getpid(), report.tid);
70 old_view = open(ns_directory, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
71 CHECK(old_view >= 0);
72 memset(link, '?', sizeof(link));
73 ssize_t count = readlink(worker_path, link, sizeof(link) - 1);
74 CHECK(count > 6 && count < (ssize_t)sizeof(link));
75 link[count] = 0;
76 CHECK(!strncmp(link, "uts:[", 5) && link[count - 1] == ']');
77 char short_link[4] = {'?', '?', '?', '?'};
78 CHECK(readlink(worker_path, short_link, 3) == 3 && !memcmp(short_link, "uts", 3) &&
79 short_link[3] == '?');
80 readonly = mmap(NULL, ns_page, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
81 CHECK(readonly != MAP_FAILED);
82 errno = 0;
83 CHECK(readlink(worker_path, readonly, 20) == -1 && errno == EFAULT);
84 CHECK(ns_expect("proc-leader", "proc-leader") == 0);
85 CHECK(ns_send(state.command[1], 'Q') == 0);
86 CHECK(pthread_join(worker, NULL) == 0);
87 joined = 1;
88 CHECK(state.result == 0);
89 CHECK(ns_fd_identity(state.held, &held) == 0 && ns_same(held, report.caller));
90 CHECK(setns(state.held, 0) == 0 && ns_expect("proc-worker", "proc-worker") == 0);
91 CHECK(ns_path_identity("/proc/thread-self/ns/uts", &path) == 0 && ns_same(path, held));
92 CHECK(ns_fd_identity(saved, &path) == 0 && ns_same(path, leader));
93 CHECK(setns(saved, CLONE_NEWUTS) == 0 && ns_expect("proc-leader", "proc-leader") == 0);
94 int stale = openat(old_view, "uts", O_RDONLY | O_CLOEXEC);
95 if (stale >= 0)
96 close(stale);
97 CHECK(stale < 0);
98out:
99 if (failed && state.command[1] >= 0) {
100 close(state.command[1]);
101 state.command[1] = -1;
102 }
103 if (started && !joined)
104 pthread_join(worker, NULL);
105 for (int i = 0; i < 2; ++i) {
106 if (state.command[i] >= 0)
107 close(state.command[i]);
108 if (state.report[i] >= 0)
109 close(state.report[i]);
110 }
111 if (state.held >= 0)
112 close(state.held);
113 if (old_view >= 0)
114 close(old_view);
115 if (saved >= 0) {
116 if (setns(saved, 0))
117 failed = 1;
118 close(saved);
119 }
120 if (directory)
121 closedir(directory);
122 if (readonly != MAP_FAILED)
123 munmap(readonly, ns_page);
124 return failed;
125}
126
127static int credential_target(int command, int report, void* argument) {
128 (void)argument;
129 if (unshare(CLONE_NEWUTS) || setgroups(0, NULL) || setresgid(1002, 1002, 1002) ||
130 setresuid(1001, 1001, 1001) || prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) || ns_send(report, 'R') ||
131 ns_receive(command, 'D') || prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) || ns_send(report, 'D') ||
132 ns_receive(command, 'Q'))
133 return 1;
134 return 0;
135}
136
137static int proc_permissions(void) {
138 int failed = 0, fd = -1;
139 struct ns_peer child = NS_PEER_INITIALIZER;
140 char path[80], text[80];
141 CHECK(ns_spawn(&child, credential_target, NULL) == 0 && ns_receive(child.report, 'R') == 0);
142 snprintf(path, sizeof(path), "/proc/%d/ns/uts", child.pid);
143 errno = 0;
144 CHECK(readlink(path, text, sizeof(text)) == -1 && errno == EACCES);
145 setfsuid(1001);
146 setfsgid(1002);
147 CHECK(setfsuid((uid_t)-1) == 1001 && setfsgid((gid_t)-1) == 1002);
148 CHECK(getuid() == 0 && geteuid() == 0);
149 CHECK(readlink(path, text, sizeof(text)) > 0);
150 fd = open(path, O_RDONLY | O_CLOEXEC);
151 CHECK(fd >= 0);
152 CHECK(ns_send(child.command, 'D') == 0 && ns_receive(child.report, 'D') == 0);
153 errno = 0;
154 CHECK(readlink(path, text, sizeof(text)) == -1 && errno == EACCES);
155 CHECK(readlink("/proc/thread-self/ns/uts", text, sizeof(text)) > 0);
156 CHECK(ns_send(child.command, 'Q') == 0 && ns_join(&child) == 0);
157 struct ns_identity retained;
158 CHECK(ns_fd_identity(fd, &retained) == 0);
159out:
160 setfsuid(0);
161 setfsgid(0);
162 if (setfsuid((uid_t)-1) != 0 || setfsgid((gid_t)-1) != 0)
163 failed = 1;
164 if (fd >= 0)
165 close(fd);
166 ns_cleanup(&child);
167 return failed;
168}
169
170int ns_proc(void) {
171 return proc_views() || proc_permissions();
172}