···1717#include <syscall.h>
1818#include <byteswap.h>
19192020-#ifndef SOURCE_PROG
2121-#error SOURCE_PROG should be defined via preprocessor commandline
2222-#endif
2323-2420// aborts when false, printing the failed expression
2521#define ASSERT(expr) ((expr) ? (void) 0 : assert_failure(#expr))
2622// aborts when returns non-zero, printing the failed expression and errno
2723#define MUSTSUCCEED(expr) ((expr) ? print_errno_and_die(#expr) : (void) 0)
28242925extern char **environ;
2626+2727+// The WRAPPER_DIR macro is supplied at compile time so that it cannot
2828+// be changed at runtime
2929+static char *wrapper_dir = WRAPPER_DIR;
30303131// Wrapper debug variable name
3232static char *wrapper_debug = "WRAPPER_DEBUG";
···151151 return 0;
152152}
153153154154+int readlink_malloc(const char *p, char **ret) {
155155+ size_t l = FILENAME_MAX+1;
156156+ int r;
157157+158158+ for (;;) {
159159+ char *c = calloc(l, sizeof(char));
160160+ if (!c) {
161161+ return -ENOMEM;
162162+ }
163163+164164+ ssize_t n = readlink(p, c, l-1);
165165+ if (n < 0) {
166166+ r = -errno;
167167+ free(c);
168168+ return r;
169169+ }
170170+171171+ if ((size_t) n < l-1) {
172172+ c[n] = 0;
173173+ *ret = c;
174174+ return 0;
175175+ }
176176+177177+ free(c);
178178+ l *= 2;
179179+ }
180180+}
181181+154182int main(int argc, char **argv) {
155183 ASSERT(argc >= 1);
184184+ char *self_path = NULL;
185185+ int self_path_size = readlink_malloc("/proc/self/exe", &self_path);
186186+ if (self_path_size < 0) {
187187+ fprintf(stderr, "cannot readlink /proc/self/exe: %s", strerror(-self_path_size));
188188+ }
189189+190190+ unsigned int ruid, euid, suid, rgid, egid, sgid;
191191+ MUSTSUCCEED(getresuid(&ruid, &euid, &suid));
192192+ MUSTSUCCEED(getresgid(&rgid, &egid, &sgid));
193193+194194+ // If true, then we did not benefit from setuid privilege escalation,
195195+ // where the original uid is still in ruid and different from euid == suid.
196196+ int didnt_suid = (ruid == euid) && (euid == suid);
197197+ // If true, then we did not benefit from setgid privilege escalation
198198+ int didnt_sgid = (rgid == egid) && (egid == sgid);
199199+200200+201201+ // Make sure that we are being executed from the right location,
202202+ // i.e., `safe_wrapper_dir'. This is to prevent someone from creating
203203+ // hard link `X' from some other location, along with a false
204204+ // `X.real' file, to allow arbitrary programs from being executed
205205+ // with elevated capabilities.
206206+ int len = strlen(wrapper_dir);
207207+ if (len > 0 && '/' == wrapper_dir[len - 1])
208208+ --len;
209209+ ASSERT(!strncmp(self_path, wrapper_dir, len));
210210+ ASSERT('/' == wrapper_dir[0]);
211211+ ASSERT('/' == self_path[len]);
212212+213213+ // If we got privileges with the fs set[ug]id bit, check that the privilege we
214214+ // got matches the one one we expected, ie that our effective uid/gid
215215+ // matches the uid/gid of `self_path`. This ensures that we were executed as
216216+ // `self_path', and not, say, as some other setuid program.
217217+ // We don't check that if we did not benefit from the set[ug]id bit, as
218218+ // can be the case in nosuid mounts or user namespaces.
219219+ struct stat st;
220220+ ASSERT(lstat(self_path, &st) != -1);
221221+222222+ // if the wrapper gained privilege with suid, check that we got the uid of the file owner
223223+ ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == euid));
224224+ // if the wrapper gained privilege with sgid, check that we got the gid of the file group
225225+ ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == egid));
226226+ // same, but with suid instead of euid
227227+ ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == suid));
228228+ ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == sgid));
229229+230230+ // And, of course, we shouldn't be writable.
231231+ ASSERT(!(st.st_mode & (S_IWGRP | S_IWOTH)));
232232+233233+ // Read the path of the real (wrapped) program from <self>.real.
234234+ char real_fn[PATH_MAX + 10];
235235+ int real_fn_size = snprintf(real_fn, sizeof(real_fn), "%s.real", self_path);
236236+ ASSERT(real_fn_size < sizeof(real_fn));
237237+238238+ int fd_self = open(real_fn, O_RDONLY);
239239+ ASSERT(fd_self != -1);
240240+241241+ char source_prog[PATH_MAX];
242242+ len = read(fd_self, source_prog, PATH_MAX);
243243+ ASSERT(len != -1);
244244+ ASSERT(len < sizeof(source_prog));
245245+ ASSERT(len > 0);
246246+ source_prog[len] = 0;
247247+248248+ close(fd_self);
156249157250 // Read the capabilities set on the wrapper and raise them in to
158251 // the ambient set so the program we're wrapping receives the
159252 // capabilities too!
160160- if (make_caps_ambient("/proc/self/exe") != 0) {
253253+ if (make_caps_ambient(self_path) != 0) {
254254+ free(self_path);
161255 return 1;
162256 }
257257+ free(self_path);
163258164164- execve(SOURCE_PROG, argv, environ);
259259+ execve(source_prog, argv, environ);
165260166261 fprintf(stderr, "%s: cannot run `%s': %s\n",
167167- argv[0], SOURCE_PROG, strerror(errno));
262262+ argv[0], source_prog, strerror(errno));
168263169264 return 1;
170265}
···8484 test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -g', '0')
8585 test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -rg', '0')
86868787- # Test that in nonewprivs environment the wrappers simply exec their target.
8888- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -u', '${toString userUid}')
8989- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -ru', '${toString userUid}')
9090- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -g', '${toString usersGid}')
9191- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -rg', '${toString usersGid}')
9292-9393- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -u', '${toString userUid}')
9494- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -ru', '${toString userUid}')
9595- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -g', '${toString usersGid}')
9696- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -rg', '${toString usersGid}')
9797-9887 # We are only testing the permitted set, because it's easiest to look at with capsh.
9988 machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_CHOWN'))
10089 machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_SYS_ADMIN'))