diff options
Diffstat (limited to 'meta/recipes-devtools/go/go-1.14/CVE-2022-41725.patch')
-rw-r--r-- | meta/recipes-devtools/go/go-1.14/CVE-2022-41725.patch | 660 |
1 files changed, 660 insertions, 0 deletions
diff --git a/meta/recipes-devtools/go/go-1.14/CVE-2022-41725.patch b/meta/recipes-devtools/go/go-1.14/CVE-2022-41725.patch new file mode 100644 index 0000000000..5f80c62b0b --- /dev/null +++ b/meta/recipes-devtools/go/go-1.14/CVE-2022-41725.patch | |||
@@ -0,0 +1,660 @@ | |||
1 | From 5c55ac9bf1e5f779220294c843526536605f42ab Mon Sep 17 00:00:00 2001 | ||
2 | From: Damien Neil <dneil@google.com> | ||
3 | Date: Wed, 25 Jan 2023 09:27:01 -0800 | ||
4 | Subject: [PATCH] [release-branch.go1.19] mime/multipart: limit memory/inode consumption of ReadForm | ||
5 | |||
6 | Reader.ReadForm is documented as storing "up to maxMemory bytes + 10MB" | ||
7 | in memory. Parsed forms can consume substantially more memory than | ||
8 | this limit, since ReadForm does not account for map entry overhead | ||
9 | and MIME headers. | ||
10 | |||
11 | In addition, while the amount of disk memory consumed by ReadForm can | ||
12 | be constrained by limiting the size of the parsed input, ReadForm will | ||
13 | create one temporary file per form part stored on disk, potentially | ||
14 | consuming a large number of inodes. | ||
15 | |||
16 | Update ReadForm's memory accounting to include part names, | ||
17 | MIME headers, and map entry overhead. | ||
18 | |||
19 | Update ReadForm to store all on-disk file parts in a single | ||
20 | temporary file. | ||
21 | |||
22 | Files returned by FileHeader.Open are documented as having a concrete | ||
23 | type of *os.File when a file is stored on disk. The change to use a | ||
24 | single temporary file for all parts means that this is no longer the | ||
25 | case when a form contains more than a single file part stored on disk. | ||
26 | |||
27 | The previous behavior of storing each file part in a separate disk | ||
28 | file may be reenabled with GODEBUG=multipartfiles=distinct. | ||
29 | |||
30 | Update Reader.NextPart and Reader.NextRawPart to set a 10MiB cap | ||
31 | on the size of MIME headers. | ||
32 | |||
33 | Thanks to Jakob Ackermann (@das7pad) for reporting this issue. | ||
34 | |||
35 | Updates #58006 | ||
36 | Fixes #58362 | ||
37 | Fixes CVE-2022-41725 | ||
38 | |||
39 | Change-Id: Ibd780a6c4c83ac8bcfd3cbe344f042e9940f2eab | ||
40 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1714276 | ||
41 | Reviewed-by: Julie Qiu <julieqiu@google.com> | ||
42 | TryBot-Result: Security TryBots <security-trybots@go-security-trybots.iam.gserviceaccount.com> | ||
43 | Reviewed-by: Roland Shoemaker <bracewell@google.com> | ||
44 | Run-TryBot: Damien Neil <dneil@google.com> | ||
45 | (cherry picked from commit ed4664330edcd91b24914c9371c377c132dbce8c) | ||
46 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1728949 | ||
47 | Reviewed-by: Tatiana Bradley <tatianabradley@google.com> | ||
48 | Run-TryBot: Roland Shoemaker <bracewell@google.com> | ||
49 | Reviewed-by: Damien Neil <dneil@google.com> | ||
50 | Reviewed-on: https://go-review.googlesource.com/c/go/+/468116 | ||
51 | TryBot-Result: Gopher Robot <gobot@golang.org> | ||
52 | Reviewed-by: Than McIntosh <thanm@google.com> | ||
53 | Run-TryBot: Michael Pratt <mpratt@google.com> | ||
54 | Auto-Submit: Michael Pratt <mpratt@google.com> | ||
55 | |||
56 | Upstream-Status: Backport [https://github.com/golang/go/commit/5c55ac9bf1e5f779220294c843526536605f42ab] | ||
57 | CVE: CVE-2022-41725 | ||
58 | Signed-off-by: Vijay Anusuri <vanusuri@mvista.com> | ||
59 | --- | ||
60 | src/mime/multipart/formdata.go | 132 ++++++++++++++++++++----- | ||
61 | src/mime/multipart/formdata_test.go | 140 ++++++++++++++++++++++++++- | ||
62 | src/mime/multipart/multipart.go | 25 +++-- | ||
63 | src/mime/multipart/readmimeheader.go | 14 +++ | ||
64 | src/net/http/request_test.go | 2 +- | ||
65 | src/net/textproto/reader.go | 27 ++++++ | ||
66 | 6 files changed, 303 insertions(+), 37 deletions(-) | ||
67 | create mode 100644 src/mime/multipart/readmimeheader.go | ||
68 | |||
69 | diff --git a/src/mime/multipart/formdata.go b/src/mime/multipart/formdata.go | ||
70 | index 9c42ea8..1eeb340 100644 | ||
71 | --- a/src/mime/multipart/formdata.go | ||
72 | +++ b/src/mime/multipart/formdata.go | ||
73 | @@ -7,6 +7,7 @@ package multipart | ||
74 | import ( | ||
75 | "bytes" | ||
76 | "errors" | ||
77 | + "internal/godebug" | ||
78 | "io" | ||
79 | "io/ioutil" | ||
80 | "math" | ||
81 | @@ -34,23 +35,58 @@ func (r *Reader) ReadForm(maxMemory int64) (*Form, error) { | ||
82 | |||
83 | func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { | ||
84 | form := &Form{make(map[string][]string), make(map[string][]*FileHeader)} | ||
85 | + var ( | ||
86 | + file *os.File | ||
87 | + fileOff int64 | ||
88 | + ) | ||
89 | + numDiskFiles := 0 | ||
90 | + multipartFiles := godebug.Get("multipartfiles") | ||
91 | + combineFiles := multipartFiles != "distinct" | ||
92 | defer func() { | ||
93 | + if file != nil { | ||
94 | + if cerr := file.Close(); err == nil { | ||
95 | + err = cerr | ||
96 | + } | ||
97 | + } | ||
98 | + if combineFiles && numDiskFiles > 1 { | ||
99 | + for _, fhs := range form.File { | ||
100 | + for _, fh := range fhs { | ||
101 | + fh.tmpshared = true | ||
102 | + } | ||
103 | + } | ||
104 | + } | ||
105 | if err != nil { | ||
106 | form.RemoveAll() | ||
107 | + if file != nil { | ||
108 | + os.Remove(file.Name()) | ||
109 | + } | ||
110 | } | ||
111 | }() | ||
112 | |||
113 | - // Reserve an additional 10 MB for non-file parts. | ||
114 | - maxValueBytes := maxMemory + int64(10<<20) | ||
115 | - if maxValueBytes <= 0 { | ||
116 | + // maxFileMemoryBytes is the maximum bytes of file data we will store in memory. | ||
117 | + // Data past this limit is written to disk. | ||
118 | + // This limit strictly applies to content, not metadata (filenames, MIME headers, etc.), | ||
119 | + // since metadata is always stored in memory, not disk. | ||
120 | + // | ||
121 | + // maxMemoryBytes is the maximum bytes we will store in memory, including file content, | ||
122 | + // non-file part values, metdata, and map entry overhead. | ||
123 | + // | ||
124 | + // We reserve an additional 10 MB in maxMemoryBytes for non-file data. | ||
125 | + // | ||
126 | + // The relationship between these parameters, as well as the overly-large and | ||
127 | + // unconfigurable 10 MB added on to maxMemory, is unfortunate but difficult to change | ||
128 | + // within the constraints of the API as documented. | ||
129 | + maxFileMemoryBytes := maxMemory | ||
130 | + maxMemoryBytes := maxMemory + int64(10<<20) | ||
131 | + if maxMemoryBytes <= 0 { | ||
132 | if maxMemory < 0 { | ||
133 | - maxValueBytes = 0 | ||
134 | + maxMemoryBytes = 0 | ||
135 | } else { | ||
136 | - maxValueBytes = math.MaxInt64 | ||
137 | + maxMemoryBytes = math.MaxInt64 | ||
138 | } | ||
139 | } | ||
140 | for { | ||
141 | - p, err := r.NextPart() | ||
142 | + p, err := r.nextPart(false, maxMemoryBytes) | ||
143 | if err == io.EOF { | ||
144 | break | ||
145 | } | ||
146 | @@ -64,16 +100,27 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { | ||
147 | } | ||
148 | filename := p.FileName() | ||
149 | |||
150 | + // Multiple values for the same key (one map entry, longer slice) are cheaper | ||
151 | + // than the same number of values for different keys (many map entries), but | ||
152 | + // using a consistent per-value cost for overhead is simpler. | ||
153 | + maxMemoryBytes -= int64(len(name)) | ||
154 | + maxMemoryBytes -= 100 // map overhead | ||
155 | + if maxMemoryBytes < 0 { | ||
156 | + // We can't actually take this path, since nextPart would already have | ||
157 | + // rejected the MIME headers for being too large. Check anyway. | ||
158 | + return nil, ErrMessageTooLarge | ||
159 | + } | ||
160 | + | ||
161 | var b bytes.Buffer | ||
162 | |||
163 | if filename == "" { | ||
164 | // value, store as string in memory | ||
165 | - n, err := io.CopyN(&b, p, maxValueBytes+1) | ||
166 | + n, err := io.CopyN(&b, p, maxMemoryBytes+1) | ||
167 | if err != nil && err != io.EOF { | ||
168 | return nil, err | ||
169 | } | ||
170 | - maxValueBytes -= n | ||
171 | - if maxValueBytes < 0 { | ||
172 | + maxMemoryBytes -= n | ||
173 | + if maxMemoryBytes < 0 { | ||
174 | return nil, ErrMessageTooLarge | ||
175 | } | ||
176 | form.Value[name] = append(form.Value[name], b.String()) | ||
177 | @@ -81,35 +128,45 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { | ||
178 | } | ||
179 | |||
180 | // file, store in memory or on disk | ||
181 | + maxMemoryBytes -= mimeHeaderSize(p.Header) | ||
182 | + if maxMemoryBytes < 0 { | ||
183 | + return nil, ErrMessageTooLarge | ||
184 | + } | ||
185 | fh := &FileHeader{ | ||
186 | Filename: filename, | ||
187 | Header: p.Header, | ||
188 | } | ||
189 | - n, err := io.CopyN(&b, p, maxMemory+1) | ||
190 | + n, err := io.CopyN(&b, p, maxFileMemoryBytes+1) | ||
191 | if err != nil && err != io.EOF { | ||
192 | return nil, err | ||
193 | } | ||
194 | - if n > maxMemory { | ||
195 | - // too big, write to disk and flush buffer | ||
196 | - file, err := ioutil.TempFile("", "multipart-") | ||
197 | - if err != nil { | ||
198 | - return nil, err | ||
199 | + if n > maxFileMemoryBytes { | ||
200 | + if file == nil { | ||
201 | + file, err = ioutil.TempFile(r.tempDir, "multipart-") | ||
202 | + if err != nil { | ||
203 | + return nil, err | ||
204 | + } | ||
205 | } | ||
206 | + numDiskFiles++ | ||
207 | size, err := io.Copy(file, io.MultiReader(&b, p)) | ||
208 | - if cerr := file.Close(); err == nil { | ||
209 | - err = cerr | ||
210 | - } | ||
211 | if err != nil { | ||
212 | - os.Remove(file.Name()) | ||
213 | return nil, err | ||
214 | } | ||
215 | fh.tmpfile = file.Name() | ||
216 | fh.Size = size | ||
217 | + fh.tmpoff = fileOff | ||
218 | + fileOff += size | ||
219 | + if !combineFiles { | ||
220 | + if err := file.Close(); err != nil { | ||
221 | + return nil, err | ||
222 | + } | ||
223 | + file = nil | ||
224 | + } | ||
225 | } else { | ||
226 | fh.content = b.Bytes() | ||
227 | fh.Size = int64(len(fh.content)) | ||
228 | - maxMemory -= n | ||
229 | - maxValueBytes -= n | ||
230 | + maxFileMemoryBytes -= n | ||
231 | + maxMemoryBytes -= n | ||
232 | } | ||
233 | form.File[name] = append(form.File[name], fh) | ||
234 | } | ||
235 | @@ -117,6 +174,17 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { | ||
236 | return form, nil | ||
237 | } | ||
238 | |||
239 | +func mimeHeaderSize(h textproto.MIMEHeader) (size int64) { | ||
240 | + for k, vs := range h { | ||
241 | + size += int64(len(k)) | ||
242 | + size += 100 // map entry overhead | ||
243 | + for _, v := range vs { | ||
244 | + size += int64(len(v)) | ||
245 | + } | ||
246 | + } | ||
247 | + return size | ||
248 | +} | ||
249 | + | ||
250 | // Form is a parsed multipart form. | ||
251 | // Its File parts are stored either in memory or on disk, | ||
252 | // and are accessible via the *FileHeader's Open method. | ||
253 | @@ -134,7 +202,7 @@ func (f *Form) RemoveAll() error { | ||
254 | for _, fh := range fhs { | ||
255 | if fh.tmpfile != "" { | ||
256 | e := os.Remove(fh.tmpfile) | ||
257 | - if e != nil && err == nil { | ||
258 | + if e != nil && !errors.Is(e, os.ErrNotExist) && err == nil { | ||
259 | err = e | ||
260 | } | ||
261 | } | ||
262 | @@ -149,15 +217,25 @@ type FileHeader struct { | ||
263 | Header textproto.MIMEHeader | ||
264 | Size int64 | ||
265 | |||
266 | - content []byte | ||
267 | - tmpfile string | ||
268 | + content []byte | ||
269 | + tmpfile string | ||
270 | + tmpoff int64 | ||
271 | + tmpshared bool | ||
272 | } | ||
273 | |||
274 | // Open opens and returns the FileHeader's associated File. | ||
275 | func (fh *FileHeader) Open() (File, error) { | ||
276 | if b := fh.content; b != nil { | ||
277 | r := io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b))) | ||
278 | - return sectionReadCloser{r}, nil | ||
279 | + return sectionReadCloser{r, nil}, nil | ||
280 | + } | ||
281 | + if fh.tmpshared { | ||
282 | + f, err := os.Open(fh.tmpfile) | ||
283 | + if err != nil { | ||
284 | + return nil, err | ||
285 | + } | ||
286 | + r := io.NewSectionReader(f, fh.tmpoff, fh.Size) | ||
287 | + return sectionReadCloser{r, f}, nil | ||
288 | } | ||
289 | return os.Open(fh.tmpfile) | ||
290 | } | ||
291 | @@ -176,8 +254,12 @@ type File interface { | ||
292 | |||
293 | type sectionReadCloser struct { | ||
294 | *io.SectionReader | ||
295 | + io.Closer | ||
296 | } | ||
297 | |||
298 | func (rc sectionReadCloser) Close() error { | ||
299 | + if rc.Closer != nil { | ||
300 | + return rc.Closer.Close() | ||
301 | + } | ||
302 | return nil | ||
303 | } | ||
304 | diff --git a/src/mime/multipart/formdata_test.go b/src/mime/multipart/formdata_test.go | ||
305 | index e3a3a3e..5cded71 100644 | ||
306 | --- a/src/mime/multipart/formdata_test.go | ||
307 | +++ b/src/mime/multipart/formdata_test.go | ||
308 | @@ -6,8 +6,10 @@ package multipart | ||
309 | |||
310 | import ( | ||
311 | "bytes" | ||
312 | + "fmt" | ||
313 | "io" | ||
314 | "math" | ||
315 | + "net/textproto" | ||
316 | "os" | ||
317 | "strings" | ||
318 | "testing" | ||
319 | @@ -208,8 +210,8 @@ Content-Disposition: form-data; name="largetext" | ||
320 | maxMemory int64 | ||
321 | err error | ||
322 | }{ | ||
323 | - {"smaller", 50, nil}, | ||
324 | - {"exact-fit", 25, nil}, | ||
325 | + {"smaller", 50 + int64(len("largetext")) + 100, nil}, | ||
326 | + {"exact-fit", 25 + int64(len("largetext")) + 100, nil}, | ||
327 | {"too-large", 0, ErrMessageTooLarge}, | ||
328 | } | ||
329 | for _, tc := range testCases { | ||
330 | @@ -224,7 +226,7 @@ Content-Disposition: form-data; name="largetext" | ||
331 | defer f.RemoveAll() | ||
332 | } | ||
333 | if tc.err != err { | ||
334 | - t.Fatalf("ReadForm error - got: %v; expected: %v", tc.err, err) | ||
335 | + t.Fatalf("ReadForm error - got: %v; expected: %v", err, tc.err) | ||
336 | } | ||
337 | if err == nil { | ||
338 | if g := f.Value["largetext"][0]; g != largeTextValue { | ||
339 | @@ -234,3 +236,135 @@ Content-Disposition: form-data; name="largetext" | ||
340 | }) | ||
341 | } | ||
342 | } | ||
343 | + | ||
344 | +// TestReadForm_MetadataTooLarge verifies that we account for the size of field names, | ||
345 | +// MIME headers, and map entry overhead while limiting the memory consumption of parsed forms. | ||
346 | +func TestReadForm_MetadataTooLarge(t *testing.T) { | ||
347 | + for _, test := range []struct { | ||
348 | + name string | ||
349 | + f func(*Writer) | ||
350 | + }{{ | ||
351 | + name: "large name", | ||
352 | + f: func(fw *Writer) { | ||
353 | + name := strings.Repeat("a", 10<<20) | ||
354 | + w, _ := fw.CreateFormField(name) | ||
355 | + w.Write([]byte("value")) | ||
356 | + }, | ||
357 | + }, { | ||
358 | + name: "large MIME header", | ||
359 | + f: func(fw *Writer) { | ||
360 | + h := make(textproto.MIMEHeader) | ||
361 | + h.Set("Content-Disposition", `form-data; name="a"`) | ||
362 | + h.Set("X-Foo", strings.Repeat("a", 10<<20)) | ||
363 | + w, _ := fw.CreatePart(h) | ||
364 | + w.Write([]byte("value")) | ||
365 | + }, | ||
366 | + }, { | ||
367 | + name: "many parts", | ||
368 | + f: func(fw *Writer) { | ||
369 | + for i := 0; i < 110000; i++ { | ||
370 | + w, _ := fw.CreateFormField("f") | ||
371 | + w.Write([]byte("v")) | ||
372 | + } | ||
373 | + }, | ||
374 | + }} { | ||
375 | + t.Run(test.name, func(t *testing.T) { | ||
376 | + var buf bytes.Buffer | ||
377 | + fw := NewWriter(&buf) | ||
378 | + test.f(fw) | ||
379 | + if err := fw.Close(); err != nil { | ||
380 | + t.Fatal(err) | ||
381 | + } | ||
382 | + fr := NewReader(&buf, fw.Boundary()) | ||
383 | + _, err := fr.ReadForm(0) | ||
384 | + if err != ErrMessageTooLarge { | ||
385 | + t.Errorf("fr.ReadForm() = %v, want ErrMessageTooLarge", err) | ||
386 | + } | ||
387 | + }) | ||
388 | + } | ||
389 | +} | ||
390 | + | ||
391 | +// TestReadForm_ManyFiles_Combined tests that a multipart form containing many files only | ||
392 | +// results in a single on-disk file. | ||
393 | +func TestReadForm_ManyFiles_Combined(t *testing.T) { | ||
394 | + const distinct = false | ||
395 | + testReadFormManyFiles(t, distinct) | ||
396 | +} | ||
397 | + | ||
398 | +// TestReadForm_ManyFiles_Distinct tests that setting GODEBUG=multipartfiles=distinct | ||
399 | +// results in every file in a multipart form being placed in a distinct on-disk file. | ||
400 | +func TestReadForm_ManyFiles_Distinct(t *testing.T) { | ||
401 | + t.Setenv("GODEBUG", "multipartfiles=distinct") | ||
402 | + const distinct = true | ||
403 | + testReadFormManyFiles(t, distinct) | ||
404 | +} | ||
405 | + | ||
406 | +func testReadFormManyFiles(t *testing.T, distinct bool) { | ||
407 | + var buf bytes.Buffer | ||
408 | + fw := NewWriter(&buf) | ||
409 | + const numFiles = 10 | ||
410 | + for i := 0; i < numFiles; i++ { | ||
411 | + name := fmt.Sprint(i) | ||
412 | + w, err := fw.CreateFormFile(name, name) | ||
413 | + if err != nil { | ||
414 | + t.Fatal(err) | ||
415 | + } | ||
416 | + w.Write([]byte(name)) | ||
417 | + } | ||
418 | + if err := fw.Close(); err != nil { | ||
419 | + t.Fatal(err) | ||
420 | + } | ||
421 | + fr := NewReader(&buf, fw.Boundary()) | ||
422 | + fr.tempDir = t.TempDir() | ||
423 | + form, err := fr.ReadForm(0) | ||
424 | + if err != nil { | ||
425 | + t.Fatal(err) | ||
426 | + } | ||
427 | + for i := 0; i < numFiles; i++ { | ||
428 | + name := fmt.Sprint(i) | ||
429 | + if got := len(form.File[name]); got != 1 { | ||
430 | + t.Fatalf("form.File[%q] has %v entries, want 1", name, got) | ||
431 | + } | ||
432 | + fh := form.File[name][0] | ||
433 | + file, err := fh.Open() | ||
434 | + if err != nil { | ||
435 | + t.Fatalf("form.File[%q].Open() = %v", name, err) | ||
436 | + } | ||
437 | + if distinct { | ||
438 | + if _, ok := file.(*os.File); !ok { | ||
439 | + t.Fatalf("form.File[%q].Open: %T, want *os.File", name, file) | ||
440 | + } | ||
441 | + } | ||
442 | + got, err := io.ReadAll(file) | ||
443 | + file.Close() | ||
444 | + if string(got) != name || err != nil { | ||
445 | + t.Fatalf("read form.File[%q]: %q, %v; want %q, nil", name, string(got), err, name) | ||
446 | + } | ||
447 | + } | ||
448 | + dir, err := os.Open(fr.tempDir) | ||
449 | + if err != nil { | ||
450 | + t.Fatal(err) | ||
451 | + } | ||
452 | + defer dir.Close() | ||
453 | + names, err := dir.Readdirnames(0) | ||
454 | + if err != nil { | ||
455 | + t.Fatal(err) | ||
456 | + } | ||
457 | + wantNames := 1 | ||
458 | + if distinct { | ||
459 | + wantNames = numFiles | ||
460 | + } | ||
461 | + if len(names) != wantNames { | ||
462 | + t.Fatalf("temp dir contains %v files; want 1", len(names)) | ||
463 | + } | ||
464 | + if err := form.RemoveAll(); err != nil { | ||
465 | + t.Fatalf("form.RemoveAll() = %v", err) | ||
466 | + } | ||
467 | + names, err = dir.Readdirnames(0) | ||
468 | + if err != nil { | ||
469 | + t.Fatal(err) | ||
470 | + } | ||
471 | + if len(names) != 0 { | ||
472 | + t.Fatalf("temp dir contains %v files; want 0", len(names)) | ||
473 | + } | ||
474 | +} | ||
475 | diff --git a/src/mime/multipart/multipart.go b/src/mime/multipart/multipart.go | ||
476 | index 1750300..958cef8 100644 | ||
477 | --- a/src/mime/multipart/multipart.go | ||
478 | +++ b/src/mime/multipart/multipart.go | ||
479 | @@ -121,12 +121,12 @@ func (r *stickyErrorReader) Read(p []byte) (n int, _ error) { | ||
480 | return n, r.err | ||
481 | } | ||
482 | |||
483 | -func newPart(mr *Reader, rawPart bool) (*Part, error) { | ||
484 | +func newPart(mr *Reader, rawPart bool, maxMIMEHeaderSize int64) (*Part, error) { | ||
485 | bp := &Part{ | ||
486 | Header: make(map[string][]string), | ||
487 | mr: mr, | ||
488 | } | ||
489 | - if err := bp.populateHeaders(); err != nil { | ||
490 | + if err := bp.populateHeaders(maxMIMEHeaderSize); err != nil { | ||
491 | return nil, err | ||
492 | } | ||
493 | bp.r = partReader{bp} | ||
494 | @@ -142,12 +142,16 @@ func newPart(mr *Reader, rawPart bool) (*Part, error) { | ||
495 | return bp, nil | ||
496 | } | ||
497 | |||
498 | -func (bp *Part) populateHeaders() error { | ||
499 | +func (bp *Part) populateHeaders(maxMIMEHeaderSize int64) error { | ||
500 | r := textproto.NewReader(bp.mr.bufReader) | ||
501 | - header, err := r.ReadMIMEHeader() | ||
502 | + header, err := readMIMEHeader(r, maxMIMEHeaderSize) | ||
503 | if err == nil { | ||
504 | bp.Header = header | ||
505 | } | ||
506 | + // TODO: Add a distinguishable error to net/textproto. | ||
507 | + if err != nil && err.Error() == "message too large" { | ||
508 | + err = ErrMessageTooLarge | ||
509 | + } | ||
510 | return err | ||
511 | } | ||
512 | |||
513 | @@ -287,6 +291,7 @@ func (p *Part) Close() error { | ||
514 | // isn't supported. | ||
515 | type Reader struct { | ||
516 | bufReader *bufio.Reader | ||
517 | + tempDir string // used in tests | ||
518 | |||
519 | currentPart *Part | ||
520 | partsRead int | ||
521 | @@ -297,6 +302,10 @@ type Reader struct { | ||
522 | dashBoundary []byte // "--boundary" | ||
523 | } | ||
524 | |||
525 | +// maxMIMEHeaderSize is the maximum size of a MIME header we will parse, | ||
526 | +// including header keys, values, and map overhead. | ||
527 | +const maxMIMEHeaderSize = 10 << 20 | ||
528 | + | ||
529 | // NextPart returns the next part in the multipart or an error. | ||
530 | // When there are no more parts, the error io.EOF is returned. | ||
531 | // | ||
532 | @@ -304,7 +313,7 @@ type Reader struct { | ||
533 | // has a value of "quoted-printable", that header is instead | ||
534 | // hidden and the body is transparently decoded during Read calls. | ||
535 | func (r *Reader) NextPart() (*Part, error) { | ||
536 | - return r.nextPart(false) | ||
537 | + return r.nextPart(false, maxMIMEHeaderSize) | ||
538 | } | ||
539 | |||
540 | // NextRawPart returns the next part in the multipart or an error. | ||
541 | @@ -313,10 +322,10 @@ func (r *Reader) NextPart() (*Part, error) { | ||
542 | // Unlike NextPart, it does not have special handling for | ||
543 | // "Content-Transfer-Encoding: quoted-printable". | ||
544 | func (r *Reader) NextRawPart() (*Part, error) { | ||
545 | - return r.nextPart(true) | ||
546 | + return r.nextPart(true, maxMIMEHeaderSize) | ||
547 | } | ||
548 | |||
549 | -func (r *Reader) nextPart(rawPart bool) (*Part, error) { | ||
550 | +func (r *Reader) nextPart(rawPart bool, maxMIMEHeaderSize int64) (*Part, error) { | ||
551 | if r.currentPart != nil { | ||
552 | r.currentPart.Close() | ||
553 | } | ||
554 | @@ -341,7 +350,7 @@ func (r *Reader) nextPart(rawPart bool) (*Part, error) { | ||
555 | |||
556 | if r.isBoundaryDelimiterLine(line) { | ||
557 | r.partsRead++ | ||
558 | - bp, err := newPart(r, rawPart) | ||
559 | + bp, err := newPart(r, rawPart, maxMIMEHeaderSize) | ||
560 | if err != nil { | ||
561 | return nil, err | ||
562 | } | ||
563 | diff --git a/src/mime/multipart/readmimeheader.go b/src/mime/multipart/readmimeheader.go | ||
564 | new file mode 100644 | ||
565 | index 0000000..6836928 | ||
566 | --- /dev/null | ||
567 | +++ b/src/mime/multipart/readmimeheader.go | ||
568 | @@ -0,0 +1,14 @@ | ||
569 | +// Copyright 2023 The Go Authors. All rights reserved. | ||
570 | +// Use of this source code is governed by a BSD-style | ||
571 | +// license that can be found in the LICENSE file. | ||
572 | +package multipart | ||
573 | + | ||
574 | +import ( | ||
575 | + "net/textproto" | ||
576 | + _ "unsafe" // for go:linkname | ||
577 | +) | ||
578 | + | ||
579 | +// readMIMEHeader is defined in package net/textproto. | ||
580 | +// | ||
581 | +//go:linkname readMIMEHeader net/textproto.readMIMEHeader | ||
582 | +func readMIMEHeader(r *textproto.Reader, lim int64) (textproto.MIMEHeader, error) | ||
583 | diff --git a/src/net/http/request_test.go b/src/net/http/request_test.go | ||
584 | index 94133ee..170d3f5 100644 | ||
585 | --- a/src/net/http/request_test.go | ||
586 | +++ b/src/net/http/request_test.go | ||
587 | @@ -962,7 +962,7 @@ func testMissingFile(t *testing.T, req *Request) { | ||
588 | t.Errorf("FormFile file = %v, want nil", f) | ||
589 | } | ||
590 | if fh != nil { | ||
591 | - t.Errorf("FormFile file header = %q, want nil", fh) | ||
592 | + t.Errorf("FormFile file header = %v, want nil", fh) | ||
593 | } | ||
594 | if err != ErrMissingFile { | ||
595 | t.Errorf("FormFile err = %q, want ErrMissingFile", err) | ||
596 | diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go | ||
597 | index f63f5ec..96553fb 100644 | ||
598 | --- a/src/net/textproto/reader.go | ||
599 | +++ b/src/net/textproto/reader.go | ||
600 | @@ -7,9 +7,11 @@ package textproto | ||
601 | import ( | ||
602 | "bufio" | ||
603 | "bytes" | ||
604 | + "errors" | ||
605 | "fmt" | ||
606 | "io" | ||
607 | "io/ioutil" | ||
608 | + "math" | ||
609 | "strconv" | ||
610 | "strings" | ||
611 | "sync" | ||
612 | @@ -482,6 +484,12 @@ func (r *Reader) ReadDotLines() ([]string, error) { | ||
613 | // } | ||
614 | // | ||
615 | func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) { | ||
616 | + return readMIMEHeader(r, math.MaxInt64) | ||
617 | +} | ||
618 | + | ||
619 | +// readMIMEHeader is a version of ReadMIMEHeader which takes a limit on the header size. | ||
620 | +// It is called by the mime/multipart package. | ||
621 | +func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) { | ||
622 | // Avoid lots of small slice allocations later by allocating one | ||
623 | // large one ahead of time which we'll cut up into smaller | ||
624 | // slices. If this isn't big enough later, we allocate small ones. | ||
625 | @@ -525,6 +533,15 @@ func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) { | ||
626 | continue | ||
627 | } | ||
628 | |||
629 | + // backport 5c55ac9bf1e5f779220294c843526536605f42ab | ||
630 | + // | ||
631 | + // value is computed as | ||
632 | + // value := string(bytes.TrimLeft(v, " \t")) | ||
633 | + // | ||
634 | + // in the original patch from 1.19. This relies on | ||
635 | + // 'v' which does not exist in 1.14. We leave the | ||
636 | + // 1.14 method unchanged. | ||
637 | + | ||
638 | // Skip initial spaces in value. | ||
639 | i++ // skip colon | ||
640 | for i < len(kv) && (kv[i] == ' ' || kv[i] == '\t') { | ||
641 | @@ -533,6 +550,16 @@ func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) { | ||
642 | value := string(kv[i:]) | ||
643 | |||
644 | vv := m[key] | ||
645 | + if vv == nil { | ||
646 | + lim -= int64(len(key)) | ||
647 | + lim -= 100 // map entry overhead | ||
648 | + } | ||
649 | + lim -= int64(len(value)) | ||
650 | + if lim < 0 { | ||
651 | + // TODO: This should be a distinguishable error (ErrMessageTooLarge) | ||
652 | + // to allow mime/multipart to detect it. | ||
653 | + return m, errors.New("message too large") | ||
654 | + } | ||
655 | if vv == nil && len(strs) > 0 { | ||
656 | // More than likely this will be a single-element key. | ||
657 | // Most headers aren't multi-valued. | ||
658 | -- | ||
659 | 2.25.1 | ||
660 | |||