summaryrefslogtreecommitdiffstats
path: root/meta/recipes-devtools/go/go-1.14/CVE-2023-39319.patch
blob: 69106e3e05e3862457f9f091e78077ae429e81d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
From 2070531d2f53df88e312edace6c8dfc9686ab2f5 Mon Sep 17 00:00:00 2001
From: Roland Shoemaker <bracewell@google.com>
Date: Thu, 3 Aug 2023 12:28:28 -0700
Subject: [PATCH] [release-branch.go1.20] html/template: properly handle
 special tags within the script context

The HTML specification has incredibly complex rules for how to handle
"<!--", "<script", and "</script" when they appear within literals in
the script context. Rather than attempting to apply these restrictions
(which require a significantly more complex state machine) we apply
the workaround suggested in section 4.12.1.3 of the HTML specification [1].

More precisely, when "<!--", "<script", and "</script" appear within
literals (strings and regular expressions, ignoring comments since we
already elide their content) we replace the "<" with "\x3C". This avoids
the unintuitive behavior that using these tags within literals can cause,
by simply preventing the rendered content from triggering it. This may
break some correct usages of these tags, but on balance is more likely
to prevent XSS attacks where users are unknowingly either closing or not
closing the script blocks where they think they are.

Thanks to Takeshi Kaneko (GMO Cybersecurity by Ierae, Inc.) for
reporting this issue.

Fixes #62197
Fixes #62397
Fixes CVE-2023-39319

[1] https://html.spec.whatwg.org/#restrictions-for-contents-of-script-elements

Change-Id: Iab57b0532694827e3eddf57a7497ba1fab1746dc
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1976594
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
Run-TryBot: Roland Shoemaker <bracewell@google.com>
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/2014621
TryBot-Result: Security TryBots <security-trybots@go-security-trybots.iam.gserviceaccount.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/526099
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Cherry Mui <cherryyz@google.com>

Upstream-Status: Backport from [https://github.com/golang/go/commit/2070531d2f53df88e312edace6c8dfc9686ab2f5]
CVE: CVE-2023-39319
Signed-off-by: Siddharth Doshi <sdoshi@mvista.com>
---
 src/html/template/context.go     | 14 ++++++++++
 src/html/template/escape.go      | 26 ++++++++++++++++++
 src/html/template/escape_test.go | 47 +++++++++++++++++++++++++++++++-
 src/html/template/transition.go  | 15 ++++++++++
 4 files changed, 101 insertions(+), 1 deletion(-)

diff --git a/src/html/template/context.go b/src/html/template/context.go
index 4eb7891..feb6517 100644
--- a/src/html/template/context.go
+++ b/src/html/template/context.go
@@ -168,6 +168,20 @@ func isInTag(s state) bool {
 	return false
 }
 
+// isInScriptLiteral returns true if s is one of the literal states within a
+// <script> tag, and as such occurances of "<!--", "<script", and "</script"
+// need to be treated specially.
+func isInScriptLiteral(s state) bool {
+	// Ignore the comment states (stateJSBlockCmt, stateJSLineCmt,
+	// stateJSHTMLOpenCmt, stateJSHTMLCloseCmt) because their content is already
+	// omitted from the output.
+	switch s {
+	case stateJSDqStr, stateJSSqStr, stateJSBqStr, stateJSRegexp:
+		return true
+	}
+	return false
+}
+
 // delim is the delimiter that will end the current HTML attribute.
 type delim uint8
 
diff --git a/src/html/template/escape.go b/src/html/template/escape.go
index ad2ec69..de8cf6f 100644
--- a/src/html/template/escape.go
+++ b/src/html/template/escape.go
@@ -10,6 +10,7 @@ import (
 	"html"
 	"internal/godebug"
 	"io"
+	"regexp"
 	"text/template"
 	"text/template/parse"
 )
@@ -650,6 +651,26 @@ var delimEnds = [...]string{
 	delimSpaceOrTagEnd: " \t\n\f\r>",
 }
 
+var (
+	// Per WHATWG HTML specification, section 4.12.1.3, there are extremely
+	// complicated rules for how to handle the set of opening tags <!--,
+	// <script, and </script when they appear in JS literals (i.e. strings,
+	// regexs, and comments). The specification suggests a simple solution,
+	// rather than implementing the arcane ABNF, which involves simply escaping
+	// the opening bracket with \x3C. We use the below regex for this, since it
+	// makes doing the case-insensitive find-replace much simpler.
+	specialScriptTagRE          = regexp.MustCompile("(?i)<(script|/script|!--)")
+	specialScriptTagReplacement = []byte("\\x3C$1")
+)
+
+func containsSpecialScriptTag(s []byte) bool {
+	return specialScriptTagRE.Match(s)
+}
+
+func escapeSpecialScriptTags(s []byte) []byte {
+	return specialScriptTagRE.ReplaceAll(s, specialScriptTagReplacement)
+}
+
 var doctypeBytes = []byte("<!DOCTYPE")
 
 // escapeText escapes a text template node.
@@ -708,6 +729,11 @@ func (e *escaper) escapeText(c context, n *parse.TextNode) context {
 			b.Write(s[written:cs])
 			written = i1
 		}
+		if isInScriptLiteral(c.state) && containsSpecialScriptTag(s[i:i1]) {
+			b.Write(s[written:i])
+			b.Write(escapeSpecialScriptTags(s[i:i1]))
+			written = i1
+		}
 		if i == i1 && c.state == c1.state {
 			panic(fmt.Sprintf("infinite loop from %v to %v on %q..%q", c, c1, s[:i], s[i:]))
 		}
diff --git a/src/html/template/escape_test.go b/src/html/template/escape_test.go
index 5f41e52..0cacb20 100644
--- a/src/html/template/escape_test.go
+++ b/src/html/template/escape_test.go
@@ -513,6 +513,21 @@ func TestEscape(t *testing.T) {
 			"<script>#! beep\n</script>",
 			"<script>\n</script>",
 		},
+		{
+			"Special tags in <script> string literals",
+			`<script>var a = "asd < 123 <!-- 456 < fgh <script jkl < 789 </script"</script>`,
+			`<script>var a = "asd < 123 \x3C!-- 456 < fgh \x3Cscript jkl < 789 \x3C/script"</script>`,
+		},
+		{
+			"Special tags in <script> string literals (mixed case)",
+			`<script>var a = "<!-- <ScripT </ScripT"</script>`,
+			`<script>var a = "\x3C!-- \x3CScripT \x3C/ScripT"</script>`,
+		},
+		{
+			"Special tags in <script> regex literals (mixed case)",
+			`<script>var a = /<!-- <ScripT </ScripT/</script>`,
+			`<script>var a = /\x3C!-- \x3CScripT \x3C/ScripT/</script>`,
+		},
 		{
 			"CSS comments",
 			"<style>p// paragraph\n" +
@@ -1501,8 +1516,38 @@ func TestEscapeText(t *testing.T) {
 			context{state: stateJS, element: elementScript},
 		},
 		{
+			// <script and </script tags are escaped, so </script> should not
+			// cause us to exit the JS state.
 			`<script>document.write("<script>alert(1)</script>");`,
-			context{state: stateText},
+			context{state: stateJS, element: elementScript},
+		},
+		{
+			`<script>document.write("<script>`,
+			context{state: stateJSDqStr, element: elementScript},
+		},
+		{
+			`<script>document.write("<script>alert(1)</script>`,
+			context{state: stateJSDqStr, element: elementScript},
+		},
+		{
+			`<script>document.write("<script>alert(1)<!--`,
+			context{state: stateJSDqStr, element: elementScript},
+		},
+		{
+			`<script>document.write("<script>alert(1)</Script>");`,
+			context{state: stateJS, element: elementScript},
+		},
+		{
+			`<script>document.write("<!--");`,
+			context{state: stateJS, element: elementScript},
+		},
+		{
+			`<script>let a = /</script`,
+			context{state: stateJSRegexp, element: elementScript},
+		},
+		{
+			`<script>let a = /</script/`,
+			context{state: stateJS, element: elementScript, jsCtx: jsCtxDivOp},
 		},
 		{
 			`<script type="text/template">`,
diff --git a/src/html/template/transition.go b/src/html/template/transition.go
index 12aa4c4..3d2a37c 100644
--- a/src/html/template/transition.go
+++ b/src/html/template/transition.go
@@ -214,6 +214,11 @@ var (
 // element states.
 func tSpecialTagEnd(c context, s []byte) (context, int) {
 	if c.element != elementNone {
+		// script end tags ("</script") within script literals are ignored, so that
+		// we can properly escape them.
+		if c.element == elementScript && (isInScriptLiteral(c.state) || isComment(c.state)) {
+			return c, len(s)
+		}
 		if i := indexTagEnd(s, specialTagEndMarkers[c.element]); i != -1 {
 			return context{}, i
 		}
@@ -353,6 +358,16 @@ func tJSDelimited(c context, s []byte) (context, int) {
 			inCharset = true
 		case ']':
 			inCharset = false
+		case '/':
+			// If "</script" appears in a regex literal, the '/' should not
+			// close the regex literal, and it will later be escaped to
+			// "\x3C/script" in escapeText.
+			if i > 0 && i+7 <= len(s) && bytes.Compare(bytes.ToLower(s[i-1:i+7]), []byte("</script")) == 0 {
+				i++
+			} else if !inCharset {
+				c.state, c.jsCtx = stateJS, jsCtxDivOp
+				return c, i + 1
+			}
 		default:
 			// end delimiter
 			if !inCharset {
-- 
2.24.4