Merge pull request #231673 from symphorien/suid_wrappers_userns
This commit is contained in:
commit
ec409e6f79
2 changed files with 55 additions and 6 deletions
|
@ -1,3 +1,4 @@
|
|||
#define _GNU_SOURCE
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
@ -16,7 +17,10 @@
|
|||
#include <syscall.h>
|
||||
#include <byteswap.h>
|
||||
|
||||
// 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
|
||||
#define MUSTSUCCEED(expr) ((expr) ? print_errno_and_die(#expr) : (void) 0)
|
||||
|
||||
extern char **environ;
|
||||
|
||||
|
@ -41,6 +45,12 @@ static noreturn void assert_failure(const char *assertion) {
|
|||
abort();
|
||||
}
|
||||
|
||||
static noreturn void print_errno_and_die(const char *assertion) {
|
||||
fprintf(stderr, "Call `%s` in NixOS's wrapper.c failed: %s\n", assertion, strerror(errno));
|
||||
fflush(stderr);
|
||||
abort();
|
||||
}
|
||||
|
||||
int get_last_cap(unsigned *last_cap) {
|
||||
FILE* file = fopen("/proc/sys/kernel/cap_last_cap", "r");
|
||||
if (file == NULL) {
|
||||
|
@ -177,6 +187,17 @@ int main(int argc, char **argv) {
|
|||
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
|
||||
|
@ -189,15 +210,22 @@ int main(int argc, char **argv) {
|
|||
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'.
|
||||
// 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);
|
||||
|
||||
ASSERT(!(st.st_mode & S_ISUID) || (st.st_uid == geteuid()));
|
||||
ASSERT(!(st.st_mode & S_ISGID) || (st.st_gid == getegid()));
|
||||
// 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)));
|
||||
|
|
|
@ -55,6 +55,10 @@ in
|
|||
out = machine.succeed(cmd_as_regular(cmd)).strip()
|
||||
assert out == expected, "Expected {0} to output {1}, but got {2}".format(cmd, expected, out)
|
||||
|
||||
def test_as_regular_in_userns_mapped_as_root(cmd, expected):
|
||||
out = machine.succeed(f"su -l regular -c '${pkgs.util-linux}/bin/unshare -rm {cmd}'").strip()
|
||||
assert out == expected, "Expected {0} to output {1}, but got {2}".format(cmd, expected, out)
|
||||
|
||||
test_as_regular('${busybox pkgs}/bin/busybox id -u', '${toString userUid}')
|
||||
test_as_regular('${busybox pkgs}/bin/busybox id -ru', '${toString userUid}')
|
||||
test_as_regular('${busybox pkgs}/bin/busybox id -g', '${toString usersGid}')
|
||||
|
@ -70,10 +74,27 @@ in
|
|||
test_as_regular('/run/wrappers/bin/sgid_root_busybox id -g', '0')
|
||||
test_as_regular('/run/wrappers/bin/sgid_root_busybox id -rg', '${toString usersGid}')
|
||||
|
||||
test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -u', '0')
|
||||
test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -ru', '0')
|
||||
test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -g', '0')
|
||||
test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -rg', '0')
|
||||
|
||||
test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -u', '0')
|
||||
test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -ru', '0')
|
||||
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')
|
||||
|
||||
# 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'))
|
||||
machine.succeed(cmd_as_regular('/run/wrappers/bin/capsh_with_chown --has-p=CAP_CHOWN'))
|
||||
machine.fail(cmd_as_regular('/run/wrappers/bin/capsh_with_chown --has-p=CAP_SYS_ADMIN'))
|
||||
|
||||
# test a few "attacks" against which the wrapper protects itself
|
||||
machine.succeed("cp /run/wrappers/bin/suid_root_busybox{,.real} /tmp/")
|
||||
machine.fail(cmd_as_regular("/tmp/suid_root_busybox id -u"))
|
||||
|
||||
machine.succeed("chmod u+s,a+w /run/wrappers/bin/suid_root_busybox")
|
||||
machine.fail(cmd_as_regular("/run/wrappers/bin/suid_root_busybox id -u"))
|
||||
'';
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue