Inspektor Gadget: Unprivileged container can crash USDT note parser via crafted ELF (no shipped gadget affected)
Summary
A malicious container can crash or destabilize the privileged Inspektor Gadget process when a gadget using USDT probes is deployed. The vulnerability is in the USDT note parser (pkg/uprobetracer/usdt.go) which is invoked when a gadget with a SEC("usdt/...") section attaches to a target binary. An unprivileged process can place a crafted ELF binary at the expected library path, triggering one of two attack vectors:
- Panic (immediate crash): A stapsdt note with a small
DescSizecauses an out-of-bounds slice access, panicking the IG process. - Memory exhaustion (OOM kill): A stapsdt note with a very large
NameSizeorDescSizecauses IG to allocate up to ~4 GiB of memory, which can killnthe process if deployed with memory restrictions (e.g., cgroup limits).
Important: The vulnerability is only triggered when running a gadget that uses USDT probes (i.e., contains a SEC("usdt/...") eBPF section). No gadget shipped by the Inspektor Gadget project uses USDT today. Users who deploy their own custom USDT gadgets are affected.
Severity
Low — Denial of Service (process crash or OOM) of a privileged host process, triggered by an unprivileged container. The vulnerable code path is only reached when a gadget using USDT probes is deployed. No such gadget is shipped by the Inspektor Gadget project; only users running custom USDT gadgets are affected.
- Attack vector: An unprivileged process in a container places a crafted ELF file at a path that a USDT gadget targets (e.g., a library name resolved via the container's ld cache). When the gadget attaches, IG parses the malicious ELF and crashes.
- Impact: The IG process panics and crashes (vector 1) or is OOM-killed (vector 2). This is a DoS against the monitoring infrastructure, not a code execution or privilege escalation vulnerability.
- Affected component:
pkg/uprobetracer/usdt.go, functiongetUsdtInfo() - Prerequisites: A gadget with a
SEC("usdt/...")eBPF section must be running and configured to attach to a library inside the attacker's container.
Affected Versions
All versions of Inspektor Gadget that include USDT support in pkg/uprobetracer/usdt.go, starting from v0.28.0 (commit 7ee5e7a90 "pkg/uprobetracer: support USDT trace points").
Root Cause
Vector 1: Out-of-bounds slice access (panic)
In pkg/uprobetracer/usdt.go, the function getUsdtInfo() parses stapsdt notes from an ELF file's .note.stapsdt section. When a matching note is found (name == "stapsdt\0" and type == 3), it reads three address fields from the note descriptor:
// usdt.go lines 137-139
elfLocation := elfReader.ByteOrder.Uint64(desc[:wordSize])
elfBase := elfReader.ByteOrder.Uint64(desc[wordSize : 2*wordSize])
elfSemaphore := elfReader.ByteOrder.Uint64(desc[2*wordSize : 3*wordSize])For a 64-bit ELF, wordSize = 8, so this requires desc to be at least 24 bytes. However, desc is allocated based on the note's DescSize field from the ELF file:
desc := make([]byte, alignUp(uint64(header.DescSize), 4))A crafted ELF with DescSize = 1 produces a 4-byte desc buffer. The expression desc[:8] then panics with:
panic: runtime error: slice bounds out of range [:8] with capacity 4Vector 2: Unbounded memory allocation (OOM)
The NameSize and DescSize fields from the note header are used directly to allocate memory without any upper bound:
name := make([]byte, alignUp(uint64(header.NameSize), 4))
desc := make([]byte, alignUp(uint64(header.DescSize), 4))A crafted ELF with NameSize = 0xFFFFFFFF would attempt to allocate ~4 GiB of memory. Under cgroup memory limits (common in Kubernetes deployments), this triggers an OOM kill of the IG process.
Vector 3: Missing panic recovery for debug/elf
Go's debug/elf package is not hardened against adversarial inputs and may panic on malformed ELF headers. The cilium/ebpf library addresses this with its SafeELFFile wrapper that uses recover(), but getUsdtInfo() calls elf.NewFile() directly without any panic recovery.
Fix
The fix (3 changes in pkg/uprobetracer/usdt.go):
- Bounds check on descriptor size: Validate
len(desc) >= 3*wordSizebefore accessing the address fields. Reject malformed notes with an error instead of panicking. - Cap allocation sizes: Limit
NameSizeandDescSizeto a reasonable maximum (1 MiB) before allocating memory, preventing DoS via memory exhaustion. There is no standard upper bound for ELF note fields; 1 MiB is a generous arbitrary cap — legitimate USDT notes are typically under 1 KB. - Panic recovery: Wrap
getUsdtInfo()withdefer/recoverto catch any panics fromdebug/elfon malformed input, converting them to errors.
Related
- Go
debug/elfknown issues: https://github.com/golang/go/issues?q=is%3Aissue+is%3Aopen+debug%2Felf+in%3Atitle - cilium/ebpf
SafeELFFilewrapper: https://github.com/cilium/ebpf/blob/main/internal/safeelf.go — usesrecover()around alldebug/elfoperations for exactly this reason.