Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 21 additions & 17 deletions nixos/modules/security/wrappers/wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdnoreturn.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <fcntl.h>
#include <dirent.h>
#include <assert.h>
#include <errno.h>
#include <linux/capability.h>
#include <sys/prctl.h>
Expand All @@ -16,10 +16,7 @@
#include <syscall.h>
#include <byteswap.h>

// Make sure assertions are not compiled out, we use them to codify
// invariants about this program and we want it to fail fast and
// loudly if they are violated.
#undef NDEBUG
#define ASSERT(expr) ((expr) ? (void) 0 : assert_failure(#expr))

extern char **environ;

Expand All @@ -38,6 +35,12 @@ static char *wrapper_debug = "WRAPPER_DEBUG";
#define LE32_TO_H(x) (x)
#endif

static noreturn void assert_failure(const char *assertion) {
fprintf(stderr, "Assertion `%s` in NixOS's wrapper.c failed.\n", assertion);
fflush(stderr);
abort();
}

int get_last_cap(unsigned *last_cap) {
FILE* file = fopen("/proc/sys/kernel/cap_last_cap", "r");
if (file == NULL) {
Expand Down Expand Up @@ -167,6 +170,7 @@ int readlink_malloc(const char *p, char **ret) {
}

int main(int argc, char **argv) {
ASSERT(argc >= 1);
char *self_path = NULL;
int self_path_size = readlink_malloc("/proc/self/exe", &self_path);
if (self_path_size < 0) {
Expand All @@ -181,36 +185,36 @@ int main(int argc, char **argv) {
int len = strlen(wrapper_dir);
if (len > 0 && '/' == wrapper_dir[len - 1])
--len;
assert(!strncmp(self_path, wrapper_dir, len));
assert('/' == wrapper_dir[0]);
assert('/' == self_path[len]);
ASSERT(!strncmp(self_path, wrapper_dir, len));
ASSERT('/' == wrapper_dir[0]);
ASSERT('/' == self_path[len]);

// Make *really* *really* sure that we were executed as
// `self_path', and not, say, as some other setuid program. That
// is, our effective uid/gid should match the uid/gid of
// `self_path'.
struct stat st;
assert(lstat(self_path, &st) != -1);
ASSERT(lstat(self_path, &st) != -1);

assert(!(st.st_mode & S_ISUID) || (st.st_uid == geteuid()));
assert(!(st.st_mode & S_ISGID) || (st.st_gid == getegid()));
ASSERT(!(st.st_mode & S_ISUID) || (st.st_uid == geteuid()));
ASSERT(!(st.st_mode & S_ISGID) || (st.st_gid == getegid()));

// And, of course, we shouldn't be writable.
assert(!(st.st_mode & (S_IWGRP | S_IWOTH)));
ASSERT(!(st.st_mode & (S_IWGRP | S_IWOTH)));

// Read the path of the real (wrapped) program from <self>.real.
char real_fn[PATH_MAX + 10];
int real_fn_size = snprintf(real_fn, sizeof(real_fn), "%s.real", self_path);
assert(real_fn_size < sizeof(real_fn));
ASSERT(real_fn_size < sizeof(real_fn));

int fd_self = open(real_fn, O_RDONLY);
assert(fd_self != -1);
ASSERT(fd_self != -1);

char source_prog[PATH_MAX];
len = read(fd_self, source_prog, PATH_MAX);
assert(len != -1);
assert(len < sizeof(source_prog));
assert(len > 0);
ASSERT(len != -1);
ASSERT(len < sizeof(source_prog));
ASSERT(len > 0);
source_prog[len] = 0;

close(fd_self);
Expand Down