at 18.09-beta 8.7 kB view raw
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 <sys/prctl.h> 14#include <limits.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