Back to Blog
high SEVERITY7 min read

How Denial of Service via Invalid UTF-8 Input happens in Go and how to fix it

CVE-2026-56852 is a high-severity Denial of Service vulnerability in `golang.org/x/text` where `norm.Iter` can enter an infinite loop when processing invalid UTF-8 input, potentially hanging any Go application that normalizes untrusted text. The fix upgrades `golang.org/x/text` from v0.34.0 to v0.39.0 in `parser/go.mod`, closing the loop condition that malformed byte sequences could exploit. Developers using any version of `golang.org/x/text` below 0.39.0 should upgrade immediately.

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

CVE-2026-56852 is a high-severity Denial of Service vulnerability (CWE-835: Loop with Unreachable Exit Condition) in the Go package `golang.org/x/text`. The `norm.Iter` iterator can enter an infinite loop when it encounters invalid UTF-8 byte sequences, allowing an attacker who can supply malformed text input to hang the process indefinitely. The fix is to upgrade `golang.org/x/text` to v0.39.0, which corrects the loop termination logic in the Unicode normalization code. In this repository, the change was made in `parser/go.mod` by bumping the indirect dependency from v0.34.0 to v0.39.0.

Vulnerability at a Glance

cweCWE-835
fixUpgrade golang.org/x/text from v0.34.0 to v0.39.0 in parser/go.mod
riskAttacker-controlled input can hang the parser process indefinitely
languageGo
root causenorm.Iter fails to advance past invalid UTF-8 byte sequences, looping forever
vulnerabilityDenial of Service via Infinite Loop (norm.Iter, invalid UTF-8)

How Denial of Service via Invalid UTF-8 Input Happens in Go and How to Fix It

The Quiet Danger of Unicode Normalization

The parser/go.mod file in this repository lists golang.org/x/text as an indirect dependency — a library pulled in to handle Unicode normalization. It's the kind of dependency that sits quietly in a module file, rarely thought about, until a malformed byte sequence turns a routine text-processing call into an infinite loop that never returns.

That's exactly what CVE-2026-56852 enables. A single crafted input containing invalid UTF-8 bytes can cause norm.Iter — the iterator at the heart of golang.org/x/text's normalization pipeline — to spin forever, consuming 100% of a CPU core and making the parser completely unresponsive. No crash, no error, just silence and a hung goroutine.


The Vulnerability Explained

What Is norm.Iter and Why Does It Loop?

golang.org/x/text/unicode/norm provides Unicode normalization forms (NFC, NFD, NFKC, NFKD). The norm.Iter type is an iterator that walks through input bytes, yielding one normalized segment at a time. Internally, it tracks its position in the input buffer and advances after processing each segment.

The bug in versions prior to v0.39.0 is a failure to advance the iterator's position when it encounters an invalid UTF-8 byte sequence. Instead of skipping the unrecognized byte and moving forward, the iterator re-processes the same position repeatedly — a classic CWE-835: Loop with Unreachable Exit Condition.

The vulnerable dependency declaration in parser/go.mod was:

// parser/go.mod (before fix)
golang.org/x/text v0.34.0 // indirect

Any code path in the parser that calls into golang.org/x/text's normalization functions — directly or transitively — inherits this bug.

How Could an Attacker Exploit This?

Consider the typical flow in a game demo parser (which this repository implements): a player's name, chat message, or any string field in a .dem file is read from disk or over the network and passed through text processing. If that string contains a carefully crafted invalid UTF-8 sequence — even a single malformed byte like 0xFF or a truncated multi-byte sequence like 0xC0 — and it reaches a normalization call backed by norm.Iter, the iterator stalls.

Concrete attack scenario:

  1. An attacker crafts a CS2 demo file with a player name field containing the byte sequence "\xC0" (an incomplete two-byte UTF-8 sequence).
  2. The parser reads the name and passes it through a normalization step that uses norm.Iter internally.
  3. norm.Iter attempts to process "\xC0", cannot decode a valid rune, fails to advance its internal offset, and loops back to the same byte.
  4. The loop never terminates. The goroutine is stuck. The parser hangs.
  5. If the parser is called repeatedly (e.g., in a web service that accepts demo uploads), the attacker can exhaust all available goroutines or CPU cores with a handful of requests.

This is a zero-cost denial of service: no authentication required, no memory corruption, just one bad byte.


The Fix

Upgrading golang.org/x/text from v0.34.0 to v0.39.0

The fix is a one-line change in parser/go.mod:

# parser/go.mod
-   golang.org/x/text v0.34.0 // indirect
+   golang.org/x/text v0.39.0 // indirect

Version 0.39.0 of golang.org/x/text corrects the loop termination logic inside norm.Iter. When the iterator encounters a byte sequence that cannot be decoded as valid UTF-8, it now advances past the offending byte(s) rather than stalling. The exit condition becomes reachable for all inputs, including malformed ones.

The corresponding parser/go.sum update replaces the old checksum entries:

# parser/go.sum (relevant lines)
-github.com/markus-wa/demoinfocs-golang/v5 v5.1.2 h1:YbC23degEUIini8Qe051wDgLM47AqHPwBKeHNPApyxw=
-github.com/markus-wa/demoinfocs-golang/v5 v5.1.2/go.mod h1:cnrd9QDLk2XroPtujR46xAKGEROHxEZgEw9Wy0Pido8=
 github.com/markus-wa/demoinfocs-golang/v5 v5.2.0 h1:hvSXyE9AUvqO4t25a9bqyMIvcwM/Wx9jO/7gPejTSkE=
 github.com/markus-wa/demoinfocs-golang/v5 v5.2.0/go.mod h1:JG2eu06s72JijIJDR7wnCSqgLtuOjhHQMtT8piem0Lw=

The go.sum changes also reflect a bump in the markus-wa/demoinfocs-golang dependency from v5.1.2 to v5.2.0, which itself likely carries the updated golang.org/x/text transitively. Both files must be updated together — go.mod declares the version constraint, and go.sum records the cryptographic hashes that Go's module system uses to verify download integrity.

Why two files?
go.mod is the human-readable version manifest. go.sum is the tamper-evident lock file. Updating one without the other would either fail go mod verify or leave the build using a cached (vulnerable) version of the module.


Key Takeaways

  • norm.Iter in golang.org/x/text < v0.39.0 cannot safely process invalid UTF-8 — a single malformed byte is enough to trigger an infinite loop and hang the parser process.
  • The fix in parser/go.mod is a one-line bump from v0.34.0 to v0.39.0, but both go.mod and go.sum must be updated together to maintain module integrity.
  • Indirect dependencies in Go are not insulated from exploitationgolang.org/x/text is listed as // indirect, yet its bug directly affects the parser's availability.
  • utf8.Valid() is a cheap, effective guard to add at any input boundary before text normalization, providing defense-in-depth even against future normalization bugs.
  • Trivy's static analysis of go.mod identified this vulnerability without needing to trace the full call graph — version-based scanning is fast and catches issues like this before they reach production.

How Orbis AppSec Detected This

  • Source: User-influenced input (player names, string fields) read from demo files processed by the parser component.
  • Sink: norm.Iter inside golang.org/x/text v0.34.0, reached transitively through the parser's text-processing pipeline — specifically the normalization functions exposed by golang.org/x/text/unicode/norm.
  • Missing control: No UTF-8 validity check before input reaches the normalization iterator; no version constraint preventing the use of the vulnerable golang.org/x/text release.
  • CWE: CWE-835 — Loop with Unreachable Exit Condition.
  • Fix: golang.org/x/text was upgraded from v0.34.0 to v0.39.0 in parser/go.mod and parser/go.sum, replacing the vulnerable iterator implementation with a version that correctly handles invalid UTF-8 byte sequences.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

CVE-2026-56852 is a reminder that Unicode normalization — something most developers treat as a solved problem — has sharp edges when the input isn't clean. The norm.Iter infinite loop is subtle: it doesn't crash, doesn't corrupt memory, and doesn't log an error. It just stops making progress, silently consuming resources until the process is killed or the service times out.

The fix is straightforward: upgrade golang.org/x/text to v0.39.0. But the broader lesson is about defense in depth — validate UTF-8 at your input boundaries, scan your dependency tree regularly, and treat // indirect dependencies with the same security scrutiny as your own code. A one-line version bump in go.mod is all it took to close this door.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #331

Related Articles

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.