VYPR
Moderate severityNVD Advisory· Published Jun 17, 2020· Updated Aug 4, 2024

CVE-2020-14040

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.

PackageAffected versionsPatched versions
golang.org/x/textGo
< 0.3.30.3.3

Affected products

13

Patches

1
23ae387dee1f

encoding/unicode: correctly handle single-byte UTF-16 inputs (and harden transform.String)

https://github.com/golang/textKatie HockmanJun 11, 2020via ghsa
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

News mentions

0

No linked articles in our index yet.