···17#include <syscall.h>
18#include <byteswap.h>
1920-#ifndef SOURCE_PROG
21-#error SOURCE_PROG should be defined via preprocessor commandline
22-#endif
23-24// aborts when false, printing the failed expression
25#define ASSERT(expr) ((expr) ? (void) 0 : assert_failure(#expr))
26// aborts when returns non-zero, printing the failed expression and errno
27#define MUSTSUCCEED(expr) ((expr) ? print_errno_and_die(#expr) : (void) 0)
2829extern char **environ;
00003031// Wrapper debug variable name
32static char *wrapper_debug = "WRAPPER_DEBUG";
···151 return 0;
152}
1530000000000000000000000000000154int main(int argc, char **argv) {
155 ASSERT(argc >= 1);
00000000000000000000000000000000000000000000000000000000000000000156157 // Read the capabilities set on the wrapper and raise them in to
158 // the ambient set so the program we're wrapping receives the
159 // capabilities too!
160- if (make_caps_ambient("/proc/self/exe") != 0) {
0161 return 1;
162 }
0163164- execve(SOURCE_PROG, argv, environ);
165166 fprintf(stderr, "%s: cannot run `%s': %s\n",
167- argv[0], SOURCE_PROG, strerror(errno));
168169 return 1;
170}
···17#include <syscall.h>
18#include <byteswap.h>
19000020// aborts when false, printing the failed expression
21#define ASSERT(expr) ((expr) ? (void) 0 : assert_failure(#expr))
22// aborts when returns non-zero, printing the failed expression and errno
23#define MUSTSUCCEED(expr) ((expr) ? print_errno_and_die(#expr) : (void) 0)
2425extern char **environ;
26+27+// The WRAPPER_DIR macro is supplied at compile time so that it cannot
28+// be changed at runtime
29+static char *wrapper_dir = WRAPPER_DIR;
3031// Wrapper debug variable name
32static char *wrapper_debug = "WRAPPER_DEBUG";
···151 return 0;
152}
153154+int readlink_malloc(const char *p, char **ret) {
155+ size_t l = FILENAME_MAX+1;
156+ int r;
157+158+ for (;;) {
159+ char *c = calloc(l, sizeof(char));
160+ if (!c) {
161+ return -ENOMEM;
162+ }
163+164+ ssize_t n = readlink(p, c, l-1);
165+ if (n < 0) {
166+ r = -errno;
167+ free(c);
168+ return r;
169+ }
170+171+ if ((size_t) n < l-1) {
172+ c[n] = 0;
173+ *ret = c;
174+ return 0;
175+ }
176+177+ free(c);
178+ l *= 2;
179+ }
180+}
181+182int main(int argc, char **argv) {
183 ASSERT(argc >= 1);
184+ char *self_path = NULL;
185+ int self_path_size = readlink_malloc("/proc/self/exe", &self_path);
186+ if (self_path_size < 0) {
187+ fprintf(stderr, "cannot readlink /proc/self/exe: %s", strerror(-self_path_size));
188+ }
189+190+ unsigned int ruid, euid, suid, rgid, egid, sgid;
191+ MUSTSUCCEED(getresuid(&ruid, &euid, &suid));
192+ MUSTSUCCEED(getresgid(&rgid, &egid, &sgid));
193+194+ // If true, then we did not benefit from setuid privilege escalation,
195+ // where the original uid is still in ruid and different from euid == suid.
196+ int didnt_suid = (ruid == euid) && (euid == suid);
197+ // If true, then we did not benefit from setgid privilege escalation
198+ int didnt_sgid = (rgid == egid) && (egid == sgid);
199+200+201+ // Make sure that we are being executed from the right location,
202+ // i.e., `safe_wrapper_dir'. This is to prevent someone from creating
203+ // hard link `X' from some other location, along with a false
204+ // `X.real' file, to allow arbitrary programs from being executed
205+ // with elevated capabilities.
206+ int len = strlen(wrapper_dir);
207+ if (len > 0 && '/' == wrapper_dir[len - 1])
208+ --len;
209+ ASSERT(!strncmp(self_path, wrapper_dir, len));
210+ ASSERT('/' == wrapper_dir[0]);
211+ ASSERT('/' == self_path[len]);
212+213+ // If we got privileges with the fs set[ug]id bit, check that the privilege we
214+ // got matches the one one we expected, ie that our effective uid/gid
215+ // matches the uid/gid of `self_path`. This ensures that we were executed as
216+ // `self_path', and not, say, as some other setuid program.
217+ // We don't check that if we did not benefit from the set[ug]id bit, as
218+ // can be the case in nosuid mounts or user namespaces.
219+ struct stat st;
220+ ASSERT(lstat(self_path, &st) != -1);
221+222+ // if the wrapper gained privilege with suid, check that we got the uid of the file owner
223+ ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == euid));
224+ // if the wrapper gained privilege with sgid, check that we got the gid of the file group
225+ ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == egid));
226+ // same, but with suid instead of euid
227+ ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == suid));
228+ ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == sgid));
229+230+ // And, of course, we shouldn't be writable.
231+ ASSERT(!(st.st_mode & (S_IWGRP | S_IWOTH)));
232+233+ // Read the path of the real (wrapped) program from <self>.real.
234+ char real_fn[PATH_MAX + 10];
235+ int real_fn_size = snprintf(real_fn, sizeof(real_fn), "%s.real", self_path);
236+ ASSERT(real_fn_size < sizeof(real_fn));
237+238+ int fd_self = open(real_fn, O_RDONLY);
239+ ASSERT(fd_self != -1);
240+241+ char source_prog[PATH_MAX];
242+ len = read(fd_self, source_prog, PATH_MAX);
243+ ASSERT(len != -1);
244+ ASSERT(len < sizeof(source_prog));
245+ ASSERT(len > 0);
246+ source_prog[len] = 0;
247+248+ close(fd_self);
249250 // Read the capabilities set on the wrapper and raise them in to
251 // the ambient set so the program we're wrapping receives the
252 // capabilities too!
253+ if (make_caps_ambient(self_path) != 0) {
254+ free(self_path);
255 return 1;
256 }
257+ free(self_path);
258259+ execve(source_prog, argv, environ);
260261 fprintf(stderr, "%s: cannot run `%s': %s\n",
262+ argv[0], source_prog, strerror(errno));
263264 return 1;
265}
···84 test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -g', '0')
85 test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -rg', '0')
8687- # Test that in nonewprivs environment the wrappers simply exec their target.
88- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -u', '${toString userUid}')
89- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -ru', '${toString userUid}')
90- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -g', '${toString usersGid}')
91- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -rg', '${toString usersGid}')
92-93- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -u', '${toString userUid}')
94- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -ru', '${toString userUid}')
95- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -g', '${toString usersGid}')
96- test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -rg', '${toString usersGid}')
97-98 # We are only testing the permitted set, because it's easiest to look at with capsh.
99 machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_CHOWN'))
100 machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_SYS_ADMIN'))
···84 test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -g', '0')
85 test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -rg', '0')
860000000000087 # We are only testing the permitted set, because it's easiest to look at with capsh.
88 machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_CHOWN'))
89 machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_SYS_ADMIN'))