Created
October 3, 2020 03:11
-
-
Save snarkmaster/4d019ab39e9adbe4fd7269f4f9812692 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This is a unfinished program that's part of a `libcap-ng` bug report. | |
The bug repro looks like this -- the target PID 3554846 is just a random | |
unprivileged process: | |
echo target: && | |
grep ^Cap /proc/3554846/status && | |
sudo capsh --drop=cap_chown -- -c ' | |
echo under capsh: && | |
/usr/bin/grep ^Cap /proc/self/status && | |
./clonecaps 3554846 -- \ | |
/usr/bin/grep ^Cap /proc/self/status' | |
The output looks like this (extra newlines for readability): | |
target: | |
CapInh: 0000000000000000 | |
CapPrm: 0000000000000000 | |
CapEff: 0000000000000000 | |
CapBnd: 0000003fffffffff | |
CapAmb: 0000000000000000 | |
under capsh: | |
CapInh: 0000000000000000 | |
CapPrm: 0000003ffffffffe | |
CapEff: 0000003ffffffffe | |
CapBnd: 0000003ffffffffe | |
CapAmb: 0000000000000000 | |
XXX procfs CapInh: 0000000000000000 | |
XXX procfs CapPrm: 0000003ffffffffe | |
XXX procfs CapEff: 0000003ffffffffe | |
XXX procfs CapBnd: 0000003ffffffffe | |
XXX procfs CapAmb: 0000000000000000 | |
XXX getpid() initial --> i 0, p 3ffffffffe, e 3ffffffffe, bs 3ffffffffe, a 0 | |
XXX procfs CapInh: 0000000000000000 | |
XXX procfs CapPrm: 0000000000000000 | |
XXX procfs CapEff: 0000000000000000 | |
XXX procfs CapBnd: 0000003fffffffff | |
XXX procfs CapAmb: 0000000000000000 | |
XXX target PID --> i 0, p 0, e 0, bs 3fffffffff, a 0 | |
XXX check 4 0 0 | |
XXX check 2 0 0 | |
XXX check 1 0 0 | |
XXX check 8 3fffffffff 3fffffffff | |
XXX check 16 0 0 | |
XXX procfs CapInh: 0000000000000000 | |
XXX procfs CapPrm: 0000000000000000 | |
XXX procfs CapEff: 0000000000000000 | |
XXX procfs CapBnd: 0000003ffffffffe | |
XXX procfs CapAmb: 0000000000000000 | |
XXX getpid() final --> i 0, p 0, e 0, bs 3ffffffffe, a 0 | |
CapInh: 0000000000000000 | |
CapPrm: 0000003ffffffffe | |
CapEff: 0000003ffffffffe | |
CapBnd: 0000003ffffffffe | |
CapAmb: 0000000000000000 | |
The `libcap-ng` bug is that the `capng_have_capability()` values shown as | |
`check` report full capabilities (ending in `fff`, even though we dropped | |
`CAP_CHOWN` via`capsh`, and thus the actual bounding set as read from | |
`procfs` (preceding "getpid() final") ended in `ffe`. | |
Besides the bug in `libcap-ng`, this has at least one bug (not handling | |
long `status` lines), and many debug prints. | |
The final variant will be part of the eventual "antlir" OSS release. | |
*/ | |
#include <cap-ng.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
// Returns the number of the last valid capability for the current kernel, | |
// or -1 on error. | |
int find_last_cap() { | |
int last_cap = -1; // Negative so we fail if `sscanf` fails | |
FILE* last_cap_file = fopen("/proc/sys/kernel/cap_last_cap", "re"); | |
if (last_cap_file == NULL) { | |
perror("open /proc/sys/kernel/cap_last_cap"); | |
return -1; | |
} | |
// No error-checking since `last_cap` will stay at -1 on failure to match. | |
fscanf(last_cap_file, "%d", &last_cap); | |
fclose(last_cap_file); | |
if (last_cap < 0 || last_cap >= 64) { | |
fprintf(stderr, "Got %d in /proc/sys/kernel/cap_last_cap\n", last_cap); | |
return -1; | |
} | |
return last_cap; | |
} | |
// Returns 1 if all `bits` were added successfully, 0 otherwise | |
int add_all_caps(int last_cap, capng_type_t cap_type, __u64 bits) { | |
for (int cap = 0; cap <= last_cap; ++cap) { | |
if (bits & ((__u64)1 << cap)) { | |
if (capng_update(CAPNG_ADD, cap_type, cap) != 0) { | |
fprintf( | |
stderr, | |
"Failed to add capability %d of capability type %d\n", | |
cap, cap_type | |
); | |
return 0; | |
} | |
} | |
} | |
return 1; | |
} | |
// Returns 1 if `expected_bits` matches the actual setting, 0 otherwise | |
int check_all_caps(int last_cap, capng_type_t cap_type, __u64 expected_bits) { | |
__u64 actual_bits = 0; | |
for (int cap = 0; cap <= last_cap; ++cap) { | |
actual_bits |= (__u64)capng_have_capability(cap_type, cap) << cap; | |
} | |
fprintf( | |
stderr, | |
"XXX check %d %llx %llx\n", | |
cap_type, actual_bits, expected_bits | |
); | |
if (actual_bits != expected_bits) { | |
fprintf( | |
stderr, | |
"Unexpected caps of type %d: actual %llx != expected %llx\n", | |
cap_type, actual_bits, expected_bits | |
); | |
return 0; | |
} | |
return 1; | |
} | |
// Detail for parsing `/proc/PID/status`. | |
int _match( | |
char* buf, const char* pref, int* cap_type, int match_type, int* pref_len | |
) { | |
*pref_len = strlen(pref); | |
if (strncmp(buf, pref, *pref_len) == 0) { | |
*cap_type = match_type; | |
return 1; | |
} | |
return 0; | |
} | |
typedef struct { | |
__u64 inheritable; | |
__u64 permitted; | |
__u64 effective; | |
__u64 bounding_set; | |
__u64 ambient; | |
} cap_bits_t; | |
// Parses the `Cap...:` lines from `/proc/pid/status` and populates `cap_bits`. | |
// Returns 1 on success. On error, logs to stderr and returns 0. | |
int read_cap_bits_for_pid(pid_t pid, cap_bits_t* cap_bits) { | |
memset(cap_bits, 0, sizeof(*cap_bits)); | |
// We'll compare these to make sure we saw all the expected procfs lines. | |
int expected_cap_types = | |
CAPNG_INHERITABLE | CAPNG_PERMITTED | CAPNG_EFFECTIVE | |
| CAPNG_BOUNDING_SET | CAPNG_AMBIENT; | |
int actual_cap_types = 0; | |
// `/proc/PID/status` would fit even with 64-bit PIDs | |
char status_filename[64]; | |
if (snprintf( | |
status_filename, sizeof(status_filename), "/proc/%d/status", pid | |
) >= sizeof(status_filename)) { | |
fprintf(stderr, "PID too long??? %d\n", pid); | |
return 1; | |
} | |
FILE* status_file = fopen(status_filename, "re"); | |
if (status_file == NULL) { | |
perror("open /proc/PID/status"); | |
return 0; | |
} | |
// Not all lines are under 64 bytes (the max length is ~unbounded thanks | |
// to groups), but Cap lines are. | |
char buf[256]; // XXX 64 | |
while (fgets(buf, sizeof(buf), status_file)) { | |
// Both values are populated by `match` | |
int cap_type = 0; | |
int pref_len = 0; | |
if (!( | |
_match(buf, "CapInh:\t", &cap_type, CAPNG_INHERITABLE, &pref_len) | |
|| _match(buf, "CapPrm:\t", &cap_type, CAPNG_PERMITTED, &pref_len) | |
|| _match(buf, "CapEff:\t", &cap_type, CAPNG_EFFECTIVE, &pref_len) | |
|| _match(buf, "CapBnd:\t", &cap_type, CAPNG_BOUNDING_SET, &pref_len) | |
|| _match(buf, "CapAmb:\t", &cap_type, CAPNG_AMBIENT, &pref_len) | |
)) { | |
// XXX for now can just crank up to 256, then | |
// if not newline-terminated XXX read until newline or eof, then skip | |
continue; | |
} | |
fprintf(stderr, "XXX procfs %s", buf); | |
// Fail on duplicate cap types in the input | |
if (actual_cap_types & cap_type) { | |
fprintf( | |
stderr, | |
"%s: Capability type %d occurred more than once\n", | |
status_filename, cap_type | |
); | |
return 0; | |
} | |
actual_cap_types |= cap_type; | |
// Read out the bits for this capability, we'll apply them later | |
char* end_of_bits = NULL; | |
__u64 bits = strtoull(buf + pref_len, &end_of_bits, 16); | |
// We should have read 16 hex bytes, terminated by a newline. | |
if ((end_of_bits - (buf + pref_len)) != 16 || end_of_bits[0] != '\n') { | |
fprintf( | |
stderr, | |
"%s: Failed to parse value %s for capability type %d\n", | |
status_filename, buf + pref_len, cap_type | |
); | |
return 0; | |
} | |
if (cap_type == CAPNG_INHERITABLE) { | |
cap_bits->inheritable = bits; | |
} else if (cap_type == CAPNG_PERMITTED) { | |
cap_bits->permitted = bits; | |
} else if (cap_type == CAPNG_EFFECTIVE) { | |
cap_bits->effective = bits; | |
} else if (cap_type == CAPNG_BOUNDING_SET) { | |
cap_bits->bounding_set = bits; | |
} else if (cap_type == CAPNG_AMBIENT) { | |
cap_bits->ambient = bits; | |
} | |
} | |
fclose(status_file); | |
if (actual_cap_types != expected_cap_types) { | |
fprintf( | |
stderr, | |
"%s: Missing capability types: %d vs %d\n", | |
status_filename, actual_cap_types, expected_cap_types | |
); | |
return 0; | |
} | |
return 1; | |
} | |
void XXX_fprint_cap_bits(FILE* file, const char* msg, cap_bits_t cap_bits) { | |
fprintf( | |
file, | |
"XXX %s --> i %llx, p %llx, e %llx, bs %llx, a %llx\n", | |
msg, | |
cap_bits.inheritable, | |
cap_bits.permitted, | |
cap_bits.effective, | |
cap_bits.bounding_set, | |
cap_bits.ambient | |
); | |
} | |
int main(int argc, char** argv) { | |
if (argc < 3 || strcmp("--", argv[2]) != 0) { | |
fprintf(stderr, "Usage: clonecaps <PID> -- /path/to/prog argv1 ...\n"); | |
return 1; | |
} | |
char* pid_arg = argv[1]; | |
argv += 3; // Skip "our" args, this is now ready to `execv`. | |
argc -= 3; | |
// Parse the target PID command-line argument. | |
char* endptr = pid_arg; | |
pid_t pid = strtol(pid_arg, &endptr, 10); | |
if (pid_arg[0] == 0 || endptr[0] != 0) { | |
fprintf(stderr, "Invalid PID: %s\n", pid_arg); | |
return 1; | |
} | |
// The running kernel may not match our compile-time headers. | |
int last_cap = find_last_cap(); | |
if (last_cap == -1) { // The function already printed the error | |
return 1; | |
} | |
cap_bits_t cap_bits; | |
if (!read_cap_bits_for_pid(getpid(), &cap_bits)) { // XXX delme | |
return 1; // An error was already printed | |
} | |
XXX_fprint_cap_bits(stderr, "getpid() initial", cap_bits); | |
if (!read_cap_bits_for_pid(pid, &cap_bits)) { | |
return 1; // An error was already printed | |
} | |
XXX_fprint_cap_bits(stderr, "target PID", cap_bits); | |
capng_clear(CAPNG_SELECT_ALL); // Clear traditional, bounding, ambient | |
// Clone the target's values | |
if (!( | |
add_all_caps(last_cap, CAPNG_INHERITABLE, cap_bits.inheritable) | |
&& add_all_caps(last_cap, CAPNG_PERMITTED, cap_bits.permitted) | |
&& add_all_caps(last_cap, CAPNG_EFFECTIVE, cap_bits.effective) | |
&& add_all_caps(last_cap, CAPNG_BOUNDING_SET, cap_bits.bounding_set) | |
&& add_all_caps(last_cap, CAPNG_AMBIENT, cap_bits.ambient) | |
)) { | |
return 1; // `add_all_caps` already printed an error | |
} | |
// Apply traditional & bounding | |
if (capng_apply(CAPNG_SELECT_ALL) != 0) { | |
fprintf(stderr, "Failed to apply new capabilities\n"); | |
return 1; | |
} | |
// Due to the following bug, ambient capabilities only get applied the | |
// second time around: https://github.com/stevegrubb/libcap-ng/issues/18 | |
if (capng_apply(CAPNG_SELECT_ALL) != 0) { | |
fprintf(stderr, "Failed to apply new capabilities a second time\n"); | |
return 1; | |
} | |
if (!( | |
check_all_caps(last_cap, CAPNG_INHERITABLE, cap_bits.inheritable) | |
&& check_all_caps(last_cap, CAPNG_PERMITTED, cap_bits.permitted) | |
&& check_all_caps(last_cap, CAPNG_EFFECTIVE, cap_bits.effective) | |
&& check_all_caps(last_cap, CAPNG_BOUNDING_SET, cap_bits.bounding_set) | |
&& check_all_caps(last_cap, CAPNG_AMBIENT, cap_bits.ambient) | |
)) { | |
return 1; // `check_all_caps` already printed an error | |
} | |
if (!read_cap_bits_for_pid(getpid(), &cap_bits)) { // XXX delme | |
return 1; // An error was already printed | |
} | |
XXX_fprint_cap_bits(stderr, "getpid() final", cap_bits); | |
execv(argv[0], argv); | |
perror("execv"); | |
return 1; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment