Skip to content

Commit d0b79e3

Browse files
katiehockmanFiloSottile
authored andcommitted
encoding/xml: prevent infinite loop while decoding
This change properly handles a TokenReader which returns an EOF in the middle of an open XML element. Thanks to Sam Whited for reporting this. Fixes CVE-2021-27918 Fixes #44913 Change-Id: Id02a3f3def4a1b415fa2d9a8e3b373eb6cb0f433 Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1004594 Reviewed-by: Russ Cox <[email protected]> Reviewed-by: Roland Shoemaker <[email protected]> Reviewed-by: Filippo Valsorda <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/go/+/300391 Trust: Katie Hockman <[email protected]> Run-TryBot: Katie Hockman <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Alexander Rakoczy <[email protected]> Reviewed-by: Filippo Valsorda <[email protected]>
1 parent cd3b4ca commit d0b79e3

File tree

2 files changed

+92
-31
lines changed

2 files changed

+92
-31
lines changed

src/encoding/xml/xml.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ func NewTokenDecoder(t TokenReader) *Decoder {
271271
// it will return an error.
272272
//
273273
// Token implements XML name spaces as described by
274-
// https://www.w3.org/TR/REC-xml-names/. Each of the
274+
// https://www.w3.org/TR/REC-xml-names/. Each of the
275275
// Name structures contained in the Token has the Space
276276
// set to the URL identifying its name space when known.
277277
// If Token encounters an unrecognized name space prefix,
@@ -285,16 +285,17 @@ func (d *Decoder) Token() (Token, error) {
285285
if d.nextToken != nil {
286286
t = d.nextToken
287287
d.nextToken = nil
288-
} else if t, err = d.rawToken(); err != nil {
289-
switch {
290-
case err == io.EOF && d.t != nil:
291-
err = nil
292-
case err == io.EOF && d.stk != nil && d.stk.kind != stkEOF:
293-
err = d.syntaxError("unexpected EOF")
288+
} else {
289+
if t, err = d.rawToken(); t == nil && err != nil {
290+
if err == io.EOF && d.stk != nil && d.stk.kind != stkEOF {
291+
err = d.syntaxError("unexpected EOF")
292+
}
293+
return nil, err
294294
}
295-
return t, err
295+
// We still have a token to process, so clear any
296+
// errors (e.g. EOF) and proceed.
297+
err = nil
296298
}
297-
298299
if !d.Strict {
299300
if t1, ok := d.autoClose(t); ok {
300301
d.nextToken = t

src/encoding/xml/xml_test.go

Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,30 +33,90 @@ func (t *toks) Token() (Token, error) {
3333

3434
func TestDecodeEOF(t *testing.T) {
3535
start := StartElement{Name: Name{Local: "test"}}
36-
t.Run("EarlyEOF", func(t *testing.T) {
37-
d := NewTokenDecoder(&toks{earlyEOF: true, t: []Token{
38-
start,
39-
start.End(),
40-
}})
41-
err := d.Decode(&struct {
42-
XMLName Name `xml:"test"`
43-
}{})
44-
if err != nil {
45-
t.Error(err)
36+
tests := []struct {
37+
name string
38+
tokens []Token
39+
ok bool
40+
}{
41+
{
42+
name: "OK",
43+
tokens: []Token{
44+
start,
45+
start.End(),
46+
},
47+
ok: true,
48+
},
49+
{
50+
name: "Malformed",
51+
tokens: []Token{
52+
start,
53+
StartElement{Name: Name{Local: "bad"}},
54+
start.End(),
55+
},
56+
ok: false,
57+
},
58+
}
59+
for _, tc := range tests {
60+
for _, eof := range []bool{true, false} {
61+
name := fmt.Sprintf("%s/earlyEOF=%v", tc.name, eof)
62+
t.Run(name, func(t *testing.T) {
63+
d := NewTokenDecoder(&toks{
64+
earlyEOF: eof,
65+
t: tc.tokens,
66+
})
67+
err := d.Decode(&struct {
68+
XMLName Name `xml:"test"`
69+
}{})
70+
if tc.ok && err != nil {
71+
t.Fatalf("d.Decode: expected nil error, got %v", err)
72+
}
73+
if _, ok := err.(*SyntaxError); !tc.ok && !ok {
74+
t.Errorf("d.Decode: expected syntax error, got %v", err)
75+
}
76+
})
4677
}
47-
})
48-
t.Run("LateEOF", func(t *testing.T) {
49-
d := NewTokenDecoder(&toks{t: []Token{
50-
start,
51-
start.End(),
52-
}})
53-
err := d.Decode(&struct {
54-
XMLName Name `xml:"test"`
55-
}{})
56-
if err != nil {
57-
t.Error(err)
78+
}
79+
}
80+
81+
type toksNil struct {
82+
returnEOF bool
83+
t []Token
84+
}
85+
86+
func (t *toksNil) Token() (Token, error) {
87+
if len(t.t) == 0 {
88+
if !t.returnEOF {
89+
// Return nil, nil before returning an EOF. It's legal, but
90+
// discouraged.
91+
t.returnEOF = true
92+
return nil, nil
5893
}
59-
})
94+
return nil, io.EOF
95+
}
96+
var tok Token
97+
tok, t.t = t.t[0], t.t[1:]
98+
return tok, nil
99+
}
100+
101+
func TestDecodeNilToken(t *testing.T) {
102+
for _, strict := range []bool{true, false} {
103+
name := fmt.Sprintf("Strict=%v", strict)
104+
t.Run(name, func(t *testing.T) {
105+
start := StartElement{Name: Name{Local: "test"}}
106+
bad := StartElement{Name: Name{Local: "bad"}}
107+
d := NewTokenDecoder(&toksNil{
108+
// Malformed
109+
t: []Token{start, bad, start.End()},
110+
})
111+
d.Strict = strict
112+
err := d.Decode(&struct {
113+
XMLName Name `xml:"test"`
114+
}{})
115+
if _, ok := err.(*SyntaxError); !ok {
116+
t.Errorf("d.Decode: expected syntax error, got %v", err)
117+
}
118+
})
119+
}
60120
}
61121

62122
const testInput = `

0 commit comments

Comments
 (0)