1#include <stdlib.h>
2#include <stdio.h>
3#include <string.h>
4#include <unistd.h>
5#include <sys/types.h>
6#include <sys/stat.h>
7#include <sys/xattr.h>
8#include <fcntl.h>
9#include <dirent.h>
10#include <assert.h>
11#include <errno.h>
12#include <linux/capability.h>
13#include <sys/prctl.h>
14#include <limits.h>
15#include <stdint.h>
16#include <syscall.h>
17#include <byteswap.h>
18
19// Make sure assertions are not compiled out, we use them to codify
20// invariants about this program and we want it to fail fast and
21// loudly if they are violated.
22#undef NDEBUG
23
24extern char **environ;
25
26// The WRAPPER_DIR macro is supplied at compile time so that it cannot
27// be changed at runtime
28static char *wrapper_dir = WRAPPER_DIR;
29
30// Wrapper debug variable name
31static char *wrapper_debug = "WRAPPER_DEBUG";
32
33#define CAP_SETPCAP 8
34
35#if __BYTE_ORDER == __BIG_ENDIAN
36#define LE32_TO_H(x) bswap_32(x)
37#else
38#define LE32_TO_H(x) (x)
39#endif
40
41int get_last_cap(unsigned *last_cap) {
42 FILE* file = fopen("/proc/sys/kernel/cap_last_cap", "r");
43 if (file == NULL) {
44 int saved_errno = errno;
45 fprintf(stderr, "failed to open /proc/sys/kernel/cap_last_cap: %s\n", strerror(errno));
46 return -saved_errno;
47 }
48 int res = fscanf(file, "%u", last_cap);
49 if (res == EOF) {
50 int saved_errno = errno;
51 fprintf(stderr, "could not read number from /proc/sys/kernel/cap_last_cap: %s\n", strerror(errno));
52 return -saved_errno;
53 }
54 fclose(file);
55 return 0;
56}
57
58// Given the path to this program, fetch its configured capability set
59// (as set by `setcap ... /path/to/file`) and raise those capabilities
60// into the Ambient set.
61static int make_caps_ambient(const char *self_path) {
62 struct vfs_ns_cap_data data = {};
63 int r = getxattr(self_path, "security.capability", &data, sizeof(data));
64
65 if (r < 0) {
66 if (errno == ENODATA) {
67 // no capabilities set
68 return 0;
69 }
70 fprintf(stderr, "cannot get capabilities for %s: %s", self_path, strerror(errno));
71 return 1;
72 }
73
74 size_t size;
75 uint32_t version = LE32_TO_H(data.magic_etc) & VFS_CAP_REVISION_MASK;
76 switch (version) {
77 case VFS_CAP_REVISION_1:
78 size = VFS_CAP_U32_1;
79 break;
80 case VFS_CAP_REVISION_2:
81 case VFS_CAP_REVISION_3:
82 size = VFS_CAP_U32_3;
83 break;
84 default:
85 fprintf(stderr, "BUG! Unsupported capability version 0x%x on %s. Report to NixOS bugtracker\n", version, self_path);
86 return 1;
87 }
88
89 const struct __user_cap_header_struct header = {
90 .version = _LINUX_CAPABILITY_VERSION_3,
91 .pid = getpid(),
92 };
93 struct __user_cap_data_struct user_data[2] = {};
94
95 for (size_t i = 0; i < size; i++) {
96 // merge inheritable & permitted into one
97 user_data[i].permitted = user_data[i].inheritable =
98 LE32_TO_H(data.data[i].inheritable) | LE32_TO_H(data.data[i].permitted);
99 }
100
101 if (syscall(SYS_capset, &header, &user_data) < 0) {
102 fprintf(stderr, "failed to inherit capabilities: %s", strerror(errno));
103 return 1;
104 }
105 unsigned last_cap;
106 r = get_last_cap(&last_cap);
107 if (r < 0) {
108 return 1;
109 }
110 uint64_t set = user_data[0].permitted | (uint64_t)user_data[1].permitted << 32;
111 for (unsigned cap = 0; cap < last_cap; cap++) {
112 if (!(set & (1ULL << cap))) {
113 continue;
114 }
115
116 // Check for the cap_setpcap capability, we set this on the
117 // wrapper so it can elevate the capabilities to the Ambient
118 // set but we do not want to propagate it down into the
119 // wrapped program.
120 //
121 // TODO: what happens if that's the behavior you want
122 // though???? I'm preferring a strict vs. loose policy here.
123 if (cap == CAP_SETPCAP) {
124 if(getenv(wrapper_debug)) {
125 fprintf(stderr, "cap_setpcap in set, skipping it\n");
126 }
127 continue;
128 }
129 if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, (unsigned long) cap, 0, 0)) {
130 fprintf(stderr, "cannot raise the capability %d into the ambient set: %s\n", cap, strerror(errno));
131 return 1;
132 }
133 if (getenv(wrapper_debug)) {
134 fprintf(stderr, "raised %d into the ambient capability set\n", cap);
135 }
136 }
137
138 return 0;
139}
140
141int readlink_malloc(const char *p, char **ret) {
142 size_t l = FILENAME_MAX+1;
143 int r;
144
145 for (;;) {
146 char *c = calloc(l, sizeof(char));
147 if (!c) {
148 return -ENOMEM;
149 }
150
151 ssize_t n = readlink(p, c, l-1);
152 if (n < 0) {
153 r = -errno;
154 free(c);
155 return r;
156 }
157
158 if ((size_t) n < l-1) {
159 c[n] = 0;
160 *ret = c;
161 return 0;
162 }
163
164 free(c);
165 l *= 2;
166 }
167}
168
169int main(int argc, char **argv) {
170 char *self_path = NULL;
171 int self_path_size = readlink_malloc("/proc/self/exe", &self_path);
172 if (self_path_size < 0) {
173 fprintf(stderr, "cannot readlink /proc/self/exe: %s", strerror(-self_path_size));
174 }
175
176 // Make sure that we are being executed from the right location,
177 // i.e., `safe_wrapper_dir'. This is to prevent someone from creating
178 // hard link `X' from some other location, along with a false
179 // `X.real' file, to allow arbitrary programs from being executed
180 // with elevated capabilities.
181 int len = strlen(wrapper_dir);
182 if (len > 0 && '/' == wrapper_dir[len - 1])
183 --len;
184 assert(!strncmp(self_path, wrapper_dir, len));
185 assert('/' == wrapper_dir[0]);
186 assert('/' == self_path[len]);
187
188 // Make *really* *really* sure that we were executed as
189 // `self_path', and not, say, as some other setuid program. That
190 // is, our effective uid/gid should match the uid/gid of
191 // `self_path'.
192 struct stat st;
193 assert(lstat(self_path, &st) != -1);
194
195 assert(!(st.st_mode & S_ISUID) || (st.st_uid == geteuid()));
196 assert(!(st.st_mode & S_ISGID) || (st.st_gid == getegid()));
197
198 // And, of course, we shouldn't be writable.
199 assert(!(st.st_mode & (S_IWGRP | S_IWOTH)));
200
201 // Read the path of the real (wrapped) program from <self>.real.
202 char real_fn[PATH_MAX + 10];
203 int real_fn_size = snprintf(real_fn, sizeof(real_fn), "%s.real", self_path);
204 assert(real_fn_size < sizeof(real_fn));
205
206 int fd_self = open(real_fn, O_RDONLY);
207 assert(fd_self != -1);
208
209 char source_prog[PATH_MAX];
210 len = read(fd_self, source_prog, PATH_MAX);
211 assert(len != -1);
212 assert(len < sizeof(source_prog));
213 assert(len > 0);
214 source_prog[len] = 0;
215
216 close(fd_self);
217
218 // Read the capabilities set on the wrapper and raise them in to
219 // the ambient set so the program we're wrapping receives the
220 // capabilities too!
221 if (make_caps_ambient(self_path) != 0) {
222 free(self_path);
223 return 1;
224 }
225 free(self_path);
226
227 execve(source_prog, argv, environ);
228
229 fprintf(stderr, "%s: cannot run `%s': %s\n",
230 argv[0], source_prog, strerror(errno));
231
232 return 1;
233}