Skip to content

Commit a8fb2fe

Browse files
rolandshoemakergopherbot
authored andcommitted
html: properly render fostered elements in foreign content
When we foster elements under another parent, there are complicated rules about which namespace may apply. This in particular affects childTextNodesAreLiteral, which checks if we should be emitting raw text, or escaped text. In childTextNodesAreLiteral, check if there is an ancestor which has a different namespace. If one is found, check if it's an HTML integration point. If not, treat the node as if it were in its parents namespace, if so, treat it as HTML. Thanks to Tristan Madani for reporting this issue. Fixes CVE-2026-42502 Change-Id: I0ae1780dae335e5f719d7f176cefa83670cfea3d Reviewed-on: https://go-review.googlesource.com/c/net/+/781701 Reviewed-by: Neal Patel <nealpatel@google.com> Reviewed-by: Nicholas Husin <nsh@golang.org> TryBot-Bypass: Roland Shoemaker <roland@golang.org> Reviewed-by: Nicholas Husin <husin@google.com> Auto-Submit: Gopher Robot <gobot@golang.org>
1 parent 0dc5b7a commit a8fb2fe

4 files changed

Lines changed: 48 additions & 0 deletions

File tree

html/parse_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,8 @@ var renderTestBlacklist = map[string]bool{
449449
`<table><math><select><mi><select></table>`: true,
450450
`<!doctype html><table><colgroup><plaintext></plaintext>`: true,
451451
`<!doctype html><svg><plaintext>a</plaintext>b`: true,
452+
// Due to fostering, parsing the rendered output produces a different tree.
453+
`<math><mtext><table><mglyph><style><img>`: true,
452454
}
453455

454456
func TestNodeConsistency(t *testing.T) {

html/render.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,24 @@ func childTextNodesAreLiteral(n *Node) bool {
243243
if n.Namespace != "" {
244244
return false
245245
}
246+
246247
switch n.Data {
247248
case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp":
249+
// We need to check if n is a node that was fostered from a HTML namespace
250+
// into a non-HTML namespace (in which case, different rules apply to it).
251+
// We do this by walking up the tree until we find a node with a non-empty
252+
// namespace. If we find such a node, we also have to check if it's
253+
// an HTML integration point. If it isn't, then the node we're currently
254+
// looking at is foster-parented and we should return false.
255+
for p := n.Parent; p != nil; p = p.Parent {
256+
if p.Namespace != "" {
257+
if !htmlIntegrationPoint(p) {
258+
return false
259+
}
260+
break
261+
}
262+
}
263+
248264
return true
249265
default:
250266
return false

html/render_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,20 @@ func TestRenderTextNodes(t *testing.T) {
205205
}
206206
}
207207
}
208+
209+
func TestRenderFosteredForeignContent(t *testing.T) {
210+
a := `<math><mtext><table><mglyph><style><img src=x onerror=alert(1)>`
211+
d, err := Parse(strings.NewReader(a))
212+
if err != nil {
213+
t.Fatal(err)
214+
}
215+
buf := bytes.NewBuffer(nil)
216+
if err := Render(buf, d); err != nil {
217+
t.Fatal(err)
218+
}
219+
220+
expected := "<html><head></head><body><math><mtext><mglyph><style>&lt;img src=x onerror=alert(1)&gt;</style></mglyph><table></table></mtext></math></body></html>"
221+
if buf.String() != expected {
222+
t.Errorf("unexpected output: got %q, want %q", buf.String(), expected)
223+
}
224+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#data
2+
<math><mtext><table><mglyph><style><img>
3+
#errors
4+
#document
5+
| <html>
6+
| <head>
7+
| <body>
8+
| <math math>
9+
| <math mtext>
10+
| <mglyph>
11+
| <style>
12+
| "<img>"
13+
| <table>

0 commit comments

Comments
 (0)