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
•Technically reviewed by Anupam Mediratta•Published August 26, 2026•Reviewed 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 exploitation — golang.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

CVE-2026-59873: node-tar 7.5.11 DoS via Crafted Gzip Bomb

node-tar versions 7.5.11 through 7.5.18 are vulnerable to a denial-of-service attack through maliciously crafted gzip archives that decompress to disproportionately large sizes. An attacker can exploit this to exhaust memory and CPU resources by submitting a small, highly compressed archive that expands beyond configured limits during extraction.

high

MapManager.get() Race Condition Duplicates API Requests

The MapManager's `get(mapUid, cache)` method used a check-then-act pattern that permitted multiple concurrent requests to pass the cache miss check simultaneously, triggering redundant API calls and risking cache corruption. The fix introduces a `_pending` promise map to deduplicate in-flight fetches for identical map UIDs.

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.

critical

No Rate Limit on /api/uploads/presign Enables DoS

The `/api/uploads/presign` endpoint accepted unlimited concurrent requests to generate storage presigned URLs, giving an attacker a free lever to exhaust storage-provider quotas and server resources. The fix adds an `express-rate-limit` middleware capping each client to 30 requests per minute on that route.