Skip to content

Commit 4ece7b6

Browse files
rolandshoemakergopherbot
authored andcommitted
html: escape greater-than symbol in doctype identifiers
During parsing, we unescape character references. When rendering, we re-escape certain characters in certain scenarios in order to avoid token content causing unexpected parser behavior. We appear to have not taken this into account when rendering DOCTYPE tokens, allowing ">" in PUBLIC/SYSTEM identifier strings, which trigger a abrupt-doctype-system-identifier parse error which immediately emits the current DOCTYPE token and then continues parsing in the data state. This may cause bypass in HTML santizers which use the html package for parsing. Thanks to ensy for reporting this issue. Fixes CVE-2026-25681 Change-Id: I1d5be92129d17bfbf0917148db2672d57c224a18 Reviewed-on: https://go-review.googlesource.com/c/net/+/781703 Reviewed-by: Neal Patel <nealpatel@google.com> Reviewed-by: Nicholas Husin <nsh@golang.org> TryBot-Bypass: Roland Shoemaker <roland@golang.org> Auto-Submit: Gopher Robot <gobot@golang.org> Reviewed-by: Nicholas Husin <husin@google.com>
1 parent 08be507 commit 4ece7b6

2 files changed

Lines changed: 21 additions & 6 deletions

File tree

html/render.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,22 +113,22 @@ func render1(w writer, n *Node) error {
113113
if _, err := w.WriteString(" PUBLIC "); err != nil {
114114
return err
115115
}
116-
if err := writeQuoted(w, p); err != nil {
116+
if err := writeDoctypeQuoted(w, p); err != nil {
117117
return err
118118
}
119119
if s != "" {
120120
if err := w.WriteByte(' '); err != nil {
121121
return err
122122
}
123-
if err := writeQuoted(w, s); err != nil {
123+
if err := writeDoctypeQuoted(w, s); err != nil {
124124
return err
125125
}
126126
}
127127
} else if s != "" {
128128
if _, err := w.WriteString(" SYSTEM "); err != nil {
129129
return err
130130
}
131-
if err := writeQuoted(w, s); err != nil {
131+
if err := writeDoctypeQuoted(w, s); err != nil {
132132
return err
133133
}
134134
}
@@ -267,19 +267,26 @@ func childTextNodesAreLiteral(n *Node) bool {
267267
}
268268
}
269269

270-
// writeQuoted writes s to w surrounded by quotes. Normally it will use double
270+
// writeDoctypeQuoted writes s to w surrounded by quotes. Normally it will use double
271271
// quotes, but if s contains a double quote, it will use single quotes.
272+
// If s contains any '>' characters, they are replaced with &gt; in order
273+
// to prevent triggering an abrupt-doctype-system-identifier parse error.
272274
// It is used for writing the identifiers in a doctype declaration.
273275
// In valid HTML, they can't contain both types of quotes.
274-
func writeQuoted(w writer, s string) error {
276+
func writeDoctypeQuoted(w writer, s string) error {
275277
var q byte = '"'
276278
if strings.Contains(s, `"`) {
279+
// parseDoctype will never produce a Node with both quote types, but a user
280+
// can construct their own Node that violates this assumption.
281+
if strings.Contains(s, `'`) {
282+
return errors.New("doctype contains both quote types, cannot be safely rendered")
283+
}
277284
q = '\''
278285
}
279286
if err := w.WriteByte(q); err != nil {
280287
return err
281288
}
282-
if _, err := w.WriteString(s); err != nil {
289+
if _, err := w.WriteString(strings.ReplaceAll(s, ">", "&gt;")); err != nil {
283290
return err
284291
}
285292
if err := w.WriteByte(q); err != nil {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#data
2+
<!DOCTYPE &gt; PUBLIC "&gt;" "&gt;">
3+
#errors
4+
#document
5+
| <!DOCTYPE > ">" ">">
6+
| <html>
7+
| <head>
8+
| <body>

0 commit comments

Comments
 (0)