diff mbox series

[scarthgap,01/14] go: patch CVE-2026-27142

Message ID 20260521100949.1299757-1-tgaige.opensource@witekio.com
State New
Headers show
Series [scarthgap,01/14] go: patch CVE-2026-27142 | expand

Commit Message

tgaige.opensource@witekio.com May 21, 2026, 10:09 a.m. UTC
From: "Theo Gaige (Schneider Electric)" <tgaige.opensource@witekio.com>

Backport patch from [1]

[1] https://go.dev/cl/752081

Signed-off-by: Theo Gaige (Schneider Electric) <tgaige.opensource@witekio.com>
Reviewed-by: Bruno Vernay <bruno.vernay@se.com>
---
 meta/recipes-devtools/go/go-1.22.12.inc       |   1 +
 .../go/go/CVE-2026-27142.patch                | 386 ++++++++++++++++++
 2 files changed, 387 insertions(+)
 create mode 100644 meta/recipes-devtools/go/go/CVE-2026-27142.patch
diff mbox series

Patch

diff --git a/meta/recipes-devtools/go/go-1.22.12.inc b/meta/recipes-devtools/go/go-1.22.12.inc
index 3fa421e223..8efa82f862 100644
--- a/meta/recipes-devtools/go/go-1.22.12.inc
+++ b/meta/recipes-devtools/go/go-1.22.12.inc
@@ -41,6 +41,7 @@  SRC_URI += "\
     file://CVE-2025-68121_p1.patch \
     file://CVE-2025-68121_p2.patch \
     file://CVE-2025-68121_p3.patch \
+    file://CVE-2026-27142.patch \
 "
 SRC_URI[main.sha256sum] = "012a7e1f37f362c0918c1dfa3334458ac2da1628c4b9cf4d9ca02db986e17d71"
 
diff --git a/meta/recipes-devtools/go/go/CVE-2026-27142.patch b/meta/recipes-devtools/go/go/CVE-2026-27142.patch
new file mode 100644
index 0000000000..e735abaf4b
--- /dev/null
+++ b/meta/recipes-devtools/go/go/CVE-2026-27142.patch
@@ -0,0 +1,386 @@ 
+From 1ac19df75e9c25951c04008a52b23a1cd95e81cc Mon Sep 17 00:00:00 2001
+From: Roland Shoemaker <bracewell@google.com>
+Date: Fri, 9 Jan 2026 11:12:01 -0800
+Subject: [PATCH] html/template: properly escape URLs in meta content
+ attributes
+
+The meta tag can include a content attribute that contains URLs, which
+we currently don't escape if they are inserted via a template action.
+This can plausibly lead to XSS vulnerabilities if untrusted data is
+inserted there, the http-equiv attribute is set to "refresh", and the
+content attribute contains an action like `url={{.}}`.
+
+Track whether we are inside of a meta element, if we are inside of a
+content attribute, _and_ if the content attribute contains "url=". If
+all of those are true, then we will apply the same URL escaping that we
+use elsewhere.
+
+Also add a new GODEBUG, htmlmetacontenturlescape, to allow disabling this
+escaping for cases where this behavior is considered safe. The behavior
+can be disabled by setting htmlmetacontenturlescape=0.
+
+Updates #77954
+Fixes #77972
+Fixes CVE-2026-27142
+
+Change-Id: I9bbca263be9894688e6ef1e9a8f8d2f4304f5873
+Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3360
+Reviewed-by: Neal Patel <nealpatel@google.com>
+Reviewed-by: Nicholas Husin <husin@google.com>
+Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3643
+Reviewed-by: Damien Neil <dneil@google.com>
+Reviewed-on: https://go-review.googlesource.com/c/go/+/752081
+Auto-Submit: Gopher Robot <gobot@golang.org>
+Reviewed-by: Cherry Mui <cherryyz@google.com>
+TryBot-Bypass: Gopher Robot <gobot@golang.org>
+Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
+
+CVE: CVE-2026-27142
+Upstream-Status: Backport [https://github.com/golang/go/commit/994692847a2cd3efd319f0cb61a07c0012c8a4ff]
+Signed-off-by: Theo Gaige (Schneider Electric) <tgaige.opensource@witekio.com>
+---
+ doc/godebug.md                      |  5 +++
+ src/html/template/attr_string.go    |  5 +--
+ src/html/template/context.go        |  8 +++++
+ src/html/template/element_string.go |  5 +--
+ src/html/template/escape.go         | 14 +++++++++
+ src/html/template/escape_test.go    | 34 +++++++++++++++++++++
+ src/html/template/state_string.go   |  8 +++--
+ src/html/template/transition.go     | 47 +++++++++++++++++++++++++----
+ src/internal/godebugs/table.go      |  1 +
+ src/runtime/metrics/doc.go          |  5 +++
+ 10 files changed, 119 insertions(+), 13 deletions(-)
+
+diff --git a/doc/godebug.md b/doc/godebug.md
+index 635597e..07b63cb 100644
+--- a/doc/godebug.md
++++ b/doc/godebug.md
+@@ -126,6 +126,11 @@ for example,
+ see the [runtime documentation](/pkg/runtime#hdr-Environment_Variables)
+ and the [go command documentation](/cmd/go#hdr-Build_and_test_caching).
+ 
++Go 1.26.1 added a new `htmlmetacontenturlescape` setting that controls whether
++html/template will escape URLs in the `url=` portion of the content attribute of
++HTML meta tags. The default `htmlmetacontentescape=1` will cause URLs to be
++escaped. Setting `htmlmetacontentescape=0` disables this behavior.
++
+ Go 1.26 added a new `urlmaxqueryparams` setting that controls the maximum number
+ of query parameters that net/url will accept when parsing a URL-encoded query string.
+ If the number of parameters exceeds the number set in `urlmaxqueryparams`,
+diff --git a/src/html/template/attr_string.go b/src/html/template/attr_string.go
+index 51c3f26..7159fa9 100644
+--- a/src/html/template/attr_string.go
++++ b/src/html/template/attr_string.go
+@@ -14,11 +14,12 @@ func _() {
+ 	_ = x[attrStyle-3]
+ 	_ = x[attrURL-4]
+ 	_ = x[attrSrcset-5]
++	_ = x[attrMetaContent-6]
+ }
+ 
+-const _attr_name = "attrNoneattrScriptattrScriptTypeattrStyleattrURLattrSrcset"
++const _attr_name = "attrNoneattrScriptattrScriptTypeattrStyleattrURLattrSrcsetattrMetaContent"
+ 
+-var _attr_index = [...]uint8{0, 8, 18, 32, 41, 48, 58}
++var _attr_index = [...]uint8{0, 8, 18, 32, 41, 48, 58, 73}
+ 
+ func (i attr) String() string {
+ 	if i >= attr(len(_attr_index)-1) {
+diff --git a/src/html/template/context.go b/src/html/template/context.go
+index b78f0f7..8b3af2f 100644
+--- a/src/html/template/context.go
++++ b/src/html/template/context.go
+@@ -156,6 +156,10 @@ const (
+ 	// stateError is an infectious error state outside any valid
+ 	// HTML/CSS/JS construct.
+ 	stateError
++	// stateMetaContent occurs inside a HTML meta element content attribute.
++	stateMetaContent
++	// stateMetaContentURL occurs inside a "url=" tag in a HTML meta element content attribute.
++	stateMetaContentURL
+ 	// stateDead marks unreachable code after a {{break}} or {{continue}}.
+ 	stateDead
+ )
+@@ -267,6 +271,8 @@ const (
+ 	elementTextarea
+ 	// elementTitle corresponds to the RCDATA <title> element.
+ 	elementTitle
++	// elementMeta corresponds to the HTML <meta> element.
++	elementMeta
+ )
+ 
+ //go:generate stringer -type attr
+@@ -288,4 +294,6 @@ const (
+ 	attrURL
+ 	// attrSrcset corresponds to a srcset attribute.
+ 	attrSrcset
++	// attrMetaContent corresponds to the content attribute in meta HTML element.
++	attrMetaContent
+ )
+diff --git a/src/html/template/element_string.go b/src/html/template/element_string.go
+index db28665..bdf9da7 100644
+--- a/src/html/template/element_string.go
++++ b/src/html/template/element_string.go
+@@ -13,11 +13,12 @@ func _() {
+ 	_ = x[elementStyle-2]
+ 	_ = x[elementTextarea-3]
+ 	_ = x[elementTitle-4]
++	_ = x[elementMeta-5]
+ }
+ 
+-const _element_name = "elementNoneelementScriptelementStyleelementTextareaelementTitle"
++const _element_name = "elementNoneelementScriptelementStyleelementTextareaelementTitleelementMeta"
+ 
+-var _element_index = [...]uint8{0, 11, 24, 36, 51, 63}
++var _element_index = [...]uint8{0, 11, 24, 36, 51, 63, 74}
+ 
+ func (i element) String() string {
+ 	if i >= element(len(_element_index)-1) {
+diff --git a/src/html/template/escape.go b/src/html/template/escape.go
+index 1eace16..b368cab 100644
+--- a/src/html/template/escape.go
++++ b/src/html/template/escape.go
+@@ -165,6 +165,8 @@ func (e *escaper) escape(c context, n parse.Node) context {
+ 
+ var debugAllowActionJSTmpl = godebug.New("jstmpllitinterp")
+ 
++var htmlmetacontenturlescape = godebug.New("htmlmetacontenturlescape")
++
+ // escapeAction escapes an action template node.
+ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
+ 	if len(n.Pipe.Decl) != 0 {
+@@ -222,6 +224,18 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
+ 		default:
+ 			panic(c.urlPart.String())
+ 		}
++	case stateMetaContent:
++		// Handled below in delim check.
++	case stateMetaContentURL:
++		if htmlmetacontenturlescape.Value() != "0" {
++			s = append(s, "_html_template_urlfilter")
++		} else {
++			// We don't have a great place to increment this, since it's hard to
++			// know if we actually escape any urls in _html_template_urlfilter,
++			// since it has no information about what context it is being
++			// executed in etc. This is probably the best we can do.
++			htmlmetacontenturlescape.IncNonDefault()
++		}
+ 	case stateJS:
+ 		s = append(s, "_html_template_jsvalescaper")
+ 		// A slash after a value starts a div operator.
+diff --git a/src/html/template/escape_test.go b/src/html/template/escape_test.go
+index 497ead8..1970db1 100644
+--- a/src/html/template/escape_test.go
++++ b/src/html/template/escape_test.go
+@@ -734,6 +734,16 @@ func TestEscape(t *testing.T) {
+ 			"<script>var a = `${ var a = \"{{\"a \\\" d\"}}\" }`</script>",
+ 			"<script>var a = `${ var a = \"a \\u0022 d\" }`</script>",
+ 		},
++		{
++			"meta content attribute url",
++			`<meta http-equiv="refresh" content="asd; url={{"javascript:alert(1)"}}; asd; url={{"vbscript:alert(1)"}}; asd">`,
++			`<meta http-equiv="refresh" content="asd; url=#ZgotmplZ; asd; url=#ZgotmplZ; asd">`,
++		},
++		{
++			"meta content string",
++			`<meta http-equiv="refresh" content="{{"asd: 123"}}">`,
++			`<meta http-equiv="refresh" content="asd: 123">`,
++		},
+ 	}
+ 
+ 	for _, test := range tests {
+@@ -1016,6 +1026,14 @@ func TestErrors(t *testing.T) {
+ 			"<script>var tmpl = `asd ${return \"{\"}`;</script>",
+ 			``,
+ 		},
++		{
++			`{{if eq "" ""}}<meta>{{end}}`,
++			``,
++		},
++		{
++			`{{if eq "" ""}}<meta content="url={{"asd"}}">{{end}}`,
++			``,
++		},
+ 
+ 		// Error cases.
+ 		{
+@@ -2194,3 +2212,19 @@ func TestAliasedParseTreeDoesNotOverescape(t *testing.T) {
+ 		t.Fatalf(`Template "foo" and "bar" rendered %q and %q respectively, expected equal values`, got1, got2)
+ 	}
+ }
++
++func TestMetaContentEscapeGODEBUG(t *testing.T) {
++	savedGODEBUG := os.Getenv("GODEBUG")
++	os.Setenv("GODEBUG", savedGODEBUG+",htmlmetacontenturlescape=0")
++	defer func() { os.Setenv("GODEBUG", savedGODEBUG) }()
++
++	tmpl := Must(New("").Parse(`<meta http-equiv="refresh" content="asd; url={{"javascript:alert(1)"}}; asd; url={{"vbscript:alert(1)"}}; asd">`))
++	var b strings.Builder
++	if err := tmpl.Execute(&b, nil); err != nil {
++		t.Fatalf("unexpected error: %s", err)
++	}
++	want := `<meta http-equiv="refresh" content="asd; url=javascript:alert(1); asd; url=vbscript:alert(1); asd">`
++	if got := b.String(); got != want {
++		t.Fatalf("got %q, want %q", got, want)
++	}
++}
+diff --git a/src/html/template/state_string.go b/src/html/template/state_string.go
+index eed1e8b..f5a70b2 100644
+--- a/src/html/template/state_string.go
++++ b/src/html/template/state_string.go
+@@ -36,12 +36,14 @@ func _() {
+ 	_ = x[stateCSSBlockCmt-25]
+ 	_ = x[stateCSSLineCmt-26]
+ 	_ = x[stateError-27]
+-	_ = x[stateDead-28]
++	_ = x[stateMetaContent-28]
++	_ = x[stateMetaContentURL-29]
++	_ = x[stateDead-30]
+ }
+ 
+-const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValuestateHTMLCmtstateRCDATAstateAttrstateURLstateSrcsetstateJSstateJSDqStrstateJSSqStrstateJSTmplLitstateJSRegexpstateJSBlockCmtstateJSLineCmtstateJSHTMLOpenCmtstateJSHTMLCloseCmtstateCSSstateCSSDqStrstateCSSSqStrstateCSSDqURLstateCSSSqURLstateCSSURLstateCSSBlockCmtstateCSSLineCmtstateErrorstateDead"
++const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValuestateHTMLCmtstateRCDATAstateAttrstateURLstateSrcsetstateJSstateJSDqStrstateJSSqStrstateJSTmplLitstateJSRegexpstateJSBlockCmtstateJSLineCmtstateJSHTMLOpenCmtstateJSHTMLCloseCmtstateCSSstateCSSDqStrstateCSSSqStrstateCSSDqURLstateCSSSqURLstateCSSURLstateCSSBlockCmtstateCSSLineCmtstateErrorstateMetaContentstateMetaContentURLstateDead"
+ 
+-var _state_index = [...]uint16{0, 9, 17, 30, 44, 60, 72, 83, 92, 100, 111, 118, 130, 142, 156, 169, 184, 198, 216, 235, 243, 256, 269, 282, 295, 306, 322, 337, 347, 356}
++var _state_index = [...]uint16{0, 9, 17, 30, 44, 60, 72, 83, 92, 100, 111, 118, 130, 142, 156, 169, 184, 198, 216, 235, 243, 256, 269, 282, 295, 306, 322, 337, 347, 363, 382, 391}
+ 
+ func (i state) String() string {
+ 	if i >= state(len(_state_index)-1) {
+diff --git a/src/html/template/transition.go b/src/html/template/transition.go
+index d5a05f6..5aa3c35 100644
+--- a/src/html/template/transition.go
++++ b/src/html/template/transition.go
+@@ -23,6 +23,8 @@ var transitionFunc = [...]func(context, []byte) (context, int){
+ 	stateRCDATA:         tSpecialTagEnd,
+ 	stateAttr:           tAttr,
+ 	stateURL:            tURL,
++	stateMetaContent:    tMetaContent,
++	stateMetaContentURL: tMetaContentURL,
+ 	stateSrcset:         tURL,
+ 	stateJS:             tJS,
+ 	stateJSDqStr:        tJSDelimited,
+@@ -83,6 +85,7 @@ var elementContentType = [...]state{
+ 	elementStyle:    stateCSS,
+ 	elementTextarea: stateRCDATA,
+ 	elementTitle:    stateRCDATA,
++	elementMeta:     stateText,
+ }
+ 
+ // tTag is the context transition function for the tag state.
+@@ -93,6 +96,11 @@ func tTag(c context, s []byte) (context, int) {
+ 		return c, len(s)
+ 	}
+ 	if s[i] == '>' {
++		// Treat <meta> specially, because it doesn't have an end tag, and we
++		// want to transition into the correct state/element for it.
++		if c.element == elementMeta {
++			return context{state: stateText, element: elementNone}, i + 1
++		}
+ 		return context{
+ 			state:   elementContentType[c.element],
+ 			element: c.element,
+@@ -113,6 +121,8 @@ func tTag(c context, s []byte) (context, int) {
+ 	attrName := strings.ToLower(string(s[i:j]))
+ 	if c.element == elementScript && attrName == "type" {
+ 		attr = attrScriptType
++	} else if c.element == elementMeta && attrName == "content" {
++		attr = attrMetaContent
+ 	} else {
+ 		switch attrType(attrName) {
+ 		case contentTypeURL:
+@@ -162,12 +172,13 @@ func tAfterName(c context, s []byte) (context, int) {
+ }
+ 
+ var attrStartStates = [...]state{
+-	attrNone:       stateAttr,
+-	attrScript:     stateJS,
+-	attrScriptType: stateAttr,
+-	attrStyle:      stateCSS,
+-	attrURL:        stateURL,
+-	attrSrcset:     stateSrcset,
++	attrNone:        stateAttr,
++	attrScript:      stateJS,
++	attrScriptType:  stateAttr,
++	attrStyle:       stateCSS,
++	attrURL:         stateURL,
++	attrSrcset:      stateSrcset,
++	attrMetaContent: stateMetaContent,
+ }
+ 
+ // tBeforeValue is the context transition function for stateBeforeValue.
+@@ -203,6 +214,7 @@ var specialTagEndMarkers = [...][]byte{
+ 	elementStyle:    []byte("style"),
+ 	elementTextarea: []byte("textarea"),
+ 	elementTitle:    []byte("title"),
++	elementMeta:     []byte(""),
+ }
+ 
+ var (
+@@ -612,6 +624,28 @@ func tError(c context, s []byte) (context, int) {
+ 	return c, len(s)
+ }
+ 
++// tMetaContent is the context transition function for the meta content attribute state.
++func tMetaContent(c context, s []byte) (context, int) {
++	for i := 0; i < len(s); i++ {
++		if i+3 <= len(s)-1 && bytes.Equal(bytes.ToLower(s[i:i+4]), []byte("url=")) {
++			c.state = stateMetaContentURL
++			return c, i + 4
++		}
++	}
++	return c, len(s)
++}
++
++// tMetaContentURL is the context transition function for the "url=" part of a meta content attribute state.
++func tMetaContentURL(c context, s []byte) (context, int) {
++	for i := 0; i < len(s); i++ {
++		if s[i] == ';' {
++			c.state = stateMetaContent
++			return c, i + 1
++		}
++	}
++	return c, len(s)
++}
++
+ // eatAttrName returns the largest j such that s[i:j] is an attribute name.
+ // It returns an error if s[i:] does not look like it begins with an
+ // attribute name, such as encountering a quote mark without a preceding
+@@ -638,6 +672,7 @@ var elementNameMap = map[string]element{
+ 	"style":    elementStyle,
+ 	"textarea": elementTextarea,
+ 	"title":    elementTitle,
++	"meta":     elementMeta,
+ }
+ 
+ // asciiAlpha reports whether c is an ASCII letter.
+diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go
+index 7178df6..90311eb 100644
+--- a/src/internal/godebugs/table.go
++++ b/src/internal/godebugs/table.go
+@@ -31,6 +31,7 @@ var All = []Info{
+ 	{Name: "gocachetest", Package: "cmd/go"},
+ 	{Name: "gocacheverify", Package: "cmd/go"},
+ 	{Name: "gotypesalias", Package: "go/types"},
++	{Name: "htmlmetacontenturlescape", Package: "html/template"},
+ 	{Name: "http2client", Package: "net/http"},
+ 	{Name: "http2debug", Package: "net/http", Opaque: true},
+ 	{Name: "http2server", Package: "net/http"},
+diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go
+index 335f787..f68e386 100644
+--- a/src/runtime/metrics/doc.go
++++ b/src/runtime/metrics/doc.go
+@@ -255,6 +255,11 @@ Below is the full list of supported metrics, ordered lexicographically.
+ 		The number of non-default behaviors executed by the go/types
+ 		package due to a non-default GODEBUG=gotypesalias=... setting.
+ 
++	/godebug/non-default-behavior/htmlmetacontenturlescape:events
++		The number of non-default behaviors executed by
++		the html/template package due to a non-default
++		GODEBUG=htmlmetacontenturlescape=... setting.
++
+ 	/godebug/non-default-behavior/http2client:events
+ 		The number of non-default behaviors executed by the net/http
+ 		package due to a non-default GODEBUG=http2client=... setting.
+-- 
+2.43.0
+