eBPF Phase 2 Enforcement Vectors¶
Shipped in cloudtaser-ebpf v0.4.8. These four vectors extend the Phase 1 enforcement set to close attack paths that were unblocked prior to this release. Each vector has its own eBPF hook, integration test matrix, and audit event type.
Tier-gate: Epic E9 Gold
This page documents the Phase 2 enforcement vectors shipped as part of Epic E9 Gold. See eBPF Enforcement for the full 23-vector Phase 1 baseline and the configuration reference.
Summary¶
| Vector | What it blocks | Hook type | Min kernel | Anchor | Event |
|---|---|---|---|---|---|
| V1 — pidfd_getfd | Cross-process FD theft via pidfd_getfd(2) |
BPF LSM | 5.10 | Target | PIDFD_GETFD_DENIED |
| V2 — setns / mount API | Namespace switching and new mount API abuse | kprobe + BPF LSM | 5.7 | Caller | SETNS_DENIED, MOUNT_API_DENIED |
| V3 — core_pattern | Coredump redirect to attacker-controlled handler | BPF LSM + kprobe | 5.7 | Caller | COREDUMP_PATTERN_DENIED |
| V4 — bpf() tamper | BPF program detach and ID enumeration | BPF LSM + kprobe | 5.7 | Caller | BPF_TAMPER_DETECTED |
All four vectors are default-on when ENFORCE_MODE=enforce and the node kernel meets the minimum requirement. No additional Helm values are needed to activate them.
V1 — Cross-process FD theft via pidfd_getfd¶
What it defends against¶
The pidfd_getfd(2) syscall (added in Linux 5.6) lets any process with CAP_SYS_PTRACE — or running on a host with ptrace_scope=0 — duplicate a file descriptor from another process's file table in a single syscall. An attacker targeting a cloudtaser wrapper process can use this to obtain a file descriptor pointing directly at the wrapper's memfd_secret region, then mmap() it to read plaintext secrets without touching /proc/<pid>/mem or triggering a ptrace attach.
Before v0.4.8 there was no kprobe or LSM coverage for pidfd_getfd. The attack was invisible to all existing CloudTaser eBPF hooks.
When it kicks in¶
The lsm.s/pidfd_getfd hook fires synchronously inside the kernel LSM path before the file descriptor is duplicated. If the target PID is in the monitored_pids map and the agent is in enforce mode (ENFORCE_MODE=enforce), the syscall returns -EPERM and the duplication never happens.
This hook is target-anchored: the deny triggers based on which process is being stolen from, not which process is doing the stealing. This matches the existing ptrace vector anchor strategy.
Kernel requirement: Linux 5.10 (the security_pidfd_getfd() LSM hook was added then; the pidfd_getfd(2) syscall itself arrived in 5.6 but had no LSM coverage until 5.10).
Detection mode: The hook fires and emits PIDFD_GETFD_DENIED (severity CRITICAL) without blocking the syscall. This lets you audit before switching to enforce.
Verifying it is working¶
Check the agent log for a blocked event:
Expected event structure:
{
"timestamp": "2026-05-09T11:14:02.001Z",
"event": "PIDFD_GETFD_DENIED",
"action": "blocked",
"source_pid": 9981,
"source_comm": "attacker",
"target_pid": 1234,
"target_comm": "wrapper",
"syscall": "pidfd_getfd",
"node": "gke-prod-pool-abc123"
}
You can also confirm the hook is loaded:
kubectl exec -n cloudtaser-system daemonset/cloudtaser-ebpf -- \
bpftool prog list | grep pidfd_getfd
Implementation: cloudtaser-ebpf#232 — design doc: pidfd-getfd-vector.md
V2 — Namespace escape via setns and new mount API¶
What it defends against¶
setns(2) — namespace switching: An attacker can call setns(nsfd, CLONE_NEWNS) to switch into the mount namespace of a monitored wrapper process. Once inside the target's mount namespace, the attacker gains visibility to bind-mounts and tmpfs volumes that were invisible from their original namespace, including any paths where the wrapper may have written intermediate state.
New mount API (open_tree, move_mount, fsmount): Linux 5.2 introduced a new programmatic mount API that bypasses the traditional mount(2) syscall path. The existing lsm/sb_mount hook covers the legacy path but not the new one. An attacker can use open_tree() to clone the entire mount tree (including /proc/<wrapper_pid>/), then move_mount() to attach the copy at a path they control, and then read /proc/<wrapper_pid>/environ or /mem from the remounted copy.
Before v0.4.8 there was no kprobe or LSM coverage for setns(2), open_tree(2), or fsmount(2).
When it kicks in¶
All hooks in this vector are caller-anchored: the deny triggers when the calling process's PID is in monitored_pids or its cgroup is in monitored_cgroups. A monitored process (e.g., a compromised application running under the wrapper) has no legitimate reason to perform namespace surgery.
| Syscall | Hook | Coverage |
|---|---|---|
setns(2) |
kprobe/__x64_sys_setns |
kprobe only — no BPF LSM hook exists through Linux 6.12 |
mount(2) (traditional) |
lsm/sb_mount |
BPF LSM — fires before superblock attach |
move_mount(2) |
lsm/move_mount + kprobe/__x64_sys_move_mount |
Dual coverage — either hook alone blocks the call |
open_tree(2) |
kprobe/__x64_sys_open_tree |
kprobe only — no BPF LSM hook through Linux 6.12 |
fsmount(2) |
kprobe/__x64_sys_fsmount |
kprobe only — no BPF LSM hook through Linux 6.12 |
setns, open_tree, and fsmount use bpf_override_return(ctx, -EPERM) to deny the call synchronously. move_mount and mount use the BPF LSM return value path (returns -EPERM).
Kernel requirement: Linux 5.7 for BPF LSM; Linux 5.2 for open_tree / move_mount / fsmount kprobes. All hooks are available on the supported kernel matrix (5.15, 6.1, 6.6, 6.12).
Verifying it is working¶
kubectl logs -n cloudtaser-system daemonset/cloudtaser-ebpf | grep -E 'SETNS_DENIED|MOUNT_API_DENIED'
Expected events:
{ "event": "SETNS_DENIED", "action": "blocked", "syscall": "setns" }
{ "event": "MOUNT_API_DENIED", "action": "blocked", "syscall": "open_tree" }
{ "event": "MOUNT_API_DENIED", "action": "blocked", "syscall": "move_mount" }
{ "event": "MOUNT_API_DENIED", "action": "blocked", "syscall": "fsmount" }
Implementation: cloudtaser-ebpf#233 — design doc: setns-mount-api-vector.md
V3 — Coredump redirect via /proc/sys/kernel/core_pattern¶
What it defends against¶
/proc/sys/kernel/core_pattern is a node-wide sysctl that controls where the kernel writes a process coredump when it crashes. An attacker with sufficient privilege can write |/attacker/handler %e %p %s to this file, redirecting coredumps to a pipe handler they control. If a monitored wrapper process subsequently crashes, the coredump is piped to the attacker's handler — including the wrapper's memfd_secret regions containing plaintext secrets.
The critical detail: core_pattern is node-wide. It overrides per-process coredump_filter settings. Any monitored process that can write this file can redirect coredumps for every process on the node, not just itself.
Before v0.4.8, the existing kprobe_openat hook blocked opens of /proc/<pid>/ paths and raw memory devices, but did not block writes to /proc/sys/kernel/core_pattern because that path is neither a PID-anchored proc path nor listed in check_dangerous_path. The gap was invisible to the existing hook.
When it kicks in¶
The hook is caller-anchored: it fires when a monitored process attempts to open /proc/sys/kernel/core_pattern or /proc/sys/kernel/core_uses_pid with write intent (O_WRONLY or O_RDWR). Read-only opens are explicitly permitted — reading the current pattern is not an attack.
Three hooks provide belt-and-suspenders coverage:
lsm.s/file_open— BPF LSM hook fires at file-open time, checksfile->f_flags, denies write-intent opens with-EACCESkprobe/__x64_sys_openat— kprobe fires at syscall entry, reads flags from syscall argument 2, denies withbpf_override_return(-EACCES)kprobe/__x64_sys_openat2— same, reads flags fromstruct open_how->flagsviabpf_probe_read_user
Any one of the three is sufficient to block the attack. The redundancy ensures the hook survives on kernels where BPF LSM is unavailable or the kprobe path is not loaded.
Kernel requirement: Linux 5.7 for BPF LSM; Linux 5.3+ for kprobes. The feature is available on all supported kernels (5.15, 6.1, 6.6, 6.12).
Verifying it is working¶
Expected event:
{
"timestamp": "2026-05-09T11:14:05.113Z",
"event": "COREDUMP_PATTERN_DENIED",
"action": "blocked",
"source_pid": 8823,
"source_comm": "attacker",
"syscall": "openat",
"path": "/proc/sys/kernel/core_pattern",
"flags": "O_WRONLY",
"node": "gke-prod-pool-abc123"
}
To confirm the hook blocks writes but not reads, run from inside a monitored pod:
# This should be denied (write intent):
kubectl exec <protected-pod> -- sh -c 'echo "|/tmp/handler" > /proc/sys/kernel/core_pattern'
# Expected: sh: /proc/sys/kernel/core_pattern: Permission denied
# This should succeed (read-only):
kubectl exec <protected-pod> -- cat /proc/sys/kernel/core_pattern
Implementation: cloudtaser-ebpf#234 — design doc: core-pattern-vector.md
V4 — BPF tamper via BPF_PROG_DETACH and ID enumeration¶
What it defends against¶
Before v0.4.8, the existing lsm.s/bpf and kprobe_bpf hooks blocked BPF_PROG_LOAD (preventing a monitored process from loading new eBPF programs) and BPF_MAP_CREATE. However, five additional bpf(2) commands were unblocked:
| Command | Value | What an attacker can do |
|---|---|---|
BPF_PROG_GET_NEXT_ID |
11 | Enumerate all loaded BPF program IDs — locates CloudTaser enforcement programs by scanning |
BPF_MAP_GET_NEXT_ID |
12 | Enumerate all loaded BPF map IDs — locates enforcement maps (e.g., monitored_pids) |
BPF_PROG_GET_FD_BY_ID |
13 | Open a file descriptor to any loaded program by ID — prerequisite for detach |
BPF_OBJ_GET |
7 | Open a pinned BPF object by filesystem path — reads enforcement program and map state |
BPF_PROG_DETACH |
15 | Detach a BPF program from a cgroup attachment point — directly removes enforcement hooks |
The attack chain: enumerate program IDs → open an FD to the CloudTaser enforcement program → detach it from the cgroup. After a successful detach, the entire enforcement boundary is gone — /proc/<wrapper_pid>/environ, ptrace, and all other monitored paths become accessible again.
When it kicks in¶
The hook is caller-anchored: the deny triggers when the calling process is monitored (its PID is in monitored_pids or its cgroup is in monitored_cgroups). The CloudTaser agent itself is an unmonitored caller and is not affected — it needs BPF map operations (BPF_MAP_LOOKUP_ELEM, BPF_MAP_UPDATE_ELEM, BPF_MAP_DELETE_ELEM, BPF_OBJ_PIN) to function and those commands are not blocked.
Three hooks provide coverage:
lsm.s/bpf— BPF LSM hook is extended with the five tamper commands in addition to the existingBPF_PROG_LOADfilter. Returns-EPERMfrom any monitored caller.kprobe/__x64_sys_bpf— kprobe gets the same tamper filter. Covers kernels without BPF LSM.trace_bpf_entertracepoint — detection-only; emitsBPF_TAMPER_DETECTEDevents even on systems in alert-only mode, enabling audit before enforcement is enabled.
Kernel requirement: Linux 5.7 for BPF LSM; Linux 5.3+ for kprobe. Available on all supported kernels (5.15, 6.1, 6.6, 6.12).
Verifying it is working¶
Expected event (in both detect and enforce modes):
{
"timestamp": "2026-05-09T11:14:07.885Z",
"event": "BPF_TAMPER_DETECTED",
"action": "blocked",
"source_pid": 7741,
"source_comm": "attacker",
"syscall": "bpf",
"bpf_cmd": "BPF_PROG_DETACH",
"node": "gke-prod-pool-abc123"
}
To confirm enumeration is blocked from inside a monitored pod:
kubectl exec <protected-pod> -- sh -c 'bpftool prog list 2>&1'
# Expected: Error: can't get next program: Operation not permitted
Note: bpftool may not be present in your application container. The underlying bpf(BPF_PROG_GET_NEXT_ID, ...) syscall is what is blocked — any tool or code that calls it from a monitored process receives -EPERM.
Implementation: cloudtaser-ebpf#235 — design doc: bpf-tamper-vector.md
Kernel version matrix¶
| Hook | Feature | Min kernel | GKE Ubuntu 6.8 | GKE COS 5.15 | EKS AL2023 6.1 | AKS Ubuntu 22.04 5.15 |
|---|---|---|---|---|---|---|
lsm.s/pidfd_getfd |
V1 | 5.10 | Yes | Yes | Yes | Yes |
kprobe/__x64_sys_setns |
V2 setns | 5.7 | Yes | Yes | Yes | Yes |
lsm/sb_mount |
V2 mount | 5.7 | Yes | Yes | Yes | Yes |
lsm/move_mount |
V2 move_mount | 5.7 | Yes | Yes | Yes | Yes |
kprobe/__x64_sys_open_tree |
V2 open_tree | 5.7 | Yes | Yes | Yes | Yes |
kprobe/__x64_sys_fsmount |
V2 fsmount | 5.7 | Yes | Yes | Yes | Yes |
lsm.s/file_open (core_pattern) |
V3 | 5.7 | Yes | Yes | Yes | Yes |
kprobe/__x64_sys_openat (core_pattern) |
V3 | 5.3 | Yes | Yes | Yes | Yes |
lsm.s/bpf (tamper ext.) |
V4 | 5.7 | Yes | Yes | Yes | Yes |
kprobe/__x64_sys_bpf (tamper ext.) |
V4 | 5.3 | Yes | Yes | Yes | Yes |
All Phase 2 hooks are available on every kernel in the supported compatibility matrix. There are no Phase 2 features that require kernel 6.x or a custom kernel build.
Updated enforcement count¶
With Phase 2 shipped in v0.4.8, the total enforcement vector count increases from 23 to 27:
| Phase | Vectors | Release |
|---|---|---|
| Phase 1 | 23 | v0.1.x – v0.4.7 |
| Phase 2 (this page) | +4 | v0.4.8 |
| Total | 27 | v0.4.8+ |
Related pages¶
- eBPF Enforcement (Phase 1) — full 23-vector baseline, enforcement modes, event format
- Memory Protection — memfd_secret, dumpable=0, /proc hardening
- Root Attack Surface — what cloudtaser does and does not protect against
- eBPF Agent Configuration — ENFORCE_MODE, REACTIVE_KILL, Helm values
- Kernel Compatibility — managed K8s kernel matrix