diff options
author | Li Zhou <li.zhou@windriver.com> | 2020-09-07 16:09:06 +0800 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2020-09-10 13:21:41 +0100 |
commit | d3de07b7810151828b4ba15ffdaed5729f62d2ae (patch) | |
tree | b21dc1072570233a252fda4946ee0198d9b9e848 /meta/recipes-devtools/go/go-1.12/CVE-2020-24553.patch | |
parent | 791f8fea3fc5db939122847cf6d1d7a63a69cba9 (diff) | |
download | poky-d3de07b7810151828b4ba15ffdaed5729f62d2ae.tar.gz |
go: Security Advisory - go - CVE-2020-24553
Backport the patch from <https://github.com/golang/go/commit/
eb07103a083237414145a45f029c873d57037e06> to solve CVE-2020-24553.
(From OE-Core rev: 794dfa173adbce781c9fe609d58d3ed9b8cbd501)
Signed-off-by: Li Zhou <li.zhou@windriver.com>
Signed-off-by: Anuj Mittal <anuj.mittal@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/recipes-devtools/go/go-1.12/CVE-2020-24553.patch')
-rw-r--r-- | meta/recipes-devtools/go/go-1.12/CVE-2020-24553.patch | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/meta/recipes-devtools/go/go-1.12/CVE-2020-24553.patch b/meta/recipes-devtools/go/go-1.12/CVE-2020-24553.patch new file mode 100644 index 0000000000..18a218bc9a --- /dev/null +++ b/meta/recipes-devtools/go/go-1.12/CVE-2020-24553.patch | |||
@@ -0,0 +1,429 @@ | |||
1 | From eb07103a083237414145a45f029c873d57037e06 Mon Sep 17 00:00:00 2001 | ||
2 | From: Roberto Clapis <roberto@golang.org> | ||
3 | Date: Wed, 26 Aug 2020 08:53:03 +0200 | ||
4 | Subject: [PATCH] [release-branch.go1.15-security] net/http/cgi,net/http/fcgi: | ||
5 | add Content-Type detection | ||
6 | |||
7 | This CL ensures that responses served via CGI and FastCGI | ||
8 | have a Content-Type header based on the content of the | ||
9 | response if not explicitly set by handlers. | ||
10 | |||
11 | If the implementers of the handler did not explicitly | ||
12 | specify a Content-Type both CGI implementations would default | ||
13 | to "text/html", potentially causing cross-site scripting. | ||
14 | |||
15 | Thanks to RedTeam Pentesting GmbH for reporting this. | ||
16 | |||
17 | Fixes CVE-2020-24553 | ||
18 | |||
19 | Change-Id: I82cfc396309b5ab2e8d6e9a87eda8ea7e3799473 | ||
20 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/823217 | ||
21 | Reviewed-by: Russ Cox <rsc@google.com> | ||
22 | (cherry picked from commit 23d675d07fdc56aafd67c0a0b63d5b7e14708ff0) | ||
23 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/835311 | ||
24 | Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> | ||
25 | |||
26 | Upstream-Status: Backport | ||
27 | CVE: CVE-2020-24553 | ||
28 | Signed-off-by: Li Zhou <li.zhou@windriver.com> | ||
29 | --- | ||
30 | src/net/http/cgi/child.go | 36 ++++++++++----- | ||
31 | src/net/http/cgi/child_test.go | 69 ++++++++++++++++++++++++++++ | ||
32 | src/net/http/cgi/integration_test.go | 53 ++++++++++++++++++++- | ||
33 | src/net/http/fcgi/child.go | 39 ++++++++++++---- | ||
34 | src/net/http/fcgi/fcgi_test.go | 52 +++++++++++++++++++++ | ||
35 | 5 files changed, 227 insertions(+), 22 deletions(-) | ||
36 | |||
37 | diff --git a/src/net/http/cgi/child.go b/src/net/http/cgi/child.go | ||
38 | index 9474175f17..61de6165f6 100644 | ||
39 | --- a/src/net/http/cgi/child.go | ||
40 | +++ b/src/net/http/cgi/child.go | ||
41 | @@ -163,10 +163,12 @@ func Serve(handler http.Handler) error { | ||
42 | } | ||
43 | |||
44 | type response struct { | ||
45 | - req *http.Request | ||
46 | - header http.Header | ||
47 | - bufw *bufio.Writer | ||
48 | - headerSent bool | ||
49 | + req *http.Request | ||
50 | + header http.Header | ||
51 | + code int | ||
52 | + wroteHeader bool | ||
53 | + wroteCGIHeader bool | ||
54 | + bufw *bufio.Writer | ||
55 | } | ||
56 | |||
57 | func (r *response) Flush() { | ||
58 | @@ -178,26 +180,38 @@ func (r *response) Header() http.Header { | ||
59 | } | ||
60 | |||
61 | func (r *response) Write(p []byte) (n int, err error) { | ||
62 | - if !r.headerSent { | ||
63 | + if !r.wroteHeader { | ||
64 | r.WriteHeader(http.StatusOK) | ||
65 | } | ||
66 | + if !r.wroteCGIHeader { | ||
67 | + r.writeCGIHeader(p) | ||
68 | + } | ||
69 | return r.bufw.Write(p) | ||
70 | } | ||
71 | |||
72 | func (r *response) WriteHeader(code int) { | ||
73 | - if r.headerSent { | ||
74 | + if r.wroteHeader { | ||
75 | // Note: explicitly using Stderr, as Stdout is our HTTP output. | ||
76 | fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL) | ||
77 | return | ||
78 | } | ||
79 | - r.headerSent = true | ||
80 | - fmt.Fprintf(r.bufw, "Status: %d %s\r\n", code, http.StatusText(code)) | ||
81 | + r.wroteHeader = true | ||
82 | + r.code = code | ||
83 | +} | ||
84 | |||
85 | - // Set a default Content-Type | ||
86 | +// writeCGIHeader finalizes the header sent to the client and writes it to the output. | ||
87 | +// p is not written by writeHeader, but is the first chunk of the body | ||
88 | +// that will be written. It is sniffed for a Content-Type if none is | ||
89 | +// set explicitly. | ||
90 | +func (r *response) writeCGIHeader(p []byte) { | ||
91 | + if r.wroteCGIHeader { | ||
92 | + return | ||
93 | + } | ||
94 | + r.wroteCGIHeader = true | ||
95 | + fmt.Fprintf(r.bufw, "Status: %d %s\r\n", r.code, http.StatusText(r.code)) | ||
96 | if _, hasType := r.header["Content-Type"]; !hasType { | ||
97 | - r.header.Add("Content-Type", "text/html; charset=utf-8") | ||
98 | + r.header.Set("Content-Type", http.DetectContentType(p)) | ||
99 | } | ||
100 | - | ||
101 | r.header.Write(r.bufw) | ||
102 | r.bufw.WriteString("\r\n") | ||
103 | r.bufw.Flush() | ||
104 | diff --git a/src/net/http/cgi/child_test.go b/src/net/http/cgi/child_test.go | ||
105 | index 14e0af475f..f6ecb6eb80 100644 | ||
106 | --- a/src/net/http/cgi/child_test.go | ||
107 | +++ b/src/net/http/cgi/child_test.go | ||
108 | @@ -7,6 +7,11 @@ | ||
109 | package cgi | ||
110 | |||
111 | import ( | ||
112 | + "bufio" | ||
113 | + "bytes" | ||
114 | + "net/http" | ||
115 | + "net/http/httptest" | ||
116 | + "strings" | ||
117 | "testing" | ||
118 | ) | ||
119 | |||
120 | @@ -148,3 +153,67 @@ func TestRequestWithoutRemotePort(t *testing.T) { | ||
121 | t.Errorf("RemoteAddr: got %q; want %q", g, e) | ||
122 | } | ||
123 | } | ||
124 | + | ||
125 | +type countingWriter int | ||
126 | + | ||
127 | +func (c *countingWriter) Write(p []byte) (int, error) { | ||
128 | + *c += countingWriter(len(p)) | ||
129 | + return len(p), nil | ||
130 | +} | ||
131 | +func (c *countingWriter) WriteString(p string) (int, error) { | ||
132 | + *c += countingWriter(len(p)) | ||
133 | + return len(p), nil | ||
134 | +} | ||
135 | + | ||
136 | +func TestResponse(t *testing.T) { | ||
137 | + var tests = []struct { | ||
138 | + name string | ||
139 | + body string | ||
140 | + wantCT string | ||
141 | + }{ | ||
142 | + { | ||
143 | + name: "no body", | ||
144 | + wantCT: "text/plain; charset=utf-8", | ||
145 | + }, | ||
146 | + { | ||
147 | + name: "html", | ||
148 | + body: "<html><head><title>test page</title></head><body>This is a body</body></html>", | ||
149 | + wantCT: "text/html; charset=utf-8", | ||
150 | + }, | ||
151 | + { | ||
152 | + name: "text", | ||
153 | + body: strings.Repeat("gopher", 86), | ||
154 | + wantCT: "text/plain; charset=utf-8", | ||
155 | + }, | ||
156 | + { | ||
157 | + name: "jpg", | ||
158 | + body: "\xFF\xD8\xFF" + strings.Repeat("B", 1024), | ||
159 | + wantCT: "image/jpeg", | ||
160 | + }, | ||
161 | + } | ||
162 | + for _, tt := range tests { | ||
163 | + t.Run(tt.name, func(t *testing.T) { | ||
164 | + var buf bytes.Buffer | ||
165 | + resp := response{ | ||
166 | + req: httptest.NewRequest("GET", "/", nil), | ||
167 | + header: http.Header{}, | ||
168 | + bufw: bufio.NewWriter(&buf), | ||
169 | + } | ||
170 | + n, err := resp.Write([]byte(tt.body)) | ||
171 | + if err != nil { | ||
172 | + t.Errorf("Write: unexpected %v", err) | ||
173 | + } | ||
174 | + if want := len(tt.body); n != want { | ||
175 | + t.Errorf("reported short Write: got %v want %v", n, want) | ||
176 | + } | ||
177 | + resp.writeCGIHeader(nil) | ||
178 | + resp.Flush() | ||
179 | + if got := resp.Header().Get("Content-Type"); got != tt.wantCT { | ||
180 | + t.Errorf("wrong content-type: got %q, want %q", got, tt.wantCT) | ||
181 | + } | ||
182 | + if !bytes.HasSuffix(buf.Bytes(), []byte(tt.body)) { | ||
183 | + t.Errorf("body was not correctly written") | ||
184 | + } | ||
185 | + }) | ||
186 | + } | ||
187 | +} | ||
188 | diff --git a/src/net/http/cgi/integration_test.go b/src/net/http/cgi/integration_test.go | ||
189 | index 32d59c09a3..295c3b82d4 100644 | ||
190 | --- a/src/net/http/cgi/integration_test.go | ||
191 | +++ b/src/net/http/cgi/integration_test.go | ||
192 | @@ -16,7 +16,9 @@ import ( | ||
193 | "io" | ||
194 | "net/http" | ||
195 | "net/http/httptest" | ||
196 | + "net/url" | ||
197 | "os" | ||
198 | + "strings" | ||
199 | "testing" | ||
200 | "time" | ||
201 | ) | ||
202 | @@ -52,7 +54,7 @@ func TestHostingOurselves(t *testing.T) { | ||
203 | } | ||
204 | replay := runCgiTest(t, h, "GET /test.go?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap) | ||
205 | |||
206 | - if expected, got := "text/html; charset=utf-8", replay.Header().Get("Content-Type"); got != expected { | ||
207 | + if expected, got := "text/plain; charset=utf-8", replay.Header().Get("Content-Type"); got != expected { | ||
208 | t.Errorf("got a Content-Type of %q; expected %q", got, expected) | ||
209 | } | ||
210 | if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected { | ||
211 | @@ -152,6 +154,51 @@ func TestChildOnlyHeaders(t *testing.T) { | ||
212 | } | ||
213 | } | ||
214 | |||
215 | +func TestChildContentType(t *testing.T) { | ||
216 | + testenv.MustHaveExec(t) | ||
217 | + | ||
218 | + h := &Handler{ | ||
219 | + Path: os.Args[0], | ||
220 | + Root: "/test.go", | ||
221 | + Args: []string{"-test.run=TestBeChildCGIProcess"}, | ||
222 | + } | ||
223 | + var tests = []struct { | ||
224 | + name string | ||
225 | + body string | ||
226 | + wantCT string | ||
227 | + }{ | ||
228 | + { | ||
229 | + name: "no body", | ||
230 | + wantCT: "text/plain; charset=utf-8", | ||
231 | + }, | ||
232 | + { | ||
233 | + name: "html", | ||
234 | + body: "<html><head><title>test page</title></head><body>This is a body</body></html>", | ||
235 | + wantCT: "text/html; charset=utf-8", | ||
236 | + }, | ||
237 | + { | ||
238 | + name: "text", | ||
239 | + body: strings.Repeat("gopher", 86), | ||
240 | + wantCT: "text/plain; charset=utf-8", | ||
241 | + }, | ||
242 | + { | ||
243 | + name: "jpg", | ||
244 | + body: "\xFF\xD8\xFF" + strings.Repeat("B", 1024), | ||
245 | + wantCT: "image/jpeg", | ||
246 | + }, | ||
247 | + } | ||
248 | + for _, tt := range tests { | ||
249 | + t.Run(tt.name, func(t *testing.T) { | ||
250 | + expectedMap := map[string]string{"_body": tt.body} | ||
251 | + req := fmt.Sprintf("GET /test.go?exact-body=%s HTTP/1.0\nHost: example.com\n\n", url.QueryEscape(tt.body)) | ||
252 | + replay := runCgiTest(t, h, req, expectedMap) | ||
253 | + if got := replay.Header().Get("Content-Type"); got != tt.wantCT { | ||
254 | + t.Errorf("got a Content-Type of %q; expected it to start with %q", got, tt.wantCT) | ||
255 | + } | ||
256 | + }) | ||
257 | + } | ||
258 | +} | ||
259 | + | ||
260 | // golang.org/issue/7198 | ||
261 | func Test500WithNoHeaders(t *testing.T) { want500Test(t, "/immediate-disconnect") } | ||
262 | func Test500WithNoContentType(t *testing.T) { want500Test(t, "/no-content-type") } | ||
263 | @@ -203,6 +250,10 @@ func TestBeChildCGIProcess(t *testing.T) { | ||
264 | if req.FormValue("no-body") == "1" { | ||
265 | return | ||
266 | } | ||
267 | + if eb, ok := req.Form["exact-body"]; ok { | ||
268 | + io.WriteString(rw, eb[0]) | ||
269 | + return | ||
270 | + } | ||
271 | if req.FormValue("write-forever") == "1" { | ||
272 | io.Copy(rw, neverEnding('a')) | ||
273 | for { | ||
274 | diff --git a/src/net/http/fcgi/child.go b/src/net/http/fcgi/child.go | ||
275 | index 30a6b2ce2d..a31273b3ec 100644 | ||
276 | --- a/src/net/http/fcgi/child.go | ||
277 | +++ b/src/net/http/fcgi/child.go | ||
278 | @@ -74,10 +74,12 @@ func (r *request) parseParams() { | ||
279 | |||
280 | // response implements http.ResponseWriter. | ||
281 | type response struct { | ||
282 | - req *request | ||
283 | - header http.Header | ||
284 | - w *bufWriter | ||
285 | - wroteHeader bool | ||
286 | + req *request | ||
287 | + header http.Header | ||
288 | + code int | ||
289 | + wroteHeader bool | ||
290 | + wroteCGIHeader bool | ||
291 | + w *bufWriter | ||
292 | } | ||
293 | |||
294 | func newResponse(c *child, req *request) *response { | ||
295 | @@ -92,11 +94,14 @@ func (r *response) Header() http.Header { | ||
296 | return r.header | ||
297 | } | ||
298 | |||
299 | -func (r *response) Write(data []byte) (int, error) { | ||
300 | +func (r *response) Write(p []byte) (n int, err error) { | ||
301 | if !r.wroteHeader { | ||
302 | r.WriteHeader(http.StatusOK) | ||
303 | } | ||
304 | - return r.w.Write(data) | ||
305 | + if !r.wroteCGIHeader { | ||
306 | + r.writeCGIHeader(p) | ||
307 | + } | ||
308 | + return r.w.Write(p) | ||
309 | } | ||
310 | |||
311 | func (r *response) WriteHeader(code int) { | ||
312 | @@ -104,22 +109,34 @@ func (r *response) WriteHeader(code int) { | ||
313 | return | ||
314 | } | ||
315 | r.wroteHeader = true | ||
316 | + r.code = code | ||
317 | if code == http.StatusNotModified { | ||
318 | // Must not have body. | ||
319 | r.header.Del("Content-Type") | ||
320 | r.header.Del("Content-Length") | ||
321 | r.header.Del("Transfer-Encoding") | ||
322 | - } else if r.header.Get("Content-Type") == "" { | ||
323 | - r.header.Set("Content-Type", "text/html; charset=utf-8") | ||
324 | } | ||
325 | - | ||
326 | if r.header.Get("Date") == "" { | ||
327 | r.header.Set("Date", time.Now().UTC().Format(http.TimeFormat)) | ||
328 | } | ||
329 | +} | ||
330 | |||
331 | - fmt.Fprintf(r.w, "Status: %d %s\r\n", code, http.StatusText(code)) | ||
332 | +// writeCGIHeader finalizes the header sent to the client and writes it to the output. | ||
333 | +// p is not written by writeHeader, but is the first chunk of the body | ||
334 | +// that will be written. It is sniffed for a Content-Type if none is | ||
335 | +// set explicitly. | ||
336 | +func (r *response) writeCGIHeader(p []byte) { | ||
337 | + if r.wroteCGIHeader { | ||
338 | + return | ||
339 | + } | ||
340 | + r.wroteCGIHeader = true | ||
341 | + fmt.Fprintf(r.w, "Status: %d %s\r\n", r.code, http.StatusText(r.code)) | ||
342 | + if _, hasType := r.header["Content-Type"]; r.code != http.StatusNotModified && !hasType { | ||
343 | + r.header.Set("Content-Type", http.DetectContentType(p)) | ||
344 | + } | ||
345 | r.header.Write(r.w) | ||
346 | r.w.WriteString("\r\n") | ||
347 | + r.w.Flush() | ||
348 | } | ||
349 | |||
350 | func (r *response) Flush() { | ||
351 | @@ -290,6 +307,8 @@ func (c *child) serveRequest(req *request, body io.ReadCloser) { | ||
352 | httpReq = httpReq.WithContext(envVarCtx) | ||
353 | c.handler.ServeHTTP(r, httpReq) | ||
354 | } | ||
355 | + // Make sure we serve something even if nothing was written to r | ||
356 | + r.Write(nil) | ||
357 | r.Close() | ||
358 | c.mu.Lock() | ||
359 | delete(c.requests, req.reqId) | ||
360 | diff --git a/src/net/http/fcgi/fcgi_test.go b/src/net/http/fcgi/fcgi_test.go | ||
361 | index e9d2b34023..4a27a12c35 100644 | ||
362 | --- a/src/net/http/fcgi/fcgi_test.go | ||
363 | +++ b/src/net/http/fcgi/fcgi_test.go | ||
364 | @@ -10,6 +10,7 @@ import ( | ||
365 | "io" | ||
366 | "io/ioutil" | ||
367 | "net/http" | ||
368 | + "strings" | ||
369 | "testing" | ||
370 | ) | ||
371 | |||
372 | @@ -344,3 +345,54 @@ func TestChildServeReadsEnvVars(t *testing.T) { | ||
373 | <-done | ||
374 | } | ||
375 | } | ||
376 | + | ||
377 | +func TestResponseWriterSniffsContentType(t *testing.T) { | ||
378 | + var tests = []struct { | ||
379 | + name string | ||
380 | + body string | ||
381 | + wantCT string | ||
382 | + }{ | ||
383 | + { | ||
384 | + name: "no body", | ||
385 | + wantCT: "text/plain; charset=utf-8", | ||
386 | + }, | ||
387 | + { | ||
388 | + name: "html", | ||
389 | + body: "<html><head><title>test page</title></head><body>This is a body</body></html>", | ||
390 | + wantCT: "text/html; charset=utf-8", | ||
391 | + }, | ||
392 | + { | ||
393 | + name: "text", | ||
394 | + body: strings.Repeat("gopher", 86), | ||
395 | + wantCT: "text/plain; charset=utf-8", | ||
396 | + }, | ||
397 | + { | ||
398 | + name: "jpg", | ||
399 | + body: "\xFF\xD8\xFF" + strings.Repeat("B", 1024), | ||
400 | + wantCT: "image/jpeg", | ||
401 | + }, | ||
402 | + } | ||
403 | + for _, tt := range tests { | ||
404 | + t.Run(tt.name, func(t *testing.T) { | ||
405 | + input := make([]byte, len(streamFullRequestStdin)) | ||
406 | + copy(input, streamFullRequestStdin) | ||
407 | + rc := nopWriteCloser{bytes.NewBuffer(input)} | ||
408 | + done := make(chan bool) | ||
409 | + var resp *response | ||
410 | + c := newChild(rc, http.HandlerFunc(func( | ||
411 | + w http.ResponseWriter, | ||
412 | + r *http.Request, | ||
413 | + ) { | ||
414 | + io.WriteString(w, tt.body) | ||
415 | + resp = w.(*response) | ||
416 | + done <- true | ||
417 | + })) | ||
418 | + defer c.cleanUp() | ||
419 | + go c.serve() | ||
420 | + <-done | ||
421 | + if got := resp.Header().Get("Content-Type"); got != tt.wantCT { | ||
422 | + t.Errorf("got a Content-Type of %q; expected it to start with %q", got, tt.wantCT) | ||
423 | + } | ||
424 | + }) | ||
425 | + } | ||
426 | +} | ||
427 | -- | ||
428 | 2.17.1 | ||
429 | |||