Skip to content

Commit 6ec8895

Browse files
html: align in row insertion mode with spec
Update inRowIM to match the HTML specification. This fixes an issue where a specific HTML document could cause the parser to enter an infinite loop when trying to parse a </tbody> and implied </tr> next to each other. Fixes CVE-2025-58190 Fixes golang/go#70179 Change-Id: Idcb133c87c7d475cc8c7eb1f1550ea21d8bdddea Reviewed-on: https://go-review.googlesource.com/c/net/+/709875 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Damien Neil <[email protected]>
1 parent 5393563 commit 6ec8895

File tree

2 files changed

+56
-33
lines changed

2 files changed

+56
-33
lines changed

html/parse.go

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func (p *parser) indexOfElementInScope(s scope, matchTags ...a.Atom) int {
136136
return -1
137137
}
138138
default:
139-
panic("unreachable")
139+
panic(fmt.Sprintf("html: internal error: indexOfElementInScope unknown scope: %d", s))
140140
}
141141
}
142142
switch s {
@@ -179,7 +179,7 @@ func (p *parser) clearStackToContext(s scope) {
179179
return
180180
}
181181
default:
182-
panic("unreachable")
182+
panic(fmt.Sprintf("html: internal error: clearStackToContext unknown scope: %d", s))
183183
}
184184
}
185185
}
@@ -1678,7 +1678,7 @@ func inTableBodyIM(p *parser) bool {
16781678
return inTableIM(p)
16791679
}
16801680

1681-
// Section 12.2.6.4.14.
1681+
// Section 13.2.6.4.14.
16821682
func inRowIM(p *parser) bool {
16831683
switch p.tok.Type {
16841684
case StartTagToken:
@@ -1690,7 +1690,9 @@ func inRowIM(p *parser) bool {
16901690
p.im = inCellIM
16911691
return true
16921692
case a.Caption, a.Col, a.Colgroup, a.Tbody, a.Tfoot, a.Thead, a.Tr:
1693-
if p.popUntil(tableScope, a.Tr) {
1693+
if p.elementInScope(tableScope, a.Tr) {
1694+
p.clearStackToContext(tableRowScope)
1695+
p.oe.pop()
16941696
p.im = inTableBodyIM
16951697
return false
16961698
}
@@ -1700,22 +1702,28 @@ func inRowIM(p *parser) bool {
17001702
case EndTagToken:
17011703
switch p.tok.DataAtom {
17021704
case a.Tr:
1703-
if p.popUntil(tableScope, a.Tr) {
1705+
if p.elementInScope(tableScope, a.Tr) {
1706+
p.clearStackToContext(tableRowScope)
1707+
p.oe.pop()
17041708
p.im = inTableBodyIM
17051709
return true
17061710
}
17071711
// Ignore the token.
17081712
return true
17091713
case a.Table:
1710-
if p.popUntil(tableScope, a.Tr) {
1714+
if p.elementInScope(tableScope, a.Tr) {
1715+
p.clearStackToContext(tableRowScope)
1716+
p.oe.pop()
17111717
p.im = inTableBodyIM
17121718
return false
17131719
}
17141720
// Ignore the token.
17151721
return true
17161722
case a.Tbody, a.Tfoot, a.Thead:
1717-
if p.elementInScope(tableScope, p.tok.DataAtom) {
1718-
p.parseImpliedToken(EndTagToken, a.Tr, a.Tr.String())
1723+
if p.elementInScope(tableScope, p.tok.DataAtom) && p.elementInScope(tableScope, a.Tr) {
1724+
p.clearStackToContext(tableRowScope)
1725+
p.oe.pop()
1726+
p.im = inTableBodyIM
17191727
return false
17201728
}
17211729
// Ignore the token.
@@ -2222,16 +2230,20 @@ func parseForeignContent(p *parser) bool {
22222230
p.acknowledgeSelfClosingTag()
22232231
}
22242232
case EndTagToken:
2233+
if strings.EqualFold(p.oe[len(p.oe)-1].Data, p.tok.Data) {
2234+
p.oe = p.oe[:len(p.oe)-1]
2235+
return true
2236+
}
22252237
for i := len(p.oe) - 1; i >= 0; i-- {
2226-
if p.oe[i].Namespace == "" {
2227-
return p.im(p)
2228-
}
22292238
if strings.EqualFold(p.oe[i].Data, p.tok.Data) {
22302239
p.oe = p.oe[:i]
2240+
return true
2241+
}
2242+
if i > 0 && p.oe[i-1].Namespace == "" {
22312243
break
22322244
}
22332245
}
2234-
return true
2246+
return p.im(p)
22352247
default:
22362248
// Ignore the token.
22372249
}

html/parse_test.go

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -251,31 +251,35 @@ func TestParser(t *testing.T) {
251251
t.Fatal(err)
252252
}
253253
for _, tf := range testFiles {
254-
f, err := os.Open(tf)
255-
if err != nil {
256-
t.Fatal(err)
257-
}
258-
defer f.Close()
259-
r := bufio.NewReader(f)
260-
261-
for i := 0; ; i++ {
262-
ta, err := readParseTest(r)
263-
if err == io.EOF {
264-
break
265-
}
254+
t.Run(tf, func(t *testing.T) {
255+
f, err := os.Open(tf)
266256
if err != nil {
267257
t.Fatal(err)
268258
}
269-
if parseTestBlacklist[ta.text] {
270-
continue
259+
defer f.Close()
260+
r := bufio.NewReader(f)
261+
262+
for i := 0; ; i++ {
263+
ta, err := readParseTest(r)
264+
if err == io.EOF {
265+
break
266+
}
267+
if err != nil {
268+
t.Fatal(err)
269+
}
270+
if parseTestBlacklist[ta.text] {
271+
continue
272+
}
273+
274+
t.Run(fmt.Sprint(i), func(t *testing.T) {
275+
err = testParseCase(ta.text, ta.want, ta.context, ParseOptionEnableScripting(ta.scripting))
276+
277+
if err != nil {
278+
t.Errorf("%s test #%d %q, %s", tf, i, ta.text, err)
279+
}
280+
})
271281
}
272-
273-
err = testParseCase(ta.text, ta.want, ta.context, ParseOptionEnableScripting(ta.scripting))
274-
275-
if err != nil {
276-
t.Errorf("%s test #%d %q, %s", tf, i, ta.text, err)
277-
}
278-
}
282+
})
279283
}
280284
}
281285
}
@@ -506,3 +510,10 @@ func BenchmarkParser(b *testing.B) {
506510
Parse(bytes.NewBuffer(buf))
507511
}
508512
}
513+
514+
func TestIssue70179(t *testing.T) {
515+
_, err := Parse(strings.NewReader("<table><tbody><svg><td><desc><select></select></tbody>"))
516+
if err != nil {
517+
t.Fatalf("unexpected failure: %v", err)
518+
}
519+
}

0 commit comments

Comments
 (0)