mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-21 20:18:06 +02:00
[pitboss] phase 17: Track E.1 — Linux process backend hardening
This commit is contained in:
parent
a4f890797a
commit
dbad78fafa
10 changed files with 2414 additions and 68 deletions
124
tests/dynamic_fixtures/hardening/probe.c
Normal file
124
tests/dynamic_fixtures/hardening/probe.c
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Phase 17 (Track E.1) — process-backend hardening probe.
|
||||
*
|
||||
* Linked statically (no glibc dynamic loader needed) so it runs after
|
||||
* `chroot(workdir)` strips access to /usr/lib. Reads its own
|
||||
* `/proc/self` view to determine which Phase 17 primitives applied,
|
||||
* then prints a structured `key:value` line per primitive. The Rust
|
||||
* test reads stdout and asserts on each line.
|
||||
*
|
||||
* The probe is also reused by the path-traversal case: when
|
||||
* `argv[1] == "traverse"` it tries to open `/etc/passwd` and reports
|
||||
* either `chroot blocked` (open failed) or `chroot escaped` (open
|
||||
* succeeded, host file visible).
|
||||
*
|
||||
* Built at test runtime with `cc -static -O2 -o probe probe.c`. Test
|
||||
* skips with an eprintln! when the host has no `cc` or no static glibc.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static void grep_status(const char *needle, const char *fallback) {
|
||||
FILE *f = fopen("/proc/self/status", "r");
|
||||
if (!f) {
|
||||
printf("%s%s\n", needle, fallback);
|
||||
return;
|
||||
}
|
||||
char line[512];
|
||||
int found = 0;
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
if (strncmp(line, needle, strlen(needle)) == 0) {
|
||||
// Strip trailing newline.
|
||||
size_t n = strlen(line);
|
||||
if (n && line[n - 1] == '\n') line[n - 1] = '\0';
|
||||
printf("%s\n", line);
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) printf("%s%s\n", needle, fallback);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
static void print_rlimit(const char *tag, int resource) {
|
||||
struct rlimit rl;
|
||||
if (getrlimit(resource, &rl) == 0) {
|
||||
printf("%s:%llu/%llu\n", tag,
|
||||
(unsigned long long)rl.rlim_cur,
|
||||
(unsigned long long)rl.rlim_max);
|
||||
} else {
|
||||
printf("%s:err\n", tag);
|
||||
}
|
||||
}
|
||||
|
||||
static void probe_namespaces(void) {
|
||||
// /proc/self/ns/user, /proc/self/ns/pid, /proc/self/ns/mnt are
|
||||
// symlinks like `user:[4026531837]`. We read the link target and
|
||||
// print the inode-id portion.
|
||||
const char *names[] = {"user", "pid", "mnt"};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
char path[64];
|
||||
char target[256];
|
||||
snprintf(path, sizeof(path), "/proc/self/ns/%s", names[i]);
|
||||
ssize_t n = readlink(path, target, sizeof(target) - 1);
|
||||
if (n > 0) {
|
||||
target[n] = '\0';
|
||||
printf("ns_%s:%s\n", names[i], target);
|
||||
} else {
|
||||
printf("ns_%s:err\n", names[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void probe_chroot(void) {
|
||||
// After chroot(workdir), `/etc/passwd` should not exist (the harness
|
||||
// workdir does not contain /etc). Open + ENOENT means chroot held.
|
||||
int fd = open("/etc/passwd", O_RDONLY);
|
||||
if (fd < 0) {
|
||||
printf("chroot:blocked errno=%d\n", errno);
|
||||
} else {
|
||||
char buf[64];
|
||||
ssize_t n = read(fd, buf, sizeof(buf) - 1);
|
||||
close(fd);
|
||||
if (n > 0) {
|
||||
buf[n] = '\0';
|
||||
printf("chroot:escaped read=%zd\n", n);
|
||||
} else {
|
||||
printf("chroot:escaped read=0\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
grep_status("NoNewPrivs:", "\t?");
|
||||
grep_status("Seccomp:", "\t?");
|
||||
print_rlimit("rlimit_as", RLIMIT_AS);
|
||||
print_rlimit("rlimit_cpu", RLIMIT_CPU);
|
||||
print_rlimit("rlimit_nofile", RLIMIT_NOFILE);
|
||||
probe_namespaces();
|
||||
probe_chroot();
|
||||
|
||||
if (argc > 1 && strcmp(argv[1], "traverse") == 0) {
|
||||
// Path-traversal acceptance case: a payload that tries to read
|
||||
// /etc/passwd outside the workdir. Exit non-zero so the verifier
|
||||
// records NotConfirmed; the probe-level "chroot blocked" line
|
||||
// already printed above is what the test asserts on.
|
||||
if (open("/etc/passwd", O_RDONLY) >= 0) {
|
||||
// chroot did not hold — exit 0 to signal escape (test fails).
|
||||
printf("traverse:escaped\n");
|
||||
return 0;
|
||||
}
|
||||
printf("traverse:blocked\n");
|
||||
return 7;
|
||||
}
|
||||
|
||||
printf("__NYX_PROBE_DONE__\n");
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue