Answer in brief
CVE-2026-53941 records a Medium severity vulnerability in Uprobe gadgets: unprivileged container's ld.so.cache causes high CPU utilization and container startup DoS in github.com/inspektor-gadget/inspektor-gadget. The current sources do not mark it as known exploited. The current feed maps github.com/inspektor-gadget/inspektor-gadget (go), github.com/inspektor-gadget/inspektor-gadget (go). Check affected ranges and fixed versions before updating.
Analysis pending evidence review
HOL Guard separates source facts from reviewed analysis. See the methodology.
A CVSS score is not reported in the current record. The current sources do not mark it as known exploited. Treat this as a source-backed prioritization signal, not a statement about your environment.
Analysis status
Analysis pending evidence review
Factual feed record only; HOL analysis is not approved for indexing. Read the methodology.
The current feed maps github.com/inspektor-gadget/inspektor-gadget (go), github.com/inspektor-gadget/inspektor-gadget (go). Check affected ranges and fixed versions before updating.
| Package | Affected range | Fixed version |
|---|---|---|
| github.com/inspektor-gadget/inspektor-gadgetgo | >=0.27.0 <0.53.1 | 0.53.1 |
| github.com/inspektor-gadget/inspektor-gadgetgo | >=0.27.0,<0.53.1 | 0.53.1 |
Published upstream
Aug 19, 2026
Evidence: source:ghsa:source_dates:source-dates:recordSource modified
Aug 26, 2026
Evidence: source:ghsa:source_dates:source-dates:recordFirst seen by HOL
Aug 19, 2026
## 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 < ldCache.EntryCount; i++ { entryOffset := ldEntriesOffset + i*ldCache2EntrySize if uint32(len(data)) <= entryOffset+ldCache2EntrySize { return nil // bounds check stops iteration } // ... reads entry ... key := readStringFromBytes(data, keyOffset) // O(n²) per call value := readStringFromBytes(data, valueOffset) // O(n²) per call } ``` The per-entry bounds check correctly prevents out-of-bounds access, but: - The loop iterates ~700K times (limited by file size, not EntryCount) - Each `readStringFromBytes` call uses quadratic string concatenation In `pkg/uprobetracer/bytes.go`: ```go func readStringFromBytes(data []byte, startPos uint32) string { res := "" for i := startPos; i < uint32(len(data)); i++ { if data[i] == 0 { return res } res += string(data[i]) // O(n²) — allocates new string each iteration } return "" } ``` ## Note on Slice Bounds Checks The code also performs slice accesses without proper bounds checks (e.g., `data[:len(cache2Header)]` when `data` may be shorter than 20 bytes, and `ldCacheFile[:len(cache1Header)]` when the file may be shorter than 11 bytes). In practice, a malicious container **cannot currently trigger a panic** from these missing checks. This is because Go's `io.ReadAll` (used to read the file) always returns slices with `cap >= 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.
Quoted source text, attributed separately from HOL analysis.