CVE-2020-14040
Description
The x/text package before 0.3.3 for Go has a vulnerability in encoding/unicode that could lead to the UTF-16 decoder entering an infinite loop, causing the program to crash or run out of memory. An attacker could provide a single byte to a UTF16 decoder instantiated with UseBOM or ExpectBOM to trigger an infinite loop if the String function on the Decoder is called, or the Decoder is passed to golang.org/x/text/transform.String.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A single-byte input can cause an infinite loop in Go's x/text UTF-16 decoder when configured with BOM detection, leading to denial of service.
Vulnerability
Overview
The vulnerability resides in the encoding/unicode package of Go's golang.org/x/text library prior to version 0.3.3. When a UTF-16 decoder is instantiated with UseBOM or ExpectBOM options, processing a single byte via the String function or passing the decoder to transform.String can trigger an infinite loop. This occurs because the decoder fails to properly handle incomplete input when a BOM is expected, causing it to repeatedly attempt to read more data without making progress [1][2].
Exploitation
Conditions
An attacker can exploit this by providing a single byte (e.g., \x00) to a UTF-16 decoder that has been configured to expect a byte order mark. The attack requires no authentication and can be delivered over any vector that supplies untrusted input to a Go application using the vulnerable decoder. The infinite loop is triggered specifically when the String method is invoked on the decoder or when the decoder is used with transform.String [1][3].
Impact
Successful exploitation results in a denial of service: the affected program enters an infinite loop, consuming CPU resources and potentially exhausting memory, leading to a crash or hang. This can be used to disrupt services that process user-supplied text with the vulnerable decoder [1].
Mitigation
The issue is fixed in golang.org/x/text version 0.3.3 and later. Users should update their dependencies to the patched version. The fix ensures that single-byte inputs are correctly handled and do not cause infinite loops [2][3].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
golang.org/x/textGo | < 0.3.3 | 0.3.3 |
Affected products
13- Go/x/textdescription
- osv-coords12 versionspkg:apk/chainguard/k3dpkg:apk/chainguard/k3d-proxypkg:apk/chainguard/k3d-toolspkg:apk/chainguard/vt-clipkg:apk/wolfi/k3dpkg:apk/wolfi/k3d-proxypkg:apk/wolfi/k3d-toolspkg:apk/wolfi/vt-clipkg:golang/golang.org/x/textpkg:rpm/almalinux/libslirppkg:rpm/almalinux/libslirp-develpkg:rpm/almalinux/python-podman-api
< 5.6.0-r11+ 11 more
- (no CPE)range: < 5.6.0-r11
- (no CPE)range: < 5.6.0-r11
- (no CPE)range: < 5.6.0-r11
- (no CPE)range: < 1.0.0-r3
- (no CPE)range: < 5.6.0-r11
- (no CPE)range: < 5.6.0-r11
- (no CPE)range: < 5.6.0-r11
- (no CPE)range: < 1.0.0-r3
- (no CPE)range: < 0.3.3
- (no CPE)range: < 4.3.1-1.module_el8.6.0+2876+9ed4eae2
- (no CPE)range: < 4.3.1-1.module_el8.6.0+2876+9ed4eae2
- (no CPE)range: < 1.2.0-0.2.gitd0a45fe.module_el8.5.0+2635+e4386a39
Patches
123ae387dee1fencoding/unicode: correctly handle single-byte UTF-16 inputs (and harden transform.String)
4 files changed · +76 −18
encoding/unicode/unicode.go+4 −7 modified@@ -368,16 +368,13 @@ func (u *utf16Decoder) Reset() { } func (u *utf16Decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + if len(src) < 2 && atEOF && u.current.bomPolicy&requireBOM != 0 { + return 0, 0, ErrMissingBOM + } if len(src) == 0 { - if atEOF && u.current.bomPolicy&requireBOM != 0 { - return 0, 0, ErrMissingBOM - } return 0, 0, nil } - if u.current.bomPolicy&acceptBOM != 0 { - if len(src) < 2 { - return 0, 0, transform.ErrShortSrc - } + if len(src) >= 2 && u.current.bomPolicy&acceptBOM != 0 { switch { case src[0] == 0xfe && src[1] == 0xff: u.current.endianness = BigEndian
encoding/unicode/unicode_test.go+44 −10 modified@@ -113,13 +113,40 @@ func TestUTF16(t *testing.T) { nSrc: 0, err: ErrMissingBOM, t: utf16BEEB.NewDecoder(), + }, { + desc: "utf-16 dec: Fail on single byte missing BOM when required", + src: "\x00", + sizeDst: 4, + t: utf16BEEB.NewDecoder(), + err: ErrMissingBOM, + }, { + desc: "utf-16 dec: Fail on short src missing BOM when required", + src: "\x00", + notEOF: true, + sizeDst: 4, + t: utf16BEEB.NewDecoder(), + err: transform.ErrShortSrc, }, { desc: "utf-16 dec: SHOULD interpret text as big-endian when BOM not present (RFC 2781:4.3)", src: "\xD8\x08\xDF\x45\x00\x3D\x00\x52\x00\x61", sizeDst: 100, want: "\U00012345=Ra", nSrc: 10, t: utf16BEUB.NewDecoder(), + }, { + desc: "utf-16 dec: incorrect UTF-16: odd bytes", + src: "\x00", + sizeDst: 100, + want: "\uFFFD", + nSrc: 1, + t: utf16BEUB.NewDecoder(), + }, { + desc: "utf-16 dec: Fail on incorrect UTF-16: short source odd bytes", + src: "\x00", + notEOF: true, + sizeDst: 100, + t: utf16BEUB.NewDecoder(), + err: transform.ErrShortSrc, }, { // This is an error according to RFC 2781. But errors in RFC 2781 are // open to interpretations, so I guess this is fine. @@ -273,16 +300,23 @@ func TestUTF16(t *testing.T) { t: utf16LEUB.NewDecoder(), }} for i, tc := range testCases { - b := make([]byte, tc.sizeDst) - nDst, nSrc, err := tc.t.Transform(b, []byte(tc.src), !tc.notEOF) - if err != tc.err { - t.Errorf("%d:%s: error was %v; want %v", i, tc.desc, err, tc.err) - } - if got := string(b[:nDst]); got != tc.want { - t.Errorf("%d:%s: result was %q: want %q", i, tc.desc, got, tc.want) - } - if nSrc != tc.nSrc { - t.Errorf("%d:%s: nSrc was %d; want %d", i, tc.desc, nSrc, tc.nSrc) + for j := 0; j < 2; j++ { + b := make([]byte, tc.sizeDst) + nDst, nSrc, err := tc.t.Transform(b, []byte(tc.src), !tc.notEOF) + if err != tc.err { + t.Errorf("%d:%s: error was %v; want %v", i, tc.desc, err, tc.err) + } + if got := string(b[:nDst]); got != tc.want { + t.Errorf("%d:%s: result was %q: want %q", i, tc.desc, got, tc.want) + } + if nSrc != tc.nSrc { + t.Errorf("%d:%s: nSrc was %d; want %d", i, tc.desc, nSrc, tc.nSrc) + } + // Since Transform is stateful, run failures again + // to ensure that the same error occurs a second time. + if err == nil { + break + } } } }
transform/transform.go+5 −1 modified@@ -648,7 +648,8 @@ func String(t Transformer, s string) (result string, n int, err error) { // Transform the remaining input, growing dst and src buffers as necessary. for { n := copy(src, s[pSrc:]) - nDst, nSrc, err := t.Transform(dst[pDst:], src[:n], pSrc+n == len(s)) + atEOF := pSrc+n == len(s) + nDst, nSrc, err := t.Transform(dst[pDst:], src[:n], atEOF) pDst += nDst pSrc += nSrc @@ -659,6 +660,9 @@ func String(t Transformer, s string) (result string, n int, err error) { dst = grow(dst, pDst) } } else if err == ErrShortSrc { + if atEOF { + return string(dst[:pDst]), pSrc, err + } if nSrc == 0 { src = grow(src, 0) }
transform/transform_test.go+23 −0 modified@@ -1315,3 +1315,26 @@ var ( aaa = strings.Repeat("a", 4096) AAA = strings.Repeat("A", 4096) ) + +type badTransformer struct{} + +func (bt badTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + return 0, 0, ErrShortSrc +} + +func (bt badTransformer) Reset() {} + +func TestBadTransformer(t *testing.T) { + bt := badTransformer{} + if _, _, err := String(bt, "aaa"); err != ErrShortSrc { + t.Errorf("String expected ErrShortSrc, got nil") + } + if _, _, err := Bytes(bt, []byte("aaa")); err != ErrShortSrc { + t.Errorf("Bytes expected ErrShortSrc, got nil") + } + r := NewReader(bytes.NewReader([]byte("aaa")), bt) + var bytes []byte + if _, err := r.Read(bytes); err != ErrShortSrc { + t.Errorf("NewReader Read expected ErrShortSrc, got nil") + } +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
13- github.com/advisories/GHSA-5rcv-m4m3-hfh7ghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/TACQFZDPA7AUR6TRZBCX2RGRFSDYLI7O/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2020-14040ghsaADVISORY
- github.com/golang/go/issues/39491ghsaWEB
- github.com/golang/text/commit/23ae387dee1f90d29a23c0e87ee0b46038fbed0eghsaWEB
- go-review.googlesource.com/c/text/+/238238ghsaWEB
- go.dev/cl/238238ghsaWEB
- go.dev/issue/39491ghsaWEB
- go.googlesource.com/text/+/23ae387dee1f90d29a23c0e87ee0b46038fbed0eghsaWEB
- groups.google.com/forum/ghsaWEB
- groups.google.com/forum/mitrex_refsource_MISC
- groups.google.com/g/golang-announce/c/bXVeAmGOqz0ghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/TACQFZDPA7AUR6TRZBCX2RGRFSDYLI7OghsaWEB
News mentions
0No linked articles in our index yet.