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:
- An attacker crafts a CS2 demo file with a player name field containing the byte sequence
"\xC0"(an incomplete two-byte UTF-8 sequence). - The parser reads the name and passes it through a normalization step that uses
norm.Iterinternally. norm.Iterattempts to process"\xC0", cannot decode a valid rune, fails to advance its internal offset, and loops back to the same byte.- The loop never terminates. The goroutine is stuck. The parser hangs.
- 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.Iteringolang.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.modis a one-line bump from v0.34.0 to v0.39.0, but bothgo.modandgo.summust be updated together to maintain module integrity. - Indirect dependencies in Go are not insulated from exploitation —
golang.org/x/textis 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.modidentified 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.Iterinsidegolang.org/x/textv0.34.0, reached transitively through the parser's text-processing pipeline — specifically the normalization functions exposed bygolang.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/textrelease. - CWE: CWE-835 — Loop with Unreachable Exit Condition.
- Fix:
golang.org/x/textwas upgraded from v0.34.0 to v0.39.0 inparser/go.modandparser/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.