CVE-2026-53941: Uprobe gadgets Denial of Service Vulnerability

MODERATE
Advisory Identifier: GHSA-vjhx-2cqw-3q6q
CWE-400 CWE-770
Published
Advisory Summary

CVE-2026-53941 is a MODERATE denial of service vulnerability (CWE-400: Uncontrolled Resource Consumption) affecting Uprobe gadgets libraries in the Go ecosystem. The issue involves unprivileged container's ld.so.cache causes high cpu utilization and container startup dos. Remediation is available by upgrading affected packages to version 0.53.1.

CVE-2026-53941 at a Glance

CVE Identifier CVE-2026-53941
GHSA Identifier GHSA-vjhx-2cqw-3q6q
Severity MODERATE
CVSS v3.1 Base Score N/A
CWE Classification CWE-400: Uncontrolled Resource Consumption
Affected Product Uprobe gadgets
Package Ecosystem Go
Published Date
Last Updated

How to Fix CVE-2026-53941

To remediate this vulnerability, upgrade all affected Uprobe gadgets dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/inspektor-gadget/inspektor-gadget fixed: 0.53.1
Upgrade github.com/inspektor-gadget/inspektor-gadget to 0.53.1
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/inspektor-gadget/inspektor-gadget

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-53941, inspect your project dependencies for Uprobe gadgets:

inspect dependencies for github.com/inspektor-gadget/inspektor-gadget

Analyze the terminal output to locate any version of Uprobe gadgets in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.53.1), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-400: Uncontrolled Resource Consumption, where the product does not properly control the allocation and maintenance of a limited resource. In Uprobe gadgets, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## 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.

CVE-2026-53941 Affected Uprobe gadgets Versions

1 component(s)
Fixed Versions:
0.53.1
Product / Package Ecosystem Affected Versions Fixed Version
github.com/inspektor-gadget/inspektor-gadget
Go
≥ 0.27.0
< 0.53.1
0.53.1

References

Frequently Asked Questions

What is CVE-2026-53941?
CVE-2026-53941 is a security vulnerability in Uprobe gadgets involving unprivileged container's ld.so.cache causes high CPU utilization and container startup DoS under CWE-400: Uncontrolled Resource Consumption.
Which Uprobe gadgets versions are affected?
Affected versions include releases prior to 0.53.1 across affected packages: github.com/inspektor-gadget/inspektor-gadget.
What version fixes CVE-2026-53941?
Upgrading to 0.53.1 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/inspektor-gadget/inspektor-gadget affected?
Yes, github.com/inspektor-gadget/inspektor-gadget is affected in releases prior to 0.53.1. Upgrade your dependency to 0.53.1 or later.
What CWE is associated with CVE-2026-53941?
CVE-2026-53941 is classified under CWE-400: Uncontrolled Resource Consumption (The product does not properly control the allocation and maintenance of a limited resource.).
How can I check Go dependencies for CVE-2026-53941?
Run the dependency tree command: 'inspect dependencies for github.com/inspektor-gadget/inspektor-gadget'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-vjhx-2cqw-3q6q",
  "modified" : "2026-08-19T19:16:35Z",
  "published" : "2026-08-19T19:16:35Z",
  "aliases" : [ "CVE-2026-53941" ],
  "summary" : "Uprobe gadgets: unprivileged container's ld.so.cache causes high CPU utilization and container startup DoS",
  "details" : "## Summary\n\nAn unprivileged container can block all other containers from starting on the\nsame host by placing a crafted `/etc/ld.so.cache` file in its filesystem. When\nInspektor Gadget attaches any uprobe-based gadget, it parses this file in the\ncontainer startup path. A malicious cache causes ~53 seconds of CPU burn,\nduring which Docker cannot start any other container. No special capabilities\nare required.\n\n## Severity\n\nTo be assessed — Availability impact, no confidentiality or integrity impact.\n\n## Affected Versions\n\nAll versions of Inspektor Gadget that support uprobe-based gadgets (trace_malloc, trace_open, trace_ssl, trace_grpc, etc.).\n\n## Description\n\nWhen 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.\n\nThe parser has three vulnerabilities:\n\n1. **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.\n\n2. **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.\n\n3. **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.\n\nCombined, these cause ~53 seconds of CPU burn per container attachment when a crafted 16MB `/etc/ld.so.cache` is present.\n\n## Impact\n\n- **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.\n- **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.\n- **Monitoring degradation**: The IG daemon is blocked processing the malicious cache, potentially missing events from other containers.\n- **Amplification**: Multiple containers with crafted caches can be started simultaneously to amplify the effect.\n- **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).\n\n## Root Cause Analysis\n\nIn `pkg/uprobetracer/ldcache_parser.go`, the function `readCacheFormat2` is called with the full file content:\n\n```go\nfor i := uint32(0); i < ldCache.EntryCount; i++ {\n    entryOffset := ldEntriesOffset + i*ldCache2EntrySize\n    if uint32(len(data)) <= entryOffset+ldCache2EntrySize {\n        return nil  // bounds check stops iteration\n    }\n    // ... reads entry ...\n    key := readStringFromBytes(data, keyOffset)    // O(n²) per call\n    value := readStringFromBytes(data, valueOffset) // O(n²) per call\n}\n```\n\nThe per-entry bounds check correctly prevents out-of-bounds access, but:\n- The loop iterates ~700K times (limited by file size, not EntryCount)\n- Each `readStringFromBytes` call uses quadratic string concatenation\n\nIn `pkg/uprobetracer/bytes.go`:\n\n```go\nfunc readStringFromBytes(data []byte, startPos uint32) string {\n    res := \"\"\n    for i := startPos; i < uint32(len(data)); i++ {\n        if data[i] == 0 {\n            return res\n        }\n        res += string(data[i])  // O(n²) — allocates new string each iteration\n    }\n    return \"\"\n}\n```\n\n## Note on Slice Bounds Checks\n\nThe code also performs slice accesses without proper bounds checks (e.g.,\n`data[:len(cache2Header)]` when `data` may be shorter than 20 bytes, and\n`ldCacheFile[:len(cache1Header)]` when the file may be shorter than 11 bytes).\n\nIn practice, a malicious container **cannot currently trigger a panic** from these\nmissing checks. This is because Go's `io.ReadAll` (used to read the file) always\nreturns slices with `cap >= 512` due to its initial buffer allocation\n(`make([]byte, 0, 512)` in Go's standard library). In Go, `s[:n]` only panics\nwhen `n > cap(s)`, not when `n > len(s)`. Since both header lengths (11 and 20)\nare well below 512, the slice expressions succeed — they simply read zero bytes\nbeyond `len`, which don't match any valid header magic.\n\nHowever, this relies on an **undocumented implementation detail** of `io.ReadAll`\nwhich could change in future Go versions. The bounds checks are still necessary\nfor correctness and defense in depth.",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "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"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/inspektor-gadget/inspektor-gadget"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0.27.0"
      }, {
        "fixed" : "0.53.1"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/inspektor-gadget/inspektor-gadget/security/advisories/GHSA-vjhx-2cqw-3q6q"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/inspektor-gadget/inspektor-gadget"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/inspektor-gadget/inspektor-gadget/releases/tag/v0.53.1"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-400", "CWE-770" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-08-19T19:16:35Z",
    "nvd_published_at" : null
  }
}