diff options
| author | Siddharth Doshi <sdoshi@mvista.com> | 2023-09-26 09:37:54 +0530 |
|---|---|---|
| committer | Steve Sakoman <steve@sakoman.com> | 2023-10-04 05:17:51 -1000 |
| commit | cbb7afa601bc8595b17cd8386c12f64f07f8220c (patch) | |
| tree | d9787921c15b1115b0027ff1c207f08aa9efeecd | |
| parent | f27e86a4d70ca186bb1a22e8c5ea63805a8822b7 (diff) | |
| download | poky-cbb7afa601bc8595b17cd8386c12f64f07f8220c.tar.gz | |
go: Fix CVE-2023-39318 and CVE-2023-39319
Upstream-Status: Backport from [https://github.com/golang/go/commit/023b542edf38e2a1f87fcefb9f75ff2f99401b4c]
CVE: CVE-2023-39318
Upstream-Status: Backport from [https://github.com/golang/go/commit/2070531d2f53df88e312edace6c8dfc9686ab2f5]
CVE: CVE-2023-39319
(From OE-Core rev: 8de380d765d8f47a961c6e45eba1cfa4d2feb68f)
Signed-off-by: Siddharth Doshi <sdoshi@mvista.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
| -rw-r--r-- | meta/recipes-devtools/go/go-1.14.inc | 2 | ||||
| -rw-r--r-- | meta/recipes-devtools/go/go-1.14/CVE-2023-39318.patch | 238 | ||||
| -rw-r--r-- | meta/recipes-devtools/go/go-1.14/CVE-2023-39319.patch | 230 |
3 files changed, 470 insertions, 0 deletions
diff --git a/meta/recipes-devtools/go/go-1.14.inc b/meta/recipes-devtools/go/go-1.14.inc index 784b502f46..be63f64825 100644 --- a/meta/recipes-devtools/go/go-1.14.inc +++ b/meta/recipes-devtools/go/go-1.14.inc | |||
| @@ -77,6 +77,8 @@ SRC_URI += "\ | |||
| 77 | file://CVE-2023-24536_1.patch \ | 77 | file://CVE-2023-24536_1.patch \ |
| 78 | file://CVE-2023-24536_2.patch \ | 78 | file://CVE-2023-24536_2.patch \ |
| 79 | file://CVE-2023-24536_3.patch \ | 79 | file://CVE-2023-24536_3.patch \ |
| 80 | file://CVE-2023-39318.patch \ | ||
| 81 | file://CVE-2023-39319.patch \ | ||
| 80 | " | 82 | " |
| 81 | 83 | ||
| 82 | SRC_URI_append_libc-musl = " file://0009-ld-replace-glibc-dynamic-linker-with-musl.patch" | 84 | SRC_URI_append_libc-musl = " file://0009-ld-replace-glibc-dynamic-linker-with-musl.patch" |
diff --git a/meta/recipes-devtools/go/go-1.14/CVE-2023-39318.patch b/meta/recipes-devtools/go/go-1.14/CVE-2023-39318.patch new file mode 100644 index 0000000000..20e70c0485 --- /dev/null +++ b/meta/recipes-devtools/go/go-1.14/CVE-2023-39318.patch | |||
| @@ -0,0 +1,238 @@ | |||
| 1 | From 023b542edf38e2a1f87fcefb9f75ff2f99401b4c Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Roland Shoemaker <bracewell@google.com> | ||
| 3 | Date: Thu, 3 Aug 2023 12:24:13 -0700 | ||
| 4 | Subject: [PATCH] [release-branch.go1.20] html/template: support HTML-like | ||
| 5 | comments in script contexts | ||
| 6 | |||
| 7 | Per Appendix B.1.1 of the ECMAScript specification, support HTML-like | ||
| 8 | comments in script contexts. Also per section 12.5, support hashbang | ||
| 9 | comments. This brings our parsing in-line with how browsers treat these | ||
| 10 | comment types. | ||
| 11 | |||
| 12 | Thanks to Takeshi Kaneko (GMO Cybersecurity by Ierae, Inc.) for | ||
| 13 | reporting this issue. | ||
| 14 | |||
| 15 | Fixes #62196 | ||
| 16 | Fixes #62395 | ||
| 17 | Fixes CVE-2023-39318 | ||
| 18 | |||
| 19 | Change-Id: Id512702c5de3ae46cf648e268cb10e1eb392a181 | ||
| 20 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1976593 | ||
| 21 | Run-TryBot: Roland Shoemaker <bracewell@google.com> | ||
| 22 | Reviewed-by: Tatiana Bradley <tatianabradley@google.com> | ||
| 23 | Reviewed-by: Damien Neil <dneil@google.com> | ||
| 24 | Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> | ||
| 25 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/2014620 | ||
| 26 | Reviewed-on: https://go-review.googlesource.com/c/go/+/526098 | ||
| 27 | Run-TryBot: Cherry Mui <cherryyz@google.com> | ||
| 28 | TryBot-Result: Gopher Robot <gobot@golang.org> | ||
| 29 | |||
| 30 | Upstream-Status: Backport from [https://github.com/golang/go/commit/023b542edf38e2a1f87fcefb9f75ff2f99401b4c] | ||
| 31 | CVE: CVE-2023-39318 | ||
| 32 | Signed-off-by: Siddharth Doshi <sdoshi@mvista.com> | ||
| 33 | --- | ||
| 34 | src/html/template/context.go | 6 ++- | ||
| 35 | src/html/template/escape.go | 5 +- | ||
| 36 | src/html/template/escape_test.go | 10 ++++ | ||
| 37 | src/html/template/state_string.go | 4 +- | ||
| 38 | src/html/template/transition.go | 80 ++++++++++++++++++++----------- | ||
| 39 | 5 files changed, 72 insertions(+), 33 deletions(-) | ||
| 40 | |||
| 41 | diff --git a/src/html/template/context.go b/src/html/template/context.go | ||
| 42 | index 0b65313..4eb7891 100644 | ||
| 43 | --- a/src/html/template/context.go | ||
| 44 | +++ b/src/html/template/context.go | ||
| 45 | @@ -124,6 +124,10 @@ const ( | ||
| 46 | stateJSBlockCmt | ||
| 47 | // stateJSLineCmt occurs inside a JavaScript // line comment. | ||
| 48 | stateJSLineCmt | ||
| 49 | + // stateJSHTMLOpenCmt occurs inside a JavaScript <!-- HTML-like comment. | ||
| 50 | + stateJSHTMLOpenCmt | ||
| 51 | + // stateJSHTMLCloseCmt occurs inside a JavaScript --> HTML-like comment. | ||
| 52 | + stateJSHTMLCloseCmt | ||
| 53 | // stateCSS occurs inside a <style> element or style attribute. | ||
| 54 | stateCSS | ||
| 55 | // stateCSSDqStr occurs inside a CSS double quoted string. | ||
| 56 | @@ -149,7 +153,7 @@ const ( | ||
| 57 | // authors & maintainers, not for end-users or machines. | ||
| 58 | func isComment(s state) bool { | ||
| 59 | switch s { | ||
| 60 | - case stateHTMLCmt, stateJSBlockCmt, stateJSLineCmt, stateCSSBlockCmt, stateCSSLineCmt: | ||
| 61 | + case stateHTMLCmt, stateJSBlockCmt, stateJSLineCmt, stateJSHTMLOpenCmt, stateJSHTMLCloseCmt, stateCSSBlockCmt, stateCSSLineCmt: | ||
| 62 | return true | ||
| 63 | } | ||
| 64 | return false | ||
| 65 | diff --git a/src/html/template/escape.go b/src/html/template/escape.go | ||
| 66 | index 435f912..ad2ec69 100644 | ||
| 67 | --- a/src/html/template/escape.go | ||
| 68 | +++ b/src/html/template/escape.go | ||
| 69 | @@ -698,9 +698,12 @@ func (e *escaper) escapeText(c context, n *parse.TextNode) context { | ||
| 70 | if c.state != c1.state && isComment(c1.state) && c1.delim == delimNone { | ||
| 71 | // Preserve the portion between written and the comment start. | ||
| 72 | cs := i1 - 2 | ||
| 73 | - if c1.state == stateHTMLCmt { | ||
| 74 | + if c1.state == stateHTMLCmt || c1.state == stateJSHTMLOpenCmt { | ||
| 75 | // "<!--" instead of "/*" or "//" | ||
| 76 | cs -= 2 | ||
| 77 | + } else if c1.state == stateJSHTMLCloseCmt { | ||
| 78 | + // "-->" instead of "/*" or "//" | ||
| 79 | + cs -= 1 | ||
| 80 | } | ||
| 81 | b.Write(s[written:cs]) | ||
| 82 | written = i1 | ||
| 83 | diff --git a/src/html/template/escape_test.go b/src/html/template/escape_test.go | ||
| 84 | index f550691..5f41e52 100644 | ||
| 85 | --- a/src/html/template/escape_test.go | ||
| 86 | +++ b/src/html/template/escape_test.go | ||
| 87 | @@ -503,6 +503,16 @@ func TestEscape(t *testing.T) { | ||
| 88 | "<script>var a/*b*///c\nd</script>", | ||
| 89 | "<script>var a \nd</script>", | ||
| 90 | }, | ||
| 91 | + { | ||
| 92 | + "JS HTML-like comments", | ||
| 93 | + "<script>before <!-- beep\nbetween\nbefore-->boop\n</script>", | ||
| 94 | + "<script>before \nbetween\nbefore\n</script>", | ||
| 95 | + }, | ||
| 96 | + { | ||
| 97 | + "JS hashbang comment", | ||
| 98 | + "<script>#! beep\n</script>", | ||
| 99 | + "<script>\n</script>", | ||
| 100 | + }, | ||
| 101 | { | ||
| 102 | "CSS comments", | ||
| 103 | "<style>p// paragraph\n" + | ||
| 104 | diff --git a/src/html/template/state_string.go b/src/html/template/state_string.go | ||
| 105 | index 05104be..b5cfe70 100644 | ||
| 106 | --- a/src/html/template/state_string.go | ||
| 107 | +++ b/src/html/template/state_string.go | ||
| 108 | @@ -4,9 +4,9 @@ package template | ||
| 109 | |||
| 110 | import "strconv" | ||
| 111 | |||
| 112 | -const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValuestateHTMLCmtstateRCDATAstateAttrstateURLstateSrcsetstateJSstateJSDqStrstateJSSqStrstateJSRegexpstateJSBlockCmtstateJSLineCmtstateCSSstateCSSDqStrstateCSSSqStrstateCSSDqURLstateCSSSqURLstateCSSURLstateCSSBlockCmtstateCSSLineCmtstateError" | ||
| 113 | +const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValuestateHTMLCmtstateRCDATAstateAttrstateURLstateSrcsetstateJSstateJSDqStrstateJSSqStrstateJSBqStrstateJSRegexpstateJSBlockCmtstateJSLineCmtstateJSHTMLOpenCmtstateJSHTMLCloseCmtstateCSSstateCSSDqStrstateCSSSqStrstateCSSDqURLstateCSSSqURLstateCSSURLstateCSSBlockCmtstateCSSLineCmtstateErrorstateDead" | ||
| 114 | |||
| 115 | -var _state_index = [...]uint16{0, 9, 17, 30, 44, 60, 72, 83, 92, 100, 111, 118, 130, 142, 155, 170, 184, 192, 205, 218, 231, 244, 255, 271, 286, 296} | ||
| 116 | +var _state_index = [...]uint16{0, 9, 17, 30, 44, 60, 72, 83, 92, 100, 111, 118, 130, 142, 154, 167, 182, 196, 214, 233, 241, 254, 267, 280, 293, 304, 320, 335, 345, 354} | ||
| 117 | |||
| 118 | func (i state) String() string { | ||
| 119 | if i >= state(len(_state_index)-1) { | ||
| 120 | diff --git a/src/html/template/transition.go b/src/html/template/transition.go | ||
| 121 | index 92eb351..12aa4c4 100644 | ||
| 122 | --- a/src/html/template/transition.go | ||
| 123 | +++ b/src/html/template/transition.go | ||
| 124 | @@ -14,32 +14,34 @@ import ( | ||
| 125 | // the updated context and the number of bytes consumed from the front of the | ||
| 126 | // input. | ||
| 127 | var transitionFunc = [...]func(context, []byte) (context, int){ | ||
| 128 | - stateText: tText, | ||
| 129 | - stateTag: tTag, | ||
| 130 | - stateAttrName: tAttrName, | ||
| 131 | - stateAfterName: tAfterName, | ||
| 132 | - stateBeforeValue: tBeforeValue, | ||
| 133 | - stateHTMLCmt: tHTMLCmt, | ||
| 134 | - stateRCDATA: tSpecialTagEnd, | ||
| 135 | - stateAttr: tAttr, | ||
| 136 | - stateURL: tURL, | ||
| 137 | - stateSrcset: tURL, | ||
| 138 | - stateJS: tJS, | ||
| 139 | - stateJSDqStr: tJSDelimited, | ||
| 140 | - stateJSSqStr: tJSDelimited, | ||
| 141 | - stateJSBqStr: tJSDelimited, | ||
| 142 | - stateJSRegexp: tJSDelimited, | ||
| 143 | - stateJSBlockCmt: tBlockCmt, | ||
| 144 | - stateJSLineCmt: tLineCmt, | ||
| 145 | - stateCSS: tCSS, | ||
| 146 | - stateCSSDqStr: tCSSStr, | ||
| 147 | - stateCSSSqStr: tCSSStr, | ||
| 148 | - stateCSSDqURL: tCSSStr, | ||
| 149 | - stateCSSSqURL: tCSSStr, | ||
| 150 | - stateCSSURL: tCSSStr, | ||
| 151 | - stateCSSBlockCmt: tBlockCmt, | ||
| 152 | - stateCSSLineCmt: tLineCmt, | ||
| 153 | - stateError: tError, | ||
| 154 | + stateText: tText, | ||
| 155 | + stateTag: tTag, | ||
| 156 | + stateAttrName: tAttrName, | ||
| 157 | + stateAfterName: tAfterName, | ||
| 158 | + stateBeforeValue: tBeforeValue, | ||
| 159 | + stateHTMLCmt: tHTMLCmt, | ||
| 160 | + stateRCDATA: tSpecialTagEnd, | ||
| 161 | + stateAttr: tAttr, | ||
| 162 | + stateURL: tURL, | ||
| 163 | + stateSrcset: tURL, | ||
| 164 | + stateJS: tJS, | ||
| 165 | + stateJSDqStr: tJSDelimited, | ||
| 166 | + stateJSSqStr: tJSDelimited, | ||
| 167 | + stateJSBqStr: tJSDelimited, | ||
| 168 | + stateJSRegexp: tJSDelimited, | ||
| 169 | + stateJSBlockCmt: tBlockCmt, | ||
| 170 | + stateJSLineCmt: tLineCmt, | ||
| 171 | + stateJSHTMLOpenCmt: tLineCmt, | ||
| 172 | + stateJSHTMLCloseCmt: tLineCmt, | ||
| 173 | + stateCSS: tCSS, | ||
| 174 | + stateCSSDqStr: tCSSStr, | ||
| 175 | + stateCSSSqStr: tCSSStr, | ||
| 176 | + stateCSSDqURL: tCSSStr, | ||
| 177 | + stateCSSSqURL: tCSSStr, | ||
| 178 | + stateCSSURL: tCSSStr, | ||
| 179 | + stateCSSBlockCmt: tBlockCmt, | ||
| 180 | + stateCSSLineCmt: tLineCmt, | ||
| 181 | + stateError: tError, | ||
| 182 | } | ||
| 183 | |||
| 184 | var commentStart = []byte("<!--") | ||
| 185 | @@ -263,7 +265,7 @@ func tURL(c context, s []byte) (context, int) { | ||
| 186 | |||
| 187 | // tJS is the context transition function for the JS state. | ||
| 188 | func tJS(c context, s []byte) (context, int) { | ||
| 189 | - i := bytes.IndexAny(s, "\"`'/") | ||
| 190 | + i := bytes.IndexAny(s, "\"`'/<-#") | ||
| 191 | if i == -1 { | ||
| 192 | // Entire input is non string, comment, regexp tokens. | ||
| 193 | c.jsCtx = nextJSCtx(s, c.jsCtx) | ||
| 194 | @@ -293,6 +295,26 @@ func tJS(c context, s []byte) (context, int) { | ||
| 195 | err: errorf(ErrSlashAmbig, nil, 0, "'/' could start a division or regexp: %.32q", s[i:]), | ||
| 196 | }, len(s) | ||
| 197 | } | ||
| 198 | + // ECMAScript supports HTML style comments for legacy reasons, see Appendix | ||
| 199 | + // B.1.1 "HTML-like Comments". The handling of these comments is somewhat | ||
| 200 | + // confusing. Multi-line comments are not supported, i.e. anything on lines | ||
| 201 | + // between the opening and closing tokens is not considered a comment, but | ||
| 202 | + // anything following the opening or closing token, on the same line, is | ||
| 203 | + // ignored. As such we simply treat any line prefixed with "<!--" or "-->" | ||
| 204 | + // as if it were actually prefixed with "//" and move on. | ||
| 205 | + case '<': | ||
| 206 | + if i+3 < len(s) && bytes.Equal(commentStart, s[i:i+4]) { | ||
| 207 | + c.state, i = stateJSHTMLOpenCmt, i+3 | ||
| 208 | + } | ||
| 209 | + case '-': | ||
| 210 | + if i+2 < len(s) && bytes.Equal(commentEnd, s[i:i+3]) { | ||
| 211 | + c.state, i = stateJSHTMLCloseCmt, i+2 | ||
| 212 | + } | ||
| 213 | + // ECMAScript also supports "hashbang" comment lines, see Section 12.5. | ||
| 214 | + case '#': | ||
| 215 | + if i+1 < len(s) && s[i+1] == '!' { | ||
| 216 | + c.state, i = stateJSLineCmt, i+1 | ||
| 217 | + } | ||
| 218 | default: | ||
| 219 | panic("unreachable") | ||
| 220 | } | ||
| 221 | @@ -372,12 +394,12 @@ func tBlockCmt(c context, s []byte) (context, int) { | ||
| 222 | return c, i + 2 | ||
| 223 | } | ||
| 224 | |||
| 225 | -// tLineCmt is the context transition function for //comment states. | ||
| 226 | +// tLineCmt is the context transition function for //comment states, and the JS HTML-like comment state. | ||
| 227 | func tLineCmt(c context, s []byte) (context, int) { | ||
| 228 | var lineTerminators string | ||
| 229 | var endState state | ||
| 230 | switch c.state { | ||
| 231 | - case stateJSLineCmt: | ||
| 232 | + case stateJSLineCmt, stateJSHTMLOpenCmt, stateJSHTMLCloseCmt: | ||
| 233 | lineTerminators, endState = "\n\r\u2028\u2029", stateJS | ||
| 234 | case stateCSSLineCmt: | ||
| 235 | lineTerminators, endState = "\n\f\r", stateCSS | ||
| 236 | -- | ||
| 237 | 2.24.4 | ||
| 238 | |||
diff --git a/meta/recipes-devtools/go/go-1.14/CVE-2023-39319.patch b/meta/recipes-devtools/go/go-1.14/CVE-2023-39319.patch new file mode 100644 index 0000000000..69106e3e05 --- /dev/null +++ b/meta/recipes-devtools/go/go-1.14/CVE-2023-39319.patch | |||
| @@ -0,0 +1,230 @@ | |||
| 1 | From 2070531d2f53df88e312edace6c8dfc9686ab2f5 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Roland Shoemaker <bracewell@google.com> | ||
| 3 | Date: Thu, 3 Aug 2023 12:28:28 -0700 | ||
| 4 | Subject: [PATCH] [release-branch.go1.20] html/template: properly handle | ||
| 5 | special tags within the script context | ||
| 6 | |||
| 7 | The HTML specification has incredibly complex rules for how to handle | ||
| 8 | "<!--", "<script", and "</script" when they appear within literals in | ||
| 9 | the script context. Rather than attempting to apply these restrictions | ||
| 10 | (which require a significantly more complex state machine) we apply | ||
| 11 | the workaround suggested in section 4.12.1.3 of the HTML specification [1]. | ||
| 12 | |||
| 13 | More precisely, when "<!--", "<script", and "</script" appear within | ||
| 14 | literals (strings and regular expressions, ignoring comments since we | ||
| 15 | already elide their content) we replace the "<" with "\x3C". This avoids | ||
| 16 | the unintuitive behavior that using these tags within literals can cause, | ||
| 17 | by simply preventing the rendered content from triggering it. This may | ||
| 18 | break some correct usages of these tags, but on balance is more likely | ||
| 19 | to prevent XSS attacks where users are unknowingly either closing or not | ||
| 20 | closing the script blocks where they think they are. | ||
| 21 | |||
| 22 | Thanks to Takeshi Kaneko (GMO Cybersecurity by Ierae, Inc.) for | ||
| 23 | reporting this issue. | ||
| 24 | |||
| 25 | Fixes #62197 | ||
| 26 | Fixes #62397 | ||
| 27 | Fixes CVE-2023-39319 | ||
| 28 | |||
| 29 | [1] https://html.spec.whatwg.org/#restrictions-for-contents-of-script-elements | ||
| 30 | |||
| 31 | Change-Id: Iab57b0532694827e3eddf57a7497ba1fab1746dc | ||
| 32 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1976594 | ||
| 33 | Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> | ||
| 34 | Reviewed-by: Tatiana Bradley <tatianabradley@google.com> | ||
| 35 | Reviewed-by: Damien Neil <dneil@google.com> | ||
| 36 | Run-TryBot: Roland Shoemaker <bracewell@google.com> | ||
| 37 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/2014621 | ||
| 38 | TryBot-Result: Security TryBots <security-trybots@go-security-trybots.iam.gserviceaccount.com> | ||
| 39 | Reviewed-on: https://go-review.googlesource.com/c/go/+/526099 | ||
| 40 | TryBot-Result: Gopher Robot <gobot@golang.org> | ||
| 41 | Run-TryBot: Cherry Mui <cherryyz@google.com> | ||
| 42 | |||
| 43 | Upstream-Status: Backport from [https://github.com/golang/go/commit/2070531d2f53df88e312edace6c8dfc9686ab2f5] | ||
| 44 | CVE: CVE-2023-39319 | ||
| 45 | Signed-off-by: Siddharth Doshi <sdoshi@mvista.com> | ||
| 46 | --- | ||
| 47 | src/html/template/context.go | 14 ++++++++++ | ||
| 48 | src/html/template/escape.go | 26 ++++++++++++++++++ | ||
| 49 | src/html/template/escape_test.go | 47 +++++++++++++++++++++++++++++++- | ||
| 50 | src/html/template/transition.go | 15 ++++++++++ | ||
| 51 | 4 files changed, 101 insertions(+), 1 deletion(-) | ||
| 52 | |||
| 53 | diff --git a/src/html/template/context.go b/src/html/template/context.go | ||
| 54 | index 4eb7891..feb6517 100644 | ||
| 55 | --- a/src/html/template/context.go | ||
| 56 | +++ b/src/html/template/context.go | ||
| 57 | @@ -168,6 +168,20 @@ func isInTag(s state) bool { | ||
| 58 | return false | ||
| 59 | } | ||
| 60 | |||
| 61 | +// isInScriptLiteral returns true if s is one of the literal states within a | ||
| 62 | +// <script> tag, and as such occurances of "<!--", "<script", and "</script" | ||
| 63 | +// need to be treated specially. | ||
| 64 | +func isInScriptLiteral(s state) bool { | ||
| 65 | + // Ignore the comment states (stateJSBlockCmt, stateJSLineCmt, | ||
| 66 | + // stateJSHTMLOpenCmt, stateJSHTMLCloseCmt) because their content is already | ||
| 67 | + // omitted from the output. | ||
| 68 | + switch s { | ||
| 69 | + case stateJSDqStr, stateJSSqStr, stateJSBqStr, stateJSRegexp: | ||
| 70 | + return true | ||
| 71 | + } | ||
| 72 | + return false | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | // delim is the delimiter that will end the current HTML attribute. | ||
| 76 | type delim uint8 | ||
| 77 | |||
| 78 | diff --git a/src/html/template/escape.go b/src/html/template/escape.go | ||
| 79 | index ad2ec69..de8cf6f 100644 | ||
| 80 | --- a/src/html/template/escape.go | ||
| 81 | +++ b/src/html/template/escape.go | ||
| 82 | @@ -10,6 +10,7 @@ import ( | ||
| 83 | "html" | ||
| 84 | "internal/godebug" | ||
| 85 | "io" | ||
| 86 | + "regexp" | ||
| 87 | "text/template" | ||
| 88 | "text/template/parse" | ||
| 89 | ) | ||
| 90 | @@ -650,6 +651,26 @@ var delimEnds = [...]string{ | ||
| 91 | delimSpaceOrTagEnd: " \t\n\f\r>", | ||
| 92 | } | ||
| 93 | |||
| 94 | +var ( | ||
| 95 | + // Per WHATWG HTML specification, section 4.12.1.3, there are extremely | ||
| 96 | + // complicated rules for how to handle the set of opening tags <!--, | ||
| 97 | + // <script, and </script when they appear in JS literals (i.e. strings, | ||
| 98 | + // regexs, and comments). The specification suggests a simple solution, | ||
| 99 | + // rather than implementing the arcane ABNF, which involves simply escaping | ||
| 100 | + // the opening bracket with \x3C. We use the below regex for this, since it | ||
| 101 | + // makes doing the case-insensitive find-replace much simpler. | ||
| 102 | + specialScriptTagRE = regexp.MustCompile("(?i)<(script|/script|!--)") | ||
| 103 | + specialScriptTagReplacement = []byte("\\x3C$1") | ||
| 104 | +) | ||
| 105 | + | ||
| 106 | +func containsSpecialScriptTag(s []byte) bool { | ||
| 107 | + return specialScriptTagRE.Match(s) | ||
| 108 | +} | ||
| 109 | + | ||
| 110 | +func escapeSpecialScriptTags(s []byte) []byte { | ||
| 111 | + return specialScriptTagRE.ReplaceAll(s, specialScriptTagReplacement) | ||
| 112 | +} | ||
| 113 | + | ||
| 114 | var doctypeBytes = []byte("<!DOCTYPE") | ||
| 115 | |||
| 116 | // escapeText escapes a text template node. | ||
| 117 | @@ -708,6 +729,11 @@ func (e *escaper) escapeText(c context, n *parse.TextNode) context { | ||
| 118 | b.Write(s[written:cs]) | ||
| 119 | written = i1 | ||
| 120 | } | ||
| 121 | + if isInScriptLiteral(c.state) && containsSpecialScriptTag(s[i:i1]) { | ||
| 122 | + b.Write(s[written:i]) | ||
| 123 | + b.Write(escapeSpecialScriptTags(s[i:i1])) | ||
| 124 | + written = i1 | ||
| 125 | + } | ||
| 126 | if i == i1 && c.state == c1.state { | ||
| 127 | panic(fmt.Sprintf("infinite loop from %v to %v on %q..%q", c, c1, s[:i], s[i:])) | ||
| 128 | } | ||
| 129 | diff --git a/src/html/template/escape_test.go b/src/html/template/escape_test.go | ||
| 130 | index 5f41e52..0cacb20 100644 | ||
| 131 | --- a/src/html/template/escape_test.go | ||
| 132 | +++ b/src/html/template/escape_test.go | ||
| 133 | @@ -513,6 +513,21 @@ func TestEscape(t *testing.T) { | ||
| 134 | "<script>#! beep\n</script>", | ||
| 135 | "<script>\n</script>", | ||
| 136 | }, | ||
| 137 | + { | ||
| 138 | + "Special tags in <script> string literals", | ||
| 139 | + `<script>var a = "asd < 123 <!-- 456 < fgh <script jkl < 789 </script"</script>`, | ||
| 140 | + `<script>var a = "asd < 123 \x3C!-- 456 < fgh \x3Cscript jkl < 789 \x3C/script"</script>`, | ||
| 141 | + }, | ||
| 142 | + { | ||
| 143 | + "Special tags in <script> string literals (mixed case)", | ||
| 144 | + `<script>var a = "<!-- <ScripT </ScripT"</script>`, | ||
| 145 | + `<script>var a = "\x3C!-- \x3CScripT \x3C/ScripT"</script>`, | ||
| 146 | + }, | ||
| 147 | + { | ||
| 148 | + "Special tags in <script> regex literals (mixed case)", | ||
| 149 | + `<script>var a = /<!-- <ScripT </ScripT/</script>`, | ||
| 150 | + `<script>var a = /\x3C!-- \x3CScripT \x3C/ScripT/</script>`, | ||
| 151 | + }, | ||
| 152 | { | ||
| 153 | "CSS comments", | ||
| 154 | "<style>p// paragraph\n" + | ||
| 155 | @@ -1501,8 +1516,38 @@ func TestEscapeText(t *testing.T) { | ||
| 156 | context{state: stateJS, element: elementScript}, | ||
| 157 | }, | ||
| 158 | { | ||
| 159 | + // <script and </script tags are escaped, so </script> should not | ||
| 160 | + // cause us to exit the JS state. | ||
| 161 | `<script>document.write("<script>alert(1)</script>");`, | ||
| 162 | - context{state: stateText}, | ||
| 163 | + context{state: stateJS, element: elementScript}, | ||
| 164 | + }, | ||
| 165 | + { | ||
| 166 | + `<script>document.write("<script>`, | ||
| 167 | + context{state: stateJSDqStr, element: elementScript}, | ||
| 168 | + }, | ||
| 169 | + { | ||
| 170 | + `<script>document.write("<script>alert(1)</script>`, | ||
| 171 | + context{state: stateJSDqStr, element: elementScript}, | ||
| 172 | + }, | ||
| 173 | + { | ||
| 174 | + `<script>document.write("<script>alert(1)<!--`, | ||
| 175 | + context{state: stateJSDqStr, element: elementScript}, | ||
| 176 | + }, | ||
| 177 | + { | ||
| 178 | + `<script>document.write("<script>alert(1)</Script>");`, | ||
| 179 | + context{state: stateJS, element: elementScript}, | ||
| 180 | + }, | ||
| 181 | + { | ||
| 182 | + `<script>document.write("<!--");`, | ||
| 183 | + context{state: stateJS, element: elementScript}, | ||
| 184 | + }, | ||
| 185 | + { | ||
| 186 | + `<script>let a = /</script`, | ||
| 187 | + context{state: stateJSRegexp, element: elementScript}, | ||
| 188 | + }, | ||
| 189 | + { | ||
| 190 | + `<script>let a = /</script/`, | ||
| 191 | + context{state: stateJS, element: elementScript, jsCtx: jsCtxDivOp}, | ||
| 192 | }, | ||
| 193 | { | ||
| 194 | `<script type="text/template">`, | ||
| 195 | diff --git a/src/html/template/transition.go b/src/html/template/transition.go | ||
| 196 | index 12aa4c4..3d2a37c 100644 | ||
| 197 | --- a/src/html/template/transition.go | ||
| 198 | +++ b/src/html/template/transition.go | ||
| 199 | @@ -214,6 +214,11 @@ var ( | ||
| 200 | // element states. | ||
| 201 | func tSpecialTagEnd(c context, s []byte) (context, int) { | ||
| 202 | if c.element != elementNone { | ||
| 203 | + // script end tags ("</script") within script literals are ignored, so that | ||
| 204 | + // we can properly escape them. | ||
| 205 | + if c.element == elementScript && (isInScriptLiteral(c.state) || isComment(c.state)) { | ||
| 206 | + return c, len(s) | ||
| 207 | + } | ||
| 208 | if i := indexTagEnd(s, specialTagEndMarkers[c.element]); i != -1 { | ||
| 209 | return context{}, i | ||
| 210 | } | ||
| 211 | @@ -353,6 +358,16 @@ func tJSDelimited(c context, s []byte) (context, int) { | ||
| 212 | inCharset = true | ||
| 213 | case ']': | ||
| 214 | inCharset = false | ||
| 215 | + case '/': | ||
| 216 | + // If "</script" appears in a regex literal, the '/' should not | ||
| 217 | + // close the regex literal, and it will later be escaped to | ||
| 218 | + // "\x3C/script" in escapeText. | ||
| 219 | + if i > 0 && i+7 <= len(s) && bytes.Compare(bytes.ToLower(s[i-1:i+7]), []byte("</script")) == 0 { | ||
| 220 | + i++ | ||
| 221 | + } else if !inCharset { | ||
| 222 | + c.state, c.jsCtx = stateJS, jsCtxDivOp | ||
| 223 | + return c, i + 1 | ||
| 224 | + } | ||
| 225 | default: | ||
| 226 | // end delimiter | ||
| 227 | if !inCharset { | ||
| 228 | -- | ||
| 229 | 2.24.4 | ||
| 230 | |||
