Why Traditional Runtime Security Tools Don't Cover GPUs: What eBPF Sees and What It Misses
For executives
eBPF (extended Berkeley Packet Filter) is the technology that powers modern Linux runtime security tools — Falco, Tetragon, various EDR products. It allows security tools to safely hook into the Linux kernel and observe events: system calls, network connections, file accesses. eBPF-based monitoring is excellent for CPU-side container security. For GPU container security, it hits a hard architectural boundary. This article explains where eBPF's visibility ends and why that boundary is where the most significant GPU attacks operate.
What eBPF does
eBPF programs run inside the Linux kernel, attached to specific kernel events. When a process makes a system call, eBPF programs can observe the call, inspect its arguments, and optionally allow or deny it. When a network packet arrives, eBPF programs can inspect and filter it. When a file is opened, eBPF programs can observe the open event.
This is powerful for monitoring CPU-side container behaviour. A container process that makes an unexpected system call — connecting to an unusual IP, opening an unexpected file, spawning a child process — is observable.
Where eBPF ends
eBPF observes Linux kernel events — events in the CPU's execution context. GPU execution is not a Linux kernel event.
When a CUDA program launches a kernel on the GPU:
- The CPU calls cudaLaunchKernel() or equivalent — a user-space library call.
- The CUDA runtime eventually makes an ioctl call to the NVIDIA driver device file — a syscall. eBPF sees this: a process made an ioctl call to /dev/nvidia0.
- The NVIDIA kernel module receives the ioctl and commands the GPU hardware to launch the kernel.
- The GPU executes the kernel — thousands of threads running on the GPU's streaming multiprocessors.
eBPF sees step 2: one ioctl call. It sees nothing of step 4: the GPU execution is entirely outside the Linux kernel's visibility. The GPU runs the kernel autonomously on its own hardware.
Why this matters for security
The ioctl call in step 2 contains encoded GPU commands — CUDA-specific data structures that specify which kernel to launch, on which streams, with which parameters. The ioctl looks the same from the eBPF perspective whether it is launching a legitimate matrix multiplication or a cryptomining kernel or a malicious memory-scanning kernel. eBPF cannot decode the ioctl's GPU-specific payload.
Furthermore, the OCI hook execution that is central to the Container Toolkit vulnerability series happens before the container's process namespace is established. There is no container process for eBPF to monitor at hook execution time. The nvidia-ctk hook process runs in the host context, and its malicious behaviour — loading a library via LD_PRELOAD, following a symlink to host filesystem locations — happens in a window before any container-level monitoring is active.
The three eBPF blind spots in GPU environments
OCI hooks: execute before the container process tree. No container process for eBPF to monitor.
GPU ioctl semantics: eBPF sees the ioctl syscall but cannot interpret the GPU-specific payload that determines what the GPU will do.
GPU execution: everything that happens on the GPU after the ioctl is submitted is outside the Linux kernel's visibility and therefore outside eBPF's reach.
What fills the gaps
Gap 1 (OCI hooks): monitoring the nvidia-ctk process itself at the host level — what libraries it loads, what files it opens, what environment variables it inherits. This requires host-level process monitoring of the hook process, not container-level monitoring.
Gap 2 (ioctl semantics): monitoring at the NVIDIA driver's internal boundaries — parsing and interpreting CUDA ioctl payloads to understand what GPU operation is being requested. This requires instrumentation inside the proprietary NVIDIA driver or at the CUDA API layer.
Gap 3 (GPU execution): monitoring via the NVIDIA driver's event interfaces — UVM events (memory allocation, eviction, migration), CUDA context events (creation, destruction), and GPU module load events (which compiled kernels are loaded into which context). These events are generated by the driver and available to instrumentation that plugs into the driver's event infrastructure.
