|
| 1 | +From 854b3d9d75f1a6c90b4ef78036e54ae6e574c045 Mon Sep 17 00:00:00 2001 |
| 2 | +From: Roland Shoemaker <roland@golang.org> |
| 3 | +Date: Fri, 8 May 2026 12:09:06 -0700 |
| 4 | +Subject: [PATCH] html: ignore duplicate attributes during tokenization |
| 5 | + |
| 6 | +During tokenization ignore attributes with names we've already seen, |
| 7 | +per WHATWG 13.2.5.33. This removes a parser misalignment that could be |
| 8 | +leveraged to confuse sanitizers. |
| 9 | + |
| 10 | +Thanks to ensy for reporting this issue. |
| 11 | + |
| 12 | +Fixes CVE-2026-27136 |
| 13 | + |
| 14 | +Change-Id: Ib0a3edb8dbea35c431f74f8b0bbe6229625d7e1f |
| 15 | +Reviewed-on: https://go-review.googlesource.com/c/net/+/781685 |
| 16 | +Reviewed-by: Neal Patel <nealpatel@google.com> |
| 17 | +Reviewed-by: Nicholas Husin <nsh@golang.org> |
| 18 | +TryBot-Bypass: Roland Shoemaker <roland@golang.org> |
| 19 | +Auto-Submit: Gopher Robot <gobot@golang.org> |
| 20 | +Reviewed-by: Nicholas Husin <husin@google.com> |
| 21 | +Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> |
| 22 | +Upstream-reference: https://github.com/golang/net/commit/a452f3cc17168a60bc3f439a3ae0fcffc32eca0e.patch |
| 23 | +--- |
| 24 | + vendor/golang.org/x/net/html/token.go | 13 +++++++++---- |
| 25 | + 1 file changed, 9 insertions(+), 4 deletions(-) |
| 26 | + |
| 27 | +diff --git a/vendor/golang.org/x/net/html/token.go b/vendor/golang.org/x/net/html/token.go |
| 28 | +index 6598c1f..058dfb2 100644 |
| 29 | +--- a/vendor/golang.org/x/net/html/token.go |
| 30 | ++++ b/vendor/golang.org/x/net/html/token.go |
| 31 | +@@ -156,6 +156,7 @@ type Tokenizer struct { |
| 32 | + // incremented on each call to TagAttr. |
| 33 | + pendingAttr [2]span |
| 34 | + attr [][2]span |
| 35 | ++ attrNames map[string]bool |
| 36 | + nAttrReturned int |
| 37 | + // rawTag is the "script" in "</script>" that closes the next token. If |
| 38 | + // non-empty, the subsequent call to Next will return a raw or RCDATA text |
| 39 | +@@ -867,6 +868,7 @@ func (z *Tokenizer) readStartTag() TokenType { |
| 40 | + func (z *Tokenizer) readTag(saveAttr bool) { |
| 41 | + z.attr = z.attr[:0] |
| 42 | + z.nAttrReturned = 0 |
| 43 | ++ clear(z.attrNames) |
| 44 | + // Read the tag name and attribute key/value pairs. |
| 45 | + z.readTagName() |
| 46 | + if z.skipWhiteSpace(); z.err != nil { |
| 47 | +@@ -880,9 +882,11 @@ func (z *Tokenizer) readTag(saveAttr bool) { |
| 48 | + z.raw.end-- |
| 49 | + z.readTagAttrKey() |
| 50 | + z.readTagAttrVal() |
| 51 | +- // Save pendingAttr if saveAttr and that attribute has a non-empty key. |
| 52 | +- if saveAttr && z.pendingAttr[0].start != z.pendingAttr[0].end { |
| 53 | ++ // Save pendingAttr if saveAttr and that attribute has a non-empty key, and the key hasn't been seen before. |
| 54 | ++ key := strings.ToLower(string(z.buf[z.pendingAttr[0].start:z.pendingAttr[0].end])) |
| 55 | ++ if saveAttr && z.pendingAttr[0].start != z.pendingAttr[0].end && !z.attrNames[key] { |
| 56 | + z.attr = append(z.attr, z.pendingAttr) |
| 57 | ++ z.attrNames[key] = true |
| 58 | + } |
| 59 | + if z.skipWhiteSpace(); z.err != nil { |
| 60 | + break |
| 61 | +@@ -1273,8 +1277,9 @@ func NewTokenizer(r io.Reader) *Tokenizer { |
| 62 | + // The input is assumed to be UTF-8 encoded. |
| 63 | + func NewTokenizerFragment(r io.Reader, contextTag string) *Tokenizer { |
| 64 | + z := &Tokenizer{ |
| 65 | +- r: r, |
| 66 | +- buf: make([]byte, 0, 4096), |
| 67 | ++ r: r, |
| 68 | ++ buf: make([]byte, 0, 4096), |
| 69 | ++ attrNames: make(map[string]bool), |
| 70 | + } |
| 71 | + if contextTag != "" { |
| 72 | + switch s := strings.ToLower(contextTag); s { |
| 73 | +-- |
| 74 | +2.45.4 |
| 75 | + |
0 commit comments