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 <fcntl.h>
8#include <dirent.h>
9#include <assert.h>
10#include <errno.h>
11#include <linux/capability.h>
12#include <sys/capability.h>
13#include <linux/prctl.h>
14#include <sys/prctl.h>
15#include <cap-ng.h>
16
17// Make sure assertions are not compiled out, we use them to codify
18// invariants about this program and we want it to fail fast and
19// loudly if they are violated.
20#undef NDEBUG
21
22extern char **environ;
23
24// The WRAPPER_DIR macro is supplied at compile time so that it cannot
25// be changed at runtime
26static char * wrapperDir = WRAPPER_DIR;
27
28// Wrapper debug variable name
29static char * wrapperDebug = "WRAPPER_DEBUG";
30
31// Update the capabilities of the running process to include the given
32// capability in the Ambient set.
33static void set_ambient_cap(cap_value_t cap)
34{
35 capng_get_caps_process();
36
37 if (capng_update(CAPNG_ADD, CAPNG_INHERITABLE, (unsigned long) cap))
38 {
39 perror("cannot raise the capability into the Inheritable set\n");
40 exit(1);
41 }
42
43 capng_apply(CAPNG_SELECT_CAPS);
44
45 if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, (unsigned long) cap, 0, 0))
46 {
47 perror("cannot raise the capability into the Ambient set\n");
48 exit(1);
49 }
50}
51
52// Given the path to this program, fetch its configured capability set
53// (as set by `setcap ... /path/to/file`) and raise those capabilities
54// into the Ambient set.
55static int make_caps_ambient(const char *selfPath)
56{
57 cap_t caps = cap_get_file(selfPath);
58
59 if(!caps)
60 {
61 if(getenv(wrapperDebug))
62 fprintf(stderr, "no caps set or could not retrieve the caps for this file, not doing anything...");
63
64 return 1;
65 }
66
67 // We use `cap_to_text` and iteration over the tokenized result
68 // string because, as of libcap's current release, there is no
69 // facility for retrieving an array of `cap_value_t`'s that can be
70 // given to `prctl` in order to lift that capability into the
71 // Ambient set.
72 //
73 // Some discussion was had around shot-gunning all of the
74 // capabilities we know about into the Ambient set but that has a
75 // security smell and I deemed the risk of the current
76 // implementation crashing the program to be lower than the risk
77 // of a privilege escalation security hole being introduced by
78 // raising all capabilities, even ones we didn't intend for the
79 // program, into the Ambient set.
80 //
81 // `cap_t` which is returned by `cap_get_*` is an opaque type and
82 // even if we could retrieve the bitmasks (which, as far as I can
83 // tell we cannot) in order to get the `cap_value_t`
84 // representation for each capability we would have to take the
85 // total number of capabilities supported and iterate over the
86 // sequence of integers up-to that maximum total, testing each one
87 // against the bitmask ((bitmask >> n) & 1) to see if it's set and
88 // aggregating each "capability integer n" that is set in the
89 // bitmask.
90 //
91 // That, combined with the fact that we can't easily get the
92 // bitmask anyway seemed much more brittle than fetching the
93 // `cap_t`, transforming it into a textual representation,
94 // tokenizing the string, and using `cap_from_name` on the token
95 // to get the `cap_value_t` that we need for `prctl`. There is
96 // indeed risk involved if the output string format of
97 // `cap_to_text` ever changes but at this time the combination of
98 // factors involving the below list have led me to the conclusion
99 // that the best implementation at this time is reading then
100 // parsing with *lots of documentation* about why we're doing it
101 // this way.
102 //
103 // 1. No explicit API for fetching an array of `cap_value_t`'s or
104 // for transforming a `cap_t` into such a representation
105 // 2. The risk of a crash is lower than lifting all capabilities
106 // into the Ambient set
107 // 3. libcap is depended on heavily in the Linux ecosystem so
108 // there is a high chance that the output representation of
109 // `cap_to_text` will not change which reduces our risk that
110 // this parsing step will cause a crash
111 //
112 // The preferred method, should it ever be available in the
113 // future, would be to use libcap API's to transform the result
114 // from a `cap_get_*` into an array of `cap_value_t`'s that can
115 // then be given to prctl.
116 //
117 // - Parnell
118 ssize_t capLen;
119 char* capstr = cap_to_text(caps, &capLen);
120 cap_free(caps);
121
122 // TODO: For now, we assume that cap_to_text always starts its
123 // result string with " =" and that the first capability is listed
124 // immediately after that. We should verify this.
125 assert(capLen >= 2);
126 capstr += 2;
127
128 char* saveptr = NULL;
129 for(char* tok = strtok_r(capstr, ",", &saveptr); tok; tok = strtok_r(NULL, ",", &saveptr))
130 {
131 cap_value_t capnum;
132 if (cap_from_name(tok, &capnum))
133 {
134 if(getenv(wrapperDebug))
135 fprintf(stderr, "cap_from_name failed, skipping: %s", tok);
136 }
137 else if (capnum == CAP_SETPCAP)
138 {
139 // Check for the cap_setpcap capability, we set this on the
140 // wrapper so it can elevate the capabilities to the Ambient
141 // set but we do not want to propagate it down into the
142 // wrapped program.
143 //
144 // TODO: what happens if that's the behavior you want
145 // though???? I'm preferring a strict vs. loose policy here.
146 if(getenv(wrapperDebug))
147 fprintf(stderr, "cap_setpcap in set, skipping it\n");
148 }
149 else
150 {
151 set_ambient_cap(capnum);
152
153 if(getenv(wrapperDebug))
154 fprintf(stderr, "raised %s into the Ambient capability set\n", tok);
155 }
156 }
157 cap_free(capstr);
158
159 return 0;
160}
161
162int main(int argc, char * * argv)
163{
164 // I *think* it's safe to assume that a path from a symbolic link
165 // should safely fit within the PATH_MAX system limit. Though I'm
166 // not positive it's safe...
167 char selfPath[PATH_MAX];
168 int selfPathSize = readlink("/proc/self/exe", selfPath, sizeof(selfPath));
169
170 assert(selfPathSize > 0);
171
172 // Assert we have room for the zero byte, this ensures the path
173 // isn't being truncated because it's too big for the buffer.
174 //
175 // A better way to handle this might be to use something like the
176 // whereami library (https://github.com/gpakosz/whereami) or a
177 // loop that resizes the buffer and re-reads the link if the
178 // contents are being truncated.
179 assert(selfPathSize < sizeof(selfPath));
180
181 // Set the zero byte since readlink doesn't do that for us.
182 selfPath[selfPathSize] = '\0';
183
184 // Make sure that we are being executed from the right location,
185 // i.e., `safeWrapperDir'. This is to prevent someone from creating
186 // hard link `X' from some other location, along with a false
187 // `X.real' file, to allow arbitrary programs from being executed
188 // with elevated capabilities.
189 int len = strlen(wrapperDir);
190 if (len > 0 && '/' == wrapperDir[len - 1])
191 --len;
192 assert(!strncmp(selfPath, wrapperDir, len));
193 assert('/' == wrapperDir[0]);
194 assert('/' == selfPath[len]);
195
196 // Make *really* *really* sure that we were executed as
197 // `selfPath', and not, say, as some other setuid program. That
198 // is, our effective uid/gid should match the uid/gid of
199 // `selfPath'.
200 struct stat st;
201 assert(lstat(selfPath, &st) != -1);
202
203 assert(!(st.st_mode & S_ISUID) || (st.st_uid == geteuid()));
204 assert(!(st.st_mode & S_ISGID) || (st.st_gid == getegid()));
205
206 // And, of course, we shouldn't be writable.
207 assert(!(st.st_mode & (S_IWGRP | S_IWOTH)));
208
209 // Read the path of the real (wrapped) program from <self>.real.
210 char realFN[PATH_MAX + 10];
211 int realFNSize = snprintf (realFN, sizeof(realFN), "%s.real", selfPath);
212 assert (realFNSize < sizeof(realFN));
213
214 int fdSelf = open(realFN, O_RDONLY);
215 assert (fdSelf != -1);
216
217 char sourceProg[PATH_MAX];
218 len = read(fdSelf, sourceProg, PATH_MAX);
219 assert (len != -1);
220 assert (len < sizeof(sourceProg));
221 assert (len > 0);
222 sourceProg[len] = 0;
223
224 close(fdSelf);
225
226 // Read the capabilities set on the wrapper and raise them in to
227 // the Ambient set so the program we're wrapping receives the
228 // capabilities too!
229 make_caps_ambient(selfPath);
230
231 execve(sourceProg, argv, environ);
232
233 fprintf(stderr, "%s: cannot run `%s': %s\n",
234 argv[0], sourceProg, strerror(errno));
235
236 exit(1);
237}
238
239