Revert "nixos/security/wrappers: simplifications and a fix for #98863"
This commit is contained in:
parent
64d0bc674f
commit
4428f3a79a
@ -5,8 +5,8 @@ let
|
||||
|
||||
parentWrapperDir = dirOf wrapperDir;
|
||||
|
||||
securityWrapper = sourceProg : pkgs.callPackage ./wrapper.nix {
|
||||
inherit sourceProg;
|
||||
securityWrapper = pkgs.callPackage ./wrapper.nix {
|
||||
inherit parentWrapperDir;
|
||||
};
|
||||
|
||||
fileModeType =
|
||||
@ -91,7 +91,8 @@ let
|
||||
, ...
|
||||
}:
|
||||
''
|
||||
cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}"
|
||||
cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}"
|
||||
echo -n "${source}" > "$wrapperDir/${program}.real"
|
||||
|
||||
# Prevent races
|
||||
chmod 0000 "$wrapperDir/${program}"
|
||||
@ -118,7 +119,8 @@ let
|
||||
, ...
|
||||
}:
|
||||
''
|
||||
cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}"
|
||||
cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}"
|
||||
echo -n "${source}" > "$wrapperDir/${program}.real"
|
||||
|
||||
# Prevent races
|
||||
chmod 0000 "$wrapperDir/${program}"
|
||||
|
@ -17,10 +17,6 @@
|
||||
#include <syscall.h>
|
||||
#include <byteswap.h>
|
||||
|
||||
#ifndef SOURCE_PROG
|
||||
#error SOURCE_PROG should be defined via preprocessor commandline
|
||||
#endif
|
||||
|
||||
// aborts when false, printing the failed expression
|
||||
#define ASSERT(expr) ((expr) ? (void) 0 : assert_failure(#expr))
|
||||
// aborts when returns non-zero, printing the failed expression and errno
|
||||
@ -28,6 +24,10 @@
|
||||
|
||||
extern char **environ;
|
||||
|
||||
// The WRAPPER_DIR macro is supplied at compile time so that it cannot
|
||||
// be changed at runtime
|
||||
static char *wrapper_dir = WRAPPER_DIR;
|
||||
|
||||
// Wrapper debug variable name
|
||||
static char *wrapper_debug = "WRAPPER_DEBUG";
|
||||
|
||||
@ -151,20 +151,115 @@ static int make_caps_ambient(const char *self_path) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int readlink_malloc(const char *p, char **ret) {
|
||||
size_t l = FILENAME_MAX+1;
|
||||
int r;
|
||||
|
||||
for (;;) {
|
||||
char *c = calloc(l, sizeof(char));
|
||||
if (!c) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ssize_t n = readlink(p, c, l-1);
|
||||
if (n < 0) {
|
||||
r = -errno;
|
||||
free(c);
|
||||
return r;
|
||||
}
|
||||
|
||||
if ((size_t) n < l-1) {
|
||||
c[n] = 0;
|
||||
*ret = c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
free(c);
|
||||
l *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
fprintf(stderr, "cannot readlink /proc/self/exe: %s", strerror(-self_path_size));
|
||||
}
|
||||
|
||||
unsigned int ruid, euid, suid, rgid, egid, sgid;
|
||||
MUSTSUCCEED(getresuid(&ruid, &euid, &suid));
|
||||
MUSTSUCCEED(getresgid(&rgid, &egid, &sgid));
|
||||
|
||||
// If true, then we did not benefit from setuid privilege escalation,
|
||||
// where the original uid is still in ruid and different from euid == suid.
|
||||
int didnt_suid = (ruid == euid) && (euid == suid);
|
||||
// If true, then we did not benefit from setgid privilege escalation
|
||||
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
|
||||
// `X.real' file, to allow arbitrary programs from being executed
|
||||
// with elevated capabilities.
|
||||
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]);
|
||||
|
||||
// If we got privileges with the fs set[ug]id bit, check that the privilege we
|
||||
// got matches the one one we expected, ie that our effective uid/gid
|
||||
// matches the uid/gid of `self_path`. This ensures that we were executed as
|
||||
// `self_path', and not, say, as some other setuid program.
|
||||
// We don't check that if we did not benefit from the set[ug]id bit, as
|
||||
// can be the case in nosuid mounts or user namespaces.
|
||||
struct stat st;
|
||||
ASSERT(lstat(self_path, &st) != -1);
|
||||
|
||||
// if the wrapper gained privilege with suid, check that we got the uid of the file owner
|
||||
ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == euid));
|
||||
// if the wrapper gained privilege with sgid, check that we got the gid of the file group
|
||||
ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == egid));
|
||||
// same, but with suid instead of euid
|
||||
ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == suid));
|
||||
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)));
|
||||
|
||||
// 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));
|
||||
|
||||
int fd_self = open(real_fn, O_RDONLY);
|
||||
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);
|
||||
source_prog[len] = 0;
|
||||
|
||||
close(fd_self);
|
||||
|
||||
// Read the capabilities set on the wrapper and raise them in to
|
||||
// the ambient set so the program we're wrapping receives the
|
||||
// capabilities too!
|
||||
if (make_caps_ambient("/proc/self/exe") != 0) {
|
||||
if (make_caps_ambient(self_path) != 0) {
|
||||
free(self_path);
|
||||
return 1;
|
||||
}
|
||||
free(self_path);
|
||||
|
||||
execve(SOURCE_PROG, argv, environ);
|
||||
execve(source_prog, argv, environ);
|
||||
|
||||
fprintf(stderr, "%s: cannot run `%s': %s\n",
|
||||
argv[0], SOURCE_PROG, strerror(errno));
|
||||
argv[0], source_prog, strerror(errno));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{ stdenv, linuxHeaders, sourceProg, debug ? false }:
|
||||
{ stdenv, linuxHeaders, parentWrapperDir, debug ? false }:
|
||||
# For testing:
|
||||
# $ nix-build -E 'with import <nixpkgs> {}; pkgs.callPackage ./wrapper.nix { parentWrapperDir = "/run/wrappers"; debug = true; }'
|
||||
stdenv.mkDerivation {
|
||||
@ -7,7 +7,7 @@ stdenv.mkDerivation {
|
||||
dontUnpack = true;
|
||||
hardeningEnable = [ "pie" ];
|
||||
CFLAGS = [
|
||||
''-DSOURCE_PROG="${sourceProg}"''
|
||||
''-DWRAPPER_DIR="${parentWrapperDir}"''
|
||||
] ++ (if debug then [
|
||||
"-Werror" "-Og" "-g"
|
||||
] else [
|
||||
|
@ -84,17 +84,6 @@ in
|
||||
test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -g', '0')
|
||||
test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -rg', '0')
|
||||
|
||||
# Test that in nonewprivs environment the wrappers simply exec their target.
|
||||
test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -u', '${toString userUid}')
|
||||
test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -ru', '${toString userUid}')
|
||||
test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -g', '${toString usersGid}')
|
||||
test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -rg', '${toString usersGid}')
|
||||
|
||||
test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -u', '${toString userUid}')
|
||||
test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -ru', '${toString userUid}')
|
||||
test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -g', '${toString usersGid}')
|
||||
test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -rg', '${toString usersGid}')
|
||||
|
||||
# We are only testing the permitted set, because it's easiest to look at with capsh.
|
||||
machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_CHOWN'))
|
||||
machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_SYS_ADMIN'))
|
||||
|
Loading…
Reference in New Issue
Block a user