···
···
20
+
// aborts when false, printing the failed expression
#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)
···
static noreturn void assert_failure(const char *assertion) {
fprintf(stderr, "Assertion `%s` in NixOS's wrapper.c failed.\n", assertion);
48
+
static noreturn void print_errno_and_die(const char *assertion) {
49
+
fprintf(stderr, "Call `%s` in NixOS's wrapper.c failed: %s\n", assertion, strerror(errno));
···
fprintf(stderr, "cannot readlink /proc/self/exe: %s", strerror(-self_path_size));
190
+
unsigned int ruid, euid, suid, rgid, egid, sgid;
191
+
MUSTSUCCEED(getresuid(&ruid, &euid, &suid));
192
+
MUSTSUCCEED(getresgid(&rgid, &egid, &sgid));
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);
// Make sure that we are being executed from the right location,
// i.e., `safe_wrapper_dir'. This is to prevent someone from creating
// hard link `X' from some other location, along with a false
···
ASSERT('/' == wrapper_dir[0]);
ASSERT('/' == self_path[len]);
192
-
// Make *really* *really* sure that we were executed as
193
-
// `self_path', and not, say, as some other setuid program. That
194
-
// is, our effective uid/gid should match the uid/gid of
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.
ASSERT(lstat(self_path, &st) != -1);
199
-
ASSERT(!(st.st_mode & S_ISUID) || (st.st_uid == geteuid()));
200
-
ASSERT(!(st.st_mode & S_ISGID) || (st.st_gid == getegid()));
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));
// And, of course, we shouldn't be writable.
ASSERT(!(st.st_mode & (S_IWGRP | S_IWOTH)));