GitHub Advisoryadvisory · Go
GHSA-vjhx-2cqw-3q6q Uprobe gadgets: unprivileged container's ld.so.cache causes high CPU utilization and container startup DoS
MEDIUM
Official page ↗
- Aliases
- CVE-2026-53941
- Ecosystem
- Go
- Packages
- github.com/inspektor-gadget/inspektor-gadget
- Severity
- Moderate
- CVSS vector
- CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N
- Weaknesses
- CWE-400, CWE-770
- Reviewed
- 2026-08-19
Affected packages
• Go github.com/inspektor-gadget/inspektor-gadget: >= 0.27.0, < 0.53.1 → fixed in 0.53.1
Description
Summary
An unprivileged container can block all other containers from starting on the
same host by placing a crafted `/etc/ld.so.cache` file in its filesystem. When
Inspektor Gadget attaches any uprobe-based gadget, it parses this file in the
container startup path. A malicious cache causes ~53 seconds of CPU burn,
during which Docker cannot start any other container. No special capabilities
are required.
Severity
To be assessed — Availability impact, no confidentiality or integrity impact.
Affected Versions
All versions of Inspektor Gadget that support uprobe-based gadgets (trace_malloc, trace_open, trace_ssl, trace_grpc, etc.).
Description
When Inspektor Gadget attaches uprobe-based gadgets to containers, it resolves library paths by parsing the container's `/etc/ld.so.cache` file (`pkg/uprobetracer/ldcache_parser.go`). This file is fully controlled by the container.
The parser has three vulnerabilities:
1. Quadratic string building (`pkg/uprobetracer/bytes.go:36-44`): The `readStringFromBytes` function concatenates one byte at a time (`res += string(data[i])`), which is O(n²) in Go due to string immutability. With a 16MB cache file containing large regions without null terminators, this causes massive CPU and memory churn.
2. Insufficient entry count validation (`pkg/uprobetracer/ldcache_parser.go:120`): The `EntryCount` field is read directly from the untrusted file. While a per-entry bounds check prevents out-of-bounds access, the loop still iterates up to `(fileSize - headerSize) / entrySize ≈ 700,000` times, calling `readStringFromBytes` on each iteration.
3. Integer overflow in format detection (`pkg/uprobetracer/ldcache_parser.go:174`): The `cache1Len` computation uses uint32 arithmetic (`ldCache1Size + cache1.EntryCount*ldCache1EntrySize`). With a crafted `EntryCount`, this overflows and produces a small value, causing the parser to misidentify the cache format.
Combined, these cause ~53 seconds of CPU burn per container attachment when a crafted 16MB `/etc/ld.so.cache` is present.
Impact
• Container runtime DoS: IG uses fanotify hooks (`pkg/container-hook`) to pause container startup until uprobe attachment completes. While IG is blocked processing the malicious cache, this pause is held, and Docker serializes container starts — meaning no other container can start on the host until IG finishes. This effectively causes a denial of service on the entire container runtime, not just on IG itself.
• Container startup delay: When any uprobe-based gadget is running (trace_malloc, trace_ssl, etc.), starting a container with a crafted ld.so.cache delays startup by ~1 minute.
• Monitoring degradation: The IG daemon is blocked processing the malicious cache, potentially missing events from other containers.
• Amplification: Multiple containers with crafted caches can be started simultaneously to amplify the effect.
• No special privileges required: Any container can include a crafted `/etc/ld.so.cache` in its image, mount one via a volume, or overwrite it at runtime before IG starts a uprobe gadget. In this last case, IG inspects all already-running containers when the gadget starts — this still burns CPU but does not block other containers from starting (since the fanotify pause only applies to new container starts).
Root Cause Analysis
In `pkg/uprobetracer/ldcache_parser.go`, the function `readCacheFormat2` is called with the full file content:
```go
for i := uint32(0); i = 512` due to its initial buffer allocation
(`make([]byte, 0, 512)` in Go's standard library). In Go, `s[:n]` only panics
when `n > cap(s)`, not when `n > len(s)`. Since both header lengths (11 and 20)
are well below 512, the slice expressions succeed — they simply read zero bytes
beyond `len`, which don't match any valid header magic.
However, this relies on an undocumented implementation detail of `io.ReadAll`
which could change in future Go versions. The bounds checks are still necessary
for correctness and defense in depth.
Related
Alias · CVE (1)
- CVE-2026-53941 Inspektor Gadget is a set of tools and framework for data collection and system inspection on Kubernetes clusters and Linux hosts using… vulnerability