eBPF Runtime Enforcement¶
Authorised application boundary
Authorised application code is trusted. An authorised app can send out a secret it has copied or encoded. Current content checks do not provide a general guarantee against this disclosure or a malicious authorised app. Memory-read tests and a Ready agent do not prove copied/encoded-secret exfiltration prevention. Enforced NetworkPolicy can restrict destinations, but allowed traffic remains a channel. See Security Model.
The cloudtaser eBPF agent is a daemonset that deploys kernel-level enforcement programs on every node. It monitors and blocks attack vectors that could be used to extract secrets from protected processes. cloudtaser-ebpf ships 37 catalogued kprobe syscall programs in the named source inventory below.
Phase 2 enforcement (v0.4.8)
Four new vectors shipped in v0.4.8 extend coverage to pidfd_getfd FD theft, namespace escape via setns and the new mount API, coredump redirect via core_pattern, and BPF program detach. See Phase 2 Enforcement Vectors for the full write-up including verification commands and kernel requirements.
Compliance and Business Summary
Published source catalogues 37 kprobe syscall programs. Compliance evidence requires qualified runtime detection and denial on the deployed path; the catalogue alone establishes neither regulatory compliance nor disclosure prevention.
Architecture¶
┌──────────────────────────────────────────────────────────┐
│ Kubernetes Node │
│ │
│ ┌─────────────────────┐ ┌─────────────────────────┐ │
│ │ cloudtaser eBPF Pod │ │ Protected Application │ │
│ │ │ │ Pod │ │
│ │ Agent (user-space) │ │ ┌───────────────────┐ │ │
│ │ - Cgroup tracking │ │ │ wrapper (PID 1) │ │ │
│ │ - Event logging │ │ │ secrets in memfd │ │ │
│ │ - Policy decisions │ │ └───────┬───────────┘ │ │
│ │ ▲ │ │ │ │ │
│ └─────────┼───────────┘ │ ┌───────▼───────────┐ │ │
│ │ │ │ application │ │ │
│ │ │ └───────────────────┘ │ │
│ ──────────┼────────────────┼─────────────────────────┼──│
│ Kernel │ │ │
│ ┌─────────▼──────────────────────────────────────────┐ │
│ │ eBPF Programs │ │
│ │ - kprobe/openat2 (file access control) │ │
│ │ - kprobe/write, writev (exfiltration block) │ │
│ │ - kprobe/sendto, sendmsg (network block) │ │
│ │ - kprobe/ptrace (debug prevention) │ │
│ │ - kprobe/process_vm_readv (cross-process block) │ │
│ │ - kprobe/init_module (privesc detection) │ │
│ │ - 35 attachment points │ │
│ └────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
The agent operates in two modes:
- Pod-cgroup enforcement (v0.1.59+) -- blocks syscalls from/targeting tasks running inside protected pod cgroups, identified via
BPF_MAP_TYPE_CGROUP_ARRAYandbpf_current_task_under_cgroup. This replaces the earlier PID-set model and correctly handles kernel-namespace ID mismatches seen on kernel 6.17+ with cgroupns tasks. - Global privilege escalation detection -- detects dangerous operations from any process on the node
Cgroup-array enforcement model (v0.1.59+)
Starting with cloudtaser-ebpf v0.1.59 (helm 1.0.61), the agent switched from an inode-based monitored_cgroups HASH map to BPF_MAP_TYPE_CGROUP_ARRAY with bpf_current_task_under_cgroup. This is architecturally cleaner and fixes the kernel-namespace ID mismatch that affected cgroupns-isolated tasks on kernel 6.17+. The user-visible change: cgroup membership is now checked by array lookup rather than inode comparison. The enforcement outcome (block or kill) is identical.
Enforcement Modes¶
The agent reports its active enforcement mode through its /status endpoint (enforcement_mode). There are four modes; which one is active depends on kernel capabilities and configuration. The first three are progressively weaker enforcement tiers; the fourth is no enforcement at all.
enforcement_mode |
Primitive | Kernel requirement | What it covers | When it is active |
|---|---|---|---|---|
full |
kprobe override (bpf_override_return()) for synchronous block, plus BPF LSM hooks where both attach |
CONFIG_BPF_KPROBE_OVERRIDE=y (and typically CONFIG_BPF_LSM=y) |
Synchronous in-kernel deny on every covered vector before the syscall executes | Only where active BPF LSM and required synchronous paths are verified; stock AKS images are not qualified |
lsm |
BPF LSM hooks only (synchronous in-kernel deny) | CONFIG_BPF_LSM=y with bpf in the boot LSM stack |
Synchronous deny on LSM-reachable vectors; the few kprobe-only vectors have no synchronous deny. With the default chart (failClosedOnEnforcementGap) the agent then returns HTTP 503 and is not ready |
Only where BPF LSM is active; assessed stock AKS Ubuntu/Azure Linux defaults omit bpf and are expected to refuse (assessed, not run). Measured COS uses lsm+fmod_ret; see platform evidence |
detect-only |
Tracepoint detection + userspace reactive kill (SIGKILL) |
Any eBPF-capable kernel (4.18+) | Detection with reactive kill after the syscall returns; weaker, has a theoretical race window. Not accepted by the default chart's readiness gate on its own | Kernels without BPF LSM or kprobe override |
disabled |
None (logging only, or agent not enforcing) | -- | No enforcement; violations are logged only if monitoring is on | ENFORCE_MODE=false, or the eBPF daemonset is not deployed |
The subsections below describe the enforcement primitives these modes are built from. full and lsm both use the synchronous in-kernel block; detect-only uses reactive kill; disabled takes no enforcement action.
Synchronous Block (kprobe override)¶
Kernel requirement: CONFIG_BPF_KPROBE_OVERRIDE=y
The eBPF program attaches to the kernel function entry point via kprobe. When a monitored syscall is invoked, the program inspects the arguments and can override the return value before the syscall executes. The calling process receives -EACCES and the syscall never runs.
Attacker calls openat("/proc/PID/mem")
→ kprobe fires BEFORE syscall executes
→ eBPF program checks caller's task against cgroup-array (bpf_current_task_under_cgroup)
→ Returns -EACCES to caller
→ File is never opened
→ Event logged: PROCMEM_READ
This is the preferred enforcement mode
Synchronous blocking means the attack is stopped before any data is accessed. There is no race condition and no window of exposure.
Reactive Kill¶
Kernel requirement: Any Linux with eBPF support (4.18+)
Reactive kill is an additional layer, not a substitute for a synchronous deny path: the default chart does not treat a vector covered only by reactive kill as enforced. When kprobe override is unavailable, the agent attaches via tracepoints and sends SIGKILL to the offending process immediately after detection.
Attacker calls openat("/proc/PID/mem")
→ Syscall executes (file descriptor returned)
→ Tracepoint fires on syscall exit
→ eBPF program detects violation
→ SIGKILL sent to attacker process
→ Process terminated before read() can follow
Reactive kill has a theoretical gap
Between syscall completion and SIGKILL delivery, the attacker technically holds an open file descriptor. In practice, the kill is delivered before a subsequent read() can execute, but this is a weaker guarantee than synchronous blocking.
Detection + Audit¶
For operations that cannot be blocked without breaking system functionality (e.g., kernel module loading from system PIDs), the agent logs detailed audit events without taking enforcement action.
Enforcement Vectors by Category¶
Memory Access¶
These vectors prevent direct reading of protected process memory.
| # | Attack Vector | Syscall / Path | Enforcement | Event Type |
|---|---|---|---|---|
| 1 | Read /proc/PID/environ | openat("/proc/PID/environ") |
kprobe block (all monitored PIDs in protected cgroup-array, including app child PIDs in sibling cgroups -- broadened in published release) | ENVIRON_READ |
| 2 | Read /proc/PID/mem | openat("/proc/PID/mem") |
kprobe block | PROCMEM_READ |
| 3 | Read /proc/PID/maps | openat("/proc/PID/maps") |
kprobe block | PROCINFO_READ |
| 4 | Read /proc/PID/pagemap | openat("/proc/PID/pagemap") |
kprobe block | PROCINFO_READ |
| 5 | Read /proc/PID/smaps | openat("/proc/PID/smaps") |
kprobe block | PROCINFO_READ |
| 6 | Cross-process memory read | process_vm_readv() |
kprobe block | VMREADV_DENIED |
| 7 | Cross-process memory write | process_vm_writev() |
kprobe block | VMREADV_DENIED |
| 8 | Debug attach | ptrace(PTRACE_ATTACH) / ptrace(PTRACE_SEIZE) |
kprobe block | PTRACE_DENIED |
Why block /proc/PID/maps?
Memory maps reveal the layout of the process address space, including where memfd_secret regions are allocated. While memfd_secret pages cannot be read through /proc/PID/mem, knowing the layout aids other attack vectors. cloudtaser blocks all /proc information disclosure for monitored processes.
Exfiltration¶
These vectors prevent secrets from being written to persistent storage or sent over the network.
| # | Attack Vector | Syscall | Enforcement | Event Type |
|---|---|---|---|---|
| 9 | Write secret to file | write(), writev() |
kprobe block (content match) | SECRET_LEAK |
| 10 | Send secret over network | sendto(), sendmsg() |
kprobe block (content match) | SECRET_LEAK |
| 11 | Zero-copy file transfer | sendfile() |
kprobe block | ZEROCOPY_EXFIL |
| 12 | Zero-copy pipe splice | splice(), tee() |
kprobe block | ZEROCOPY_EXFIL |
| 13 | Zero-copy VM splice | vmsplice() |
kprobe block | ZEROCOPY_EXFIL |
| 14 | DNS exfiltration | DNS traffic matching registered secret content; copied/encoded forms are not guaranteed | Content matching on sendto/sendmsg | SECRET_LEAK |
Content-based matching
For write() and sendto()/sendmsg(), the eBPF program performs content matching against known secret values. This catches cases where the application itself (not an attacker) inadvertently logs or transmits a secret. Zero-copy syscalls are blocked unconditionally for monitored processes because the kernel transfers data without user-space buffer inspection.
Privilege Escalation¶
These vectors detect or block attempts to gain kernel-level access that could bypass all protections.
| # | Attack Vector | Syscall | Enforcement | Event Type |
|---|---|---|---|---|
| 15 | Load kernel module | init_module(), finit_module() |
Global detection (all PIDs) + kprobe block (protected pod cgroups) | MODULE_LOAD |
| 16 | Load eBPF program | bpf(BPF_PROG_LOAD) |
Global detection (all PIDs) + kprobe block (protected pod cgroups) | BPF_LOAD |
| 17 | Performance event sampling | perf_event_open() |
kprobe block | PERF_EVENT_DENIED |
Why module and eBPF loading are treated specially
Kernel modules and eBPF programs run with full kernel privileges. A malicious module can bypass all user-space protections (except memfd_secret on 5.14+). cloudtaser detects these operations globally -- from any PID on the node, not just monitored processes -- because the threat is node-wide. Blocking is applied only to monitored PIDs to avoid breaking legitimate system operations (e.g., kube-proxy loading eBPF programs).
Evasion¶
These vectors block techniques that could circumvent the other enforcement categories.
| # | Attack Vector | Syscall / Path | Enforcement | Event Type |
|---|---|---|---|---|
| 18 | Async I/O bypass | io_uring_setup() |
kprobe block | IOURING_DENIED |
| 19 | Page fault interception | userfaultfd() |
kprobe block | USERFAULTFD_DENIED |
| 20 | Raw memory device | openat("/dev/mem"), /dev/kmem, /proc/kcore |
kprobe block | DEVMEM_DENIED |
| 21 | Re-enable core dumps | openat("/proc/PID/coredump_filter") for write |
kprobe block | PROC_WRITE_DENIED |
| 22 | Read kernel stack | openat("/proc/PID/stack") |
kprobe block | PROCINFO_READ |
| 23 | Read registers | openat("/proc/PID/syscall") |
kprobe block | PROCINFO_READ |
io_uring is blocked entirely for monitored processes
io_uring provides a submission queue that bypasses per-syscall eBPF hooks. An attacker could use io_uring to perform file writes or network sends that the eBPF agent cannot inspect. cloudtaser blocks io_uring_setup() for monitored processes. Applications that rely on io_uring for performance must use standard syscalls instead. This is an intentional security trade-off.
Event Format¶
All enforcement events are logged by the user-space agent in structured format:
{
"timestamp": "2026-03-21T14:32:01.847Z",
"event": "PROCMEM_READ",
"action": "blocked",
"source_pid": 4821,
"source_comm": "attacker",
"target_pid": 1234,
"target_comm": "wrapper",
"syscall": "openat",
"path": "/proc/1234/mem",
"node": "gke-prod-pool-abc123"
}
Events are emitted to:
- Container stdout (collected by standard log pipelines)
- cloudtaser Platform (if connected) for centralized audit
- Kubernetes events on the protected pod
Configuration¶
Enforcement Mode¶
env:
- name: ENFORCE_MODE
value: "true" # Block violations (not just detect)
- name: GLOBAL_PRIVESC_DETECT
value: "true" # Detect module/BPF loads from ANY process
Per-Vector Control¶
Individual vectors can be toggled for debugging or compatibility:
env:
- name: CLOUDTASER_EBPF_ALLOW_IOURING
value: "false" # Default: false (blocked)
- name: CLOUDTASER_EBPF_ALLOW_PERF
value: "false" # Default: false (blocked)
Disabling enforcement vectors weakens protection
Every disabled vector is an open attack path. Disable individual vectors only for debugging, never in production.
Kernel Compatibility¶
| Feature | Minimum Kernel | Distribution Examples |
|---|---|---|
| eBPF basic (tracepoints) | 4.18 | RHEL 8, Ubuntu 18.04 |
| kprobe override (synchronous block) | 4.18 + CONFIG_BPF_KPROBE_OVERRIDE=y |
Most cloud provider kernels |
| BTF (CO-RE, no per-kernel compilation) | 5.2 | Ubuntu 20.04+, RHEL 8.4+ |
| memfd_secret | 5.14 | Ubuntu 22.04+, Bottlerocket, Flatcar |
Check your kernel configuration
Vector Summary¶
| Named source set | Count (inferred) | Meaning |
|---|---|---|
| Kprobe syscall program catalogue | 37 | Program definitions, not measured synchronous denials. |
| LSM program sections | 17 | Sections in source; variants can share a hook. |
| Distinct LSM section names | 15 | Hook section names, not syscall coverage. |
| fmod_ret manifest programs | 76 | Manifest entries; loading and denial depend on the actual kernel. |
| Runtime enrollment manifest programs | 183 | Attachment inventory, not deployment qualification. |
Synchronous denial and detect-only coverage must be read from the running agent and a qualifying cell on the selected kernel. Catalogue counts do not establish enforcement or a general disclosure-prevention guarantee.
Phase 2 enforcement detail | Memory Protection | Root Attack Surface
Cgroup anchor registration is inferred from startup wiring, not a measured refusal guarantee: the wrapper can log and continue without the anchor when cgroup v2 or ID resolution is unavailable. Verify the runtime state on the selected node.