diff options
author | Sakib Sajal <sakib.sajal@windriver.com> | 2023-08-01 17:18:11 -0700 |
---|---|---|
committer | Steve Sakoman <steve@sakoman.com> | 2023-08-07 04:40:44 -1000 |
commit | 1ba43f2c8858ffb0a05644b455daf5ea75aa2fe5 (patch) | |
tree | 65354063944cf70c59f3665a05bbbdb1defed31f | |
parent | ae7992e3b7b688e1e06b20c92aaa60af01f6bbbb (diff) | |
download | poky-1ba43f2c8858ffb0a05644b455daf5ea75aa2fe5.tar.gz |
go: fix CVE-2023-24536
Backport required patches to fix CVE-2023-24536.
(From OE-Core rev: a774c895f4a425979cef8e05e8dd17c2dcb67654)
Signed-off-by: Sakib Sajal <sakib.sajal@windriver.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
-rw-r--r-- | meta/recipes-devtools/go/go-1.17.13.inc | 3 | ||||
-rw-r--r-- | meta/recipes-devtools/go/go-1.19/CVE-2023-24536_1.patch | 137 | ||||
-rw-r--r-- | meta/recipes-devtools/go/go-1.19/CVE-2023-24536_2.patch | 187 | ||||
-rw-r--r-- | meta/recipes-devtools/go/go-1.19/CVE-2023-24536_3.patch | 349 |
4 files changed, 676 insertions, 0 deletions
diff --git a/meta/recipes-devtools/go/go-1.17.13.inc b/meta/recipes-devtools/go/go-1.17.13.inc index 36904a92fb..53e09a545c 100644 --- a/meta/recipes-devtools/go/go-1.17.13.inc +++ b/meta/recipes-devtools/go/go-1.17.13.inc | |||
@@ -37,6 +37,9 @@ SRC_URI += "\ | |||
37 | file://CVE-2023-29402.patch \ | 37 | file://CVE-2023-29402.patch \ |
38 | file://CVE-2023-29400.patch \ | 38 | file://CVE-2023-29400.patch \ |
39 | file://CVE-2023-29406.patch \ | 39 | file://CVE-2023-29406.patch \ |
40 | file://CVE-2023-24536_1.patch \ | ||
41 | file://CVE-2023-24536_2.patch \ | ||
42 | file://CVE-2023-24536_3.patch \ | ||
40 | " | 43 | " |
41 | SRC_URI[main.sha256sum] = "a1a48b23afb206f95e7bbaa9b898d965f90826f6f1d1fc0c1d784ada0cd300fd" | 44 | SRC_URI[main.sha256sum] = "a1a48b23afb206f95e7bbaa9b898d965f90826f6f1d1fc0c1d784ada0cd300fd" |
42 | 45 | ||
diff --git a/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_1.patch b/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_1.patch new file mode 100644 index 0000000000..ff9ba18ec5 --- /dev/null +++ b/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_1.patch | |||
@@ -0,0 +1,137 @@ | |||
1 | From f8d691d335c6ac14bcbae6886b5bf8ca8bf1e6a5 Mon Sep 17 00:00:00 2001 | ||
2 | From: Damien Neil <dneil@google.com> | ||
3 | Date: Thu, 16 Mar 2023 14:18:04 -0700 | ||
4 | Subject: [PATCH 1/3] mime/multipart: avoid excessive copy buffer allocations | ||
5 | in ReadForm | ||
6 | |||
7 | When copying form data to disk with io.Copy, | ||
8 | allocate only one copy buffer and reuse it rather than | ||
9 | creating two buffers per file (one from io.multiReader.WriteTo, | ||
10 | and a second one from os.File.ReadFrom). | ||
11 | |||
12 | Thanks to Jakob Ackermann (@das7pad) for reporting this issue. | ||
13 | |||
14 | For CVE-2023-24536 | ||
15 | For #59153 | ||
16 | For #59269 | ||
17 | |||
18 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802453 | ||
19 | Run-TryBot: Damien Neil <dneil@google.com> | ||
20 | Reviewed-by: Julie Qiu <julieqiu@google.com> | ||
21 | Reviewed-by: Roland Shoemaker <bracewell@google.com> | ||
22 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802395 | ||
23 | Run-TryBot: Roland Shoemaker <bracewell@google.com> | ||
24 | Reviewed-by: Damien Neil <dneil@google.com> | ||
25 | Change-Id: Ie405470c92abffed3356913b37d813e982c96c8b | ||
26 | Reviewed-on: https://go-review.googlesource.com/c/go/+/481983 | ||
27 | Run-TryBot: Michael Knyszek <mknyszek@google.com> | ||
28 | TryBot-Result: Gopher Robot <gobot@golang.org> | ||
29 | Auto-Submit: Michael Knyszek <mknyszek@google.com> | ||
30 | Reviewed-by: Matthew Dempsky <mdempsky@google.com> | ||
31 | |||
32 | CVE: CVE-2023-24536 | ||
33 | Upstream-Status: Backport [ef41a4e2face45e580c5836eaebd51629fc23f15] | ||
34 | Signed-off-by: Sakib Sajal <sakib.sajal@windriver.com> | ||
35 | --- | ||
36 | src/mime/multipart/formdata.go | 15 +++++++-- | ||
37 | src/mime/multipart/formdata_test.go | 49 +++++++++++++++++++++++++++++ | ||
38 | 2 files changed, 61 insertions(+), 3 deletions(-) | ||
39 | |||
40 | diff --git a/src/mime/multipart/formdata.go b/src/mime/multipart/formdata.go | ||
41 | index a7d4ca9..975dcb6 100644 | ||
42 | --- a/src/mime/multipart/formdata.go | ||
43 | +++ b/src/mime/multipart/formdata.go | ||
44 | @@ -84,6 +84,7 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { | ||
45 | maxMemoryBytes = math.MaxInt64 | ||
46 | } | ||
47 | } | ||
48 | + var copyBuf []byte | ||
49 | for { | ||
50 | p, err := r.nextPart(false, maxMemoryBytes) | ||
51 | if err == io.EOF { | ||
52 | @@ -147,14 +148,22 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { | ||
53 | } | ||
54 | } | ||
55 | numDiskFiles++ | ||
56 | - size, err := io.Copy(file, io.MultiReader(&b, p)) | ||
57 | + if _, err := file.Write(b.Bytes()); err != nil { | ||
58 | + return nil, err | ||
59 | + } | ||
60 | + if copyBuf == nil { | ||
61 | + copyBuf = make([]byte, 32*1024) // same buffer size as io.Copy uses | ||
62 | + } | ||
63 | + // os.File.ReadFrom will allocate its own copy buffer if we let io.Copy use it. | ||
64 | + type writerOnly struct{ io.Writer } | ||
65 | + remainingSize, err := io.CopyBuffer(writerOnly{file}, p, copyBuf) | ||
66 | if err != nil { | ||
67 | return nil, err | ||
68 | } | ||
69 | fh.tmpfile = file.Name() | ||
70 | - fh.Size = size | ||
71 | + fh.Size = int64(b.Len()) + remainingSize | ||
72 | fh.tmpoff = fileOff | ||
73 | - fileOff += size | ||
74 | + fileOff += fh.Size | ||
75 | if !combineFiles { | ||
76 | if err := file.Close(); err != nil { | ||
77 | return nil, err | ||
78 | diff --git a/src/mime/multipart/formdata_test.go b/src/mime/multipart/formdata_test.go | ||
79 | index 5cded71..f5b5608 100644 | ||
80 | --- a/src/mime/multipart/formdata_test.go | ||
81 | +++ b/src/mime/multipart/formdata_test.go | ||
82 | @@ -368,3 +368,52 @@ func testReadFormManyFiles(t *testing.T, distinct bool) { | ||
83 | t.Fatalf("temp dir contains %v files; want 0", len(names)) | ||
84 | } | ||
85 | } | ||
86 | + | ||
87 | +func BenchmarkReadForm(b *testing.B) { | ||
88 | + for _, test := range []struct { | ||
89 | + name string | ||
90 | + form func(fw *Writer, count int) | ||
91 | + }{{ | ||
92 | + name: "fields", | ||
93 | + form: func(fw *Writer, count int) { | ||
94 | + for i := 0; i < count; i++ { | ||
95 | + w, _ := fw.CreateFormField(fmt.Sprintf("field%v", i)) | ||
96 | + fmt.Fprintf(w, "value %v", i) | ||
97 | + } | ||
98 | + }, | ||
99 | + }, { | ||
100 | + name: "files", | ||
101 | + form: func(fw *Writer, count int) { | ||
102 | + for i := 0; i < count; i++ { | ||
103 | + w, _ := fw.CreateFormFile(fmt.Sprintf("field%v", i), fmt.Sprintf("file%v", i)) | ||
104 | + fmt.Fprintf(w, "value %v", i) | ||
105 | + } | ||
106 | + }, | ||
107 | + }} { | ||
108 | + b.Run(test.name, func(b *testing.B) { | ||
109 | + for _, maxMemory := range []int64{ | ||
110 | + 0, | ||
111 | + 1 << 20, | ||
112 | + } { | ||
113 | + var buf bytes.Buffer | ||
114 | + fw := NewWriter(&buf) | ||
115 | + test.form(fw, 10) | ||
116 | + if err := fw.Close(); err != nil { | ||
117 | + b.Fatal(err) | ||
118 | + } | ||
119 | + b.Run(fmt.Sprintf("maxMemory=%v", maxMemory), func(b *testing.B) { | ||
120 | + b.ReportAllocs() | ||
121 | + for i := 0; i < b.N; i++ { | ||
122 | + fr := NewReader(bytes.NewReader(buf.Bytes()), fw.Boundary()) | ||
123 | + form, err := fr.ReadForm(maxMemory) | ||
124 | + if err != nil { | ||
125 | + b.Fatal(err) | ||
126 | + } | ||
127 | + form.RemoveAll() | ||
128 | + } | ||
129 | + | ||
130 | + }) | ||
131 | + } | ||
132 | + }) | ||
133 | + } | ||
134 | +} | ||
135 | -- | ||
136 | 2.35.5 | ||
137 | |||
diff --git a/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_2.patch b/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_2.patch new file mode 100644 index 0000000000..704a1fb567 --- /dev/null +++ b/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_2.patch | |||
@@ -0,0 +1,187 @@ | |||
1 | From 4174a87b600c58e8cc00d9d18d0c507c67ca5d41 Mon Sep 17 00:00:00 2001 | ||
2 | From: Damien Neil <dneil@google.com> | ||
3 | Date: Thu, 16 Mar 2023 16:56:12 -0700 | ||
4 | Subject: [PATCH 2/3] net/textproto, mime/multipart: improve accounting of | ||
5 | non-file data | ||
6 | |||
7 | For requests containing large numbers of small parts, | ||
8 | memory consumption of a parsed form could be about 250% | ||
9 | over the estimated size. | ||
10 | |||
11 | When considering the size of parsed forms, account for the size of | ||
12 | FileHeader structs and increase the estimate of memory consumed by | ||
13 | map entries. | ||
14 | |||
15 | Thanks to Jakob Ackermann (@das7pad) for reporting this issue. | ||
16 | |||
17 | For CVE-2023-24536 | ||
18 | For #59153 | ||
19 | For #59269 | ||
20 | |||
21 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802454 | ||
22 | Run-TryBot: Damien Neil <dneil@google.com> | ||
23 | Reviewed-by: Roland Shoemaker <bracewell@google.com> | ||
24 | Reviewed-by: Julie Qiu <julieqiu@google.com> | ||
25 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802396 | ||
26 | Run-TryBot: Roland Shoemaker <bracewell@google.com> | ||
27 | Reviewed-by: Damien Neil <dneil@google.com> | ||
28 | Change-Id: I31bc50e9346b4eee6fbe51a18c3c57230cc066db | ||
29 | Reviewed-on: https://go-review.googlesource.com/c/go/+/481984 | ||
30 | Reviewed-by: Matthew Dempsky <mdempsky@google.com> | ||
31 | Auto-Submit: Michael Knyszek <mknyszek@google.com> | ||
32 | TryBot-Result: Gopher Robot <gobot@golang.org> | ||
33 | Run-TryBot: Michael Knyszek <mknyszek@google.com> | ||
34 | |||
35 | CVE: CVE-2023-24536 | ||
36 | Upstream-Status: Backport [7a359a651c7ebdb29e0a1c03102fce793e9f58f0] | ||
37 | Signed-off-by: Sakib Sajal <sakib.sajal@windriver.com> | ||
38 | --- | ||
39 | src/mime/multipart/formdata.go | 9 +++-- | ||
40 | src/mime/multipart/formdata_test.go | 55 ++++++++++++----------------- | ||
41 | src/net/textproto/reader.go | 8 ++++- | ||
42 | 3 files changed, 37 insertions(+), 35 deletions(-) | ||
43 | |||
44 | diff --git a/src/mime/multipart/formdata.go b/src/mime/multipart/formdata.go | ||
45 | index 975dcb6..3f6ff69 100644 | ||
46 | --- a/src/mime/multipart/formdata.go | ||
47 | +++ b/src/mime/multipart/formdata.go | ||
48 | @@ -103,8 +103,9 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { | ||
49 | // Multiple values for the same key (one map entry, longer slice) are cheaper | ||
50 | // than the same number of values for different keys (many map entries), but | ||
51 | // using a consistent per-value cost for overhead is simpler. | ||
52 | + const mapEntryOverhead = 200 | ||
53 | maxMemoryBytes -= int64(len(name)) | ||
54 | - maxMemoryBytes -= 100 // map overhead | ||
55 | + maxMemoryBytes -= mapEntryOverhead | ||
56 | if maxMemoryBytes < 0 { | ||
57 | // We can't actually take this path, since nextPart would already have | ||
58 | // rejected the MIME headers for being too large. Check anyway. | ||
59 | @@ -128,7 +129,10 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { | ||
60 | } | ||
61 | |||
62 | // file, store in memory or on disk | ||
63 | + const fileHeaderSize = 100 | ||
64 | maxMemoryBytes -= mimeHeaderSize(p.Header) | ||
65 | + maxMemoryBytes -= mapEntryOverhead | ||
66 | + maxMemoryBytes -= fileHeaderSize | ||
67 | if maxMemoryBytes < 0 { | ||
68 | return nil, ErrMessageTooLarge | ||
69 | } | ||
70 | @@ -183,9 +187,10 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { | ||
71 | } | ||
72 | |||
73 | func mimeHeaderSize(h textproto.MIMEHeader) (size int64) { | ||
74 | + size = 400 | ||
75 | for k, vs := range h { | ||
76 | size += int64(len(k)) | ||
77 | - size += 100 // map entry overhead | ||
78 | + size += 200 // map entry overhead | ||
79 | for _, v := range vs { | ||
80 | size += int64(len(v)) | ||
81 | } | ||
82 | diff --git a/src/mime/multipart/formdata_test.go b/src/mime/multipart/formdata_test.go | ||
83 | index f5b5608..8ed26e0 100644 | ||
84 | --- a/src/mime/multipart/formdata_test.go | ||
85 | +++ b/src/mime/multipart/formdata_test.go | ||
86 | @@ -192,10 +192,10 @@ func (r *failOnReadAfterErrorReader) Read(p []byte) (n int, err error) { | ||
87 | // TestReadForm_NonFileMaxMemory asserts that the ReadForm maxMemory limit is applied | ||
88 | // while processing non-file form data as well as file form data. | ||
89 | func TestReadForm_NonFileMaxMemory(t *testing.T) { | ||
90 | - n := 10<<20 + 25 | ||
91 | if testing.Short() { | ||
92 | - n = 10<<10 + 25 | ||
93 | + t.Skip("skipping in -short mode") | ||
94 | } | ||
95 | + n := 10 << 20 | ||
96 | largeTextValue := strings.Repeat("1", n) | ||
97 | message := `--MyBoundary | ||
98 | Content-Disposition: form-data; name="largetext" | ||
99 | @@ -203,38 +203,29 @@ Content-Disposition: form-data; name="largetext" | ||
100 | ` + largeTextValue + ` | ||
101 | --MyBoundary-- | ||
102 | ` | ||
103 | - | ||
104 | testBody := strings.ReplaceAll(message, "\n", "\r\n") | ||
105 | - testCases := []struct { | ||
106 | - name string | ||
107 | - maxMemory int64 | ||
108 | - err error | ||
109 | - }{ | ||
110 | - {"smaller", 50 + int64(len("largetext")) + 100, nil}, | ||
111 | - {"exact-fit", 25 + int64(len("largetext")) + 100, nil}, | ||
112 | - {"too-large", 0, ErrMessageTooLarge}, | ||
113 | - } | ||
114 | - for _, tc := range testCases { | ||
115 | - t.Run(tc.name, func(t *testing.T) { | ||
116 | - if tc.maxMemory == 0 && testing.Short() { | ||
117 | - t.Skip("skipping in -short mode") | ||
118 | - } | ||
119 | - b := strings.NewReader(testBody) | ||
120 | - r := NewReader(b, boundary) | ||
121 | - f, err := r.ReadForm(tc.maxMemory) | ||
122 | - if err == nil { | ||
123 | - defer f.RemoveAll() | ||
124 | - } | ||
125 | - if tc.err != err { | ||
126 | - t.Fatalf("ReadForm error - got: %v; expected: %v", err, tc.err) | ||
127 | - } | ||
128 | - if err == nil { | ||
129 | - if g := f.Value["largetext"][0]; g != largeTextValue { | ||
130 | - t.Errorf("largetext mismatch: got size: %v, expected size: %v", len(g), len(largeTextValue)) | ||
131 | - } | ||
132 | - } | ||
133 | - }) | ||
134 | + // Try parsing the form with increasing maxMemory values. | ||
135 | + // Changes in how we account for non-file form data may cause the exact point | ||
136 | + // where we change from rejecting the form as too large to accepting it to vary, | ||
137 | + // but we should see both successes and failures. | ||
138 | + const failWhenMaxMemoryLessThan = 128 | ||
139 | + for maxMemory := int64(0); maxMemory < failWhenMaxMemoryLessThan*2; maxMemory += 16 { | ||
140 | + b := strings.NewReader(testBody) | ||
141 | + r := NewReader(b, boundary) | ||
142 | + f, err := r.ReadForm(maxMemory) | ||
143 | + if err != nil { | ||
144 | + continue | ||
145 | + } | ||
146 | + if g := f.Value["largetext"][0]; g != largeTextValue { | ||
147 | + t.Errorf("largetext mismatch: got size: %v, expected size: %v", len(g), len(largeTextValue)) | ||
148 | + } | ||
149 | + f.RemoveAll() | ||
150 | + if maxMemory < failWhenMaxMemoryLessThan { | ||
151 | + t.Errorf("ReadForm(%v): no error, expect to hit memory limit when maxMemory < %v", maxMemory, failWhenMaxMemoryLessThan) | ||
152 | + } | ||
153 | + return | ||
154 | } | ||
155 | + t.Errorf("ReadForm(x) failed for x < 1024, expect success") | ||
156 | } | ||
157 | |||
158 | // TestReadForm_MetadataTooLarge verifies that we account for the size of field names, | ||
159 | diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go | ||
160 | index fcbede8..9af4c49 100644 | ||
161 | --- a/src/net/textproto/reader.go | ||
162 | +++ b/src/net/textproto/reader.go | ||
163 | @@ -503,6 +503,12 @@ func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) { | ||
164 | |||
165 | m := make(MIMEHeader, hint) | ||
166 | |||
167 | + // Account for 400 bytes of overhead for the MIMEHeader, plus 200 bytes per entry. | ||
168 | + // Benchmarking map creation as of go1.20, a one-entry MIMEHeader is 416 bytes and large | ||
169 | + // MIMEHeaders average about 200 bytes per entry. | ||
170 | + lim -= 400 | ||
171 | + const mapEntryOverhead = 200 | ||
172 | + | ||
173 | // The first line cannot start with a leading space. | ||
174 | if buf, err := r.R.Peek(1); err == nil && (buf[0] == ' ' || buf[0] == '\t') { | ||
175 | line, err := r.readLineSlice() | ||
176 | @@ -552,7 +558,7 @@ func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) { | ||
177 | vv := m[key] | ||
178 | if vv == nil { | ||
179 | lim -= int64(len(key)) | ||
180 | - lim -= 100 // map entry overhead | ||
181 | + lim -= mapEntryOverhead | ||
182 | } | ||
183 | lim -= int64(len(value)) | ||
184 | if lim < 0 { | ||
185 | -- | ||
186 | 2.35.5 | ||
187 | |||
diff --git a/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_3.patch b/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_3.patch new file mode 100644 index 0000000000..6de04e9a61 --- /dev/null +++ b/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_3.patch | |||
@@ -0,0 +1,349 @@ | |||
1 | From ec763bc936f76cec0fe71a791c6bb7d4ac5f3e46 Mon Sep 17 00:00:00 2001 | ||
2 | From: Damien Neil <dneil@google.com> | ||
3 | Date: Mon, 20 Mar 2023 10:43:19 -0700 | ||
4 | Subject: [PATCH 3/3] mime/multipart: limit parsed mime message sizes | ||
5 | |||
6 | The parsed forms of MIME headers and multipart forms can consume | ||
7 | substantially more memory than the size of the input data. | ||
8 | A malicious input containing a very large number of headers or | ||
9 | form parts can cause excessively large memory allocations. | ||
10 | |||
11 | Set limits on the size of MIME data: | ||
12 | |||
13 | Reader.NextPart and Reader.NextRawPart limit the the number | ||
14 | of headers in a part to 10000. | ||
15 | |||
16 | Reader.ReadForm limits the total number of headers in all | ||
17 | FileHeaders to 10000. | ||
18 | |||
19 | Both of these limits may be set with with | ||
20 | GODEBUG=multipartmaxheaders=<values>. | ||
21 | |||
22 | Reader.ReadForm limits the number of parts in a form to 1000. | ||
23 | This limit may be set with GODEBUG=multipartmaxparts=<value>. | ||
24 | |||
25 | Thanks for Jakob Ackermann (@das7pad) for reporting this issue. | ||
26 | |||
27 | For CVE-2023-24536 | ||
28 | For #59153 | ||
29 | For #59269 | ||
30 | |||
31 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802455 | ||
32 | Run-TryBot: Damien Neil <dneil@google.com> | ||
33 | Reviewed-by: Roland Shoemaker <bracewell@google.com> | ||
34 | Reviewed-by: Julie Qiu <julieqiu@google.com> | ||
35 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1801087 | ||
36 | Reviewed-by: Damien Neil <dneil@google.com> | ||
37 | Run-TryBot: Roland Shoemaker <bracewell@google.com> | ||
38 | Change-Id: If134890d75f0d95c681d67234daf191ba08e6424 | ||
39 | Reviewed-on: https://go-review.googlesource.com/c/go/+/481985 | ||
40 | Run-TryBot: Michael Knyszek <mknyszek@google.com> | ||
41 | Auto-Submit: Michael Knyszek <mknyszek@google.com> | ||
42 | TryBot-Result: Gopher Robot <gobot@golang.org> | ||
43 | Reviewed-by: Matthew Dempsky <mdempsky@google.com> | ||
44 | |||
45 | CVE: CVE-2023-24536 | ||
46 | Upstream-Status: Backport [7917b5f31204528ea72e0629f0b7d52b35b27538] | ||
47 | Signed-off-by: Sakib Sajal <sakib.sajal@windriver.com> | ||
48 | --- | ||
49 | src/mime/multipart/formdata.go | 19 ++++++++- | ||
50 | src/mime/multipart/formdata_test.go | 61 ++++++++++++++++++++++++++++ | ||
51 | src/mime/multipart/multipart.go | 31 ++++++++++---- | ||
52 | src/mime/multipart/readmimeheader.go | 2 +- | ||
53 | src/net/textproto/reader.go | 19 +++++---- | ||
54 | 5 files changed, 115 insertions(+), 17 deletions(-) | ||
55 | |||
56 | diff --git a/src/mime/multipart/formdata.go b/src/mime/multipart/formdata.go | ||
57 | index 3f6ff69..4f26aab 100644 | ||
58 | --- a/src/mime/multipart/formdata.go | ||
59 | +++ b/src/mime/multipart/formdata.go | ||
60 | @@ -12,6 +12,7 @@ import ( | ||
61 | "math" | ||
62 | "net/textproto" | ||
63 | "os" | ||
64 | + "strconv" | ||
65 | ) | ||
66 | |||
67 | // ErrMessageTooLarge is returned by ReadForm if the message form | ||
68 | @@ -41,6 +42,15 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { | ||
69 | numDiskFiles := 0 | ||
70 | multipartFiles := godebug.Get("multipartfiles") | ||
71 | combineFiles := multipartFiles != "distinct" | ||
72 | + maxParts := 1000 | ||
73 | + multipartMaxParts := godebug.Get("multipartmaxparts") | ||
74 | + if multipartMaxParts != "" { | ||
75 | + if v, err := strconv.Atoi(multipartMaxParts); err == nil && v >= 0 { | ||
76 | + maxParts = v | ||
77 | + } | ||
78 | + } | ||
79 | + maxHeaders := maxMIMEHeaders() | ||
80 | + | ||
81 | defer func() { | ||
82 | if file != nil { | ||
83 | if cerr := file.Close(); err == nil { | ||
84 | @@ -86,13 +96,17 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { | ||
85 | } | ||
86 | var copyBuf []byte | ||
87 | for { | ||
88 | - p, err := r.nextPart(false, maxMemoryBytes) | ||
89 | + p, err := r.nextPart(false, maxMemoryBytes, maxHeaders) | ||
90 | if err == io.EOF { | ||
91 | break | ||
92 | } | ||
93 | if err != nil { | ||
94 | return nil, err | ||
95 | } | ||
96 | + if maxParts <= 0 { | ||
97 | + return nil, ErrMessageTooLarge | ||
98 | + } | ||
99 | + maxParts-- | ||
100 | |||
101 | name := p.FormName() | ||
102 | if name == "" { | ||
103 | @@ -136,6 +150,9 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { | ||
104 | if maxMemoryBytes < 0 { | ||
105 | return nil, ErrMessageTooLarge | ||
106 | } | ||
107 | + for _, v := range p.Header { | ||
108 | + maxHeaders -= int64(len(v)) | ||
109 | + } | ||
110 | fh := &FileHeader{ | ||
111 | Filename: filename, | ||
112 | Header: p.Header, | ||
113 | diff --git a/src/mime/multipart/formdata_test.go b/src/mime/multipart/formdata_test.go | ||
114 | index 8ed26e0..c78eeb7 100644 | ||
115 | --- a/src/mime/multipart/formdata_test.go | ||
116 | +++ b/src/mime/multipart/formdata_test.go | ||
117 | @@ -360,6 +360,67 @@ func testReadFormManyFiles(t *testing.T, distinct bool) { | ||
118 | } | ||
119 | } | ||
120 | |||
121 | +func TestReadFormLimits(t *testing.T) { | ||
122 | + for _, test := range []struct { | ||
123 | + values int | ||
124 | + files int | ||
125 | + extraKeysPerFile int | ||
126 | + wantErr error | ||
127 | + godebug string | ||
128 | + }{ | ||
129 | + {values: 1000}, | ||
130 | + {values: 1001, wantErr: ErrMessageTooLarge}, | ||
131 | + {values: 500, files: 500}, | ||
132 | + {values: 501, files: 500, wantErr: ErrMessageTooLarge}, | ||
133 | + {files: 1000}, | ||
134 | + {files: 1001, wantErr: ErrMessageTooLarge}, | ||
135 | + {files: 1, extraKeysPerFile: 9998}, // plus Content-Disposition and Content-Type | ||
136 | + {files: 1, extraKeysPerFile: 10000, wantErr: ErrMessageTooLarge}, | ||
137 | + {godebug: "multipartmaxparts=100", values: 100}, | ||
138 | + {godebug: "multipartmaxparts=100", values: 101, wantErr: ErrMessageTooLarge}, | ||
139 | + {godebug: "multipartmaxheaders=100", files: 2, extraKeysPerFile: 48}, | ||
140 | + {godebug: "multipartmaxheaders=100", files: 2, extraKeysPerFile: 50, wantErr: ErrMessageTooLarge}, | ||
141 | + } { | ||
142 | + name := fmt.Sprintf("values=%v/files=%v/extraKeysPerFile=%v", test.values, test.files, test.extraKeysPerFile) | ||
143 | + if test.godebug != "" { | ||
144 | + name += fmt.Sprintf("/godebug=%v", test.godebug) | ||
145 | + } | ||
146 | + t.Run(name, func(t *testing.T) { | ||
147 | + if test.godebug != "" { | ||
148 | + t.Setenv("GODEBUG", test.godebug) | ||
149 | + } | ||
150 | + var buf bytes.Buffer | ||
151 | + fw := NewWriter(&buf) | ||
152 | + for i := 0; i < test.values; i++ { | ||
153 | + w, _ := fw.CreateFormField(fmt.Sprintf("field%v", i)) | ||
154 | + fmt.Fprintf(w, "value %v", i) | ||
155 | + } | ||
156 | + for i := 0; i < test.files; i++ { | ||
157 | + h := make(textproto.MIMEHeader) | ||
158 | + h.Set("Content-Disposition", | ||
159 | + fmt.Sprintf(`form-data; name="file%v"; filename="file%v"`, i, i)) | ||
160 | + h.Set("Content-Type", "application/octet-stream") | ||
161 | + for j := 0; j < test.extraKeysPerFile; j++ { | ||
162 | + h.Set(fmt.Sprintf("k%v", j), "v") | ||
163 | + } | ||
164 | + w, _ := fw.CreatePart(h) | ||
165 | + fmt.Fprintf(w, "value %v", i) | ||
166 | + } | ||
167 | + if err := fw.Close(); err != nil { | ||
168 | + t.Fatal(err) | ||
169 | + } | ||
170 | + fr := NewReader(bytes.NewReader(buf.Bytes()), fw.Boundary()) | ||
171 | + form, err := fr.ReadForm(1 << 10) | ||
172 | + if err == nil { | ||
173 | + defer form.RemoveAll() | ||
174 | + } | ||
175 | + if err != test.wantErr { | ||
176 | + t.Errorf("ReadForm = %v, want %v", err, test.wantErr) | ||
177 | + } | ||
178 | + }) | ||
179 | + } | ||
180 | +} | ||
181 | + | ||
182 | func BenchmarkReadForm(b *testing.B) { | ||
183 | for _, test := range []struct { | ||
184 | name string | ||
185 | diff --git a/src/mime/multipart/multipart.go b/src/mime/multipart/multipart.go | ||
186 | index 19fe0ea..80acabc 100644 | ||
187 | --- a/src/mime/multipart/multipart.go | ||
188 | +++ b/src/mime/multipart/multipart.go | ||
189 | @@ -16,11 +16,13 @@ import ( | ||
190 | "bufio" | ||
191 | "bytes" | ||
192 | "fmt" | ||
193 | + "internal/godebug" | ||
194 | "io" | ||
195 | "mime" | ||
196 | "mime/quotedprintable" | ||
197 | "net/textproto" | ||
198 | "path/filepath" | ||
199 | + "strconv" | ||
200 | "strings" | ||
201 | ) | ||
202 | |||
203 | @@ -128,12 +130,12 @@ func (r *stickyErrorReader) Read(p []byte) (n int, _ error) { | ||
204 | return n, r.err | ||
205 | } | ||
206 | |||
207 | -func newPart(mr *Reader, rawPart bool, maxMIMEHeaderSize int64) (*Part, error) { | ||
208 | +func newPart(mr *Reader, rawPart bool, maxMIMEHeaderSize, maxMIMEHeaders int64) (*Part, error) { | ||
209 | bp := &Part{ | ||
210 | Header: make(map[string][]string), | ||
211 | mr: mr, | ||
212 | } | ||
213 | - if err := bp.populateHeaders(maxMIMEHeaderSize); err != nil { | ||
214 | + if err := bp.populateHeaders(maxMIMEHeaderSize, maxMIMEHeaders); err != nil { | ||
215 | return nil, err | ||
216 | } | ||
217 | bp.r = partReader{bp} | ||
218 | @@ -149,9 +151,9 @@ func newPart(mr *Reader, rawPart bool, maxMIMEHeaderSize int64) (*Part, error) { | ||
219 | return bp, nil | ||
220 | } | ||
221 | |||
222 | -func (bp *Part) populateHeaders(maxMIMEHeaderSize int64) error { | ||
223 | +func (bp *Part) populateHeaders(maxMIMEHeaderSize, maxMIMEHeaders int64) error { | ||
224 | r := textproto.NewReader(bp.mr.bufReader) | ||
225 | - header, err := readMIMEHeader(r, maxMIMEHeaderSize) | ||
226 | + header, err := readMIMEHeader(r, maxMIMEHeaderSize, maxMIMEHeaders) | ||
227 | if err == nil { | ||
228 | bp.Header = header | ||
229 | } | ||
230 | @@ -313,6 +315,19 @@ type Reader struct { | ||
231 | // including header keys, values, and map overhead. | ||
232 | const maxMIMEHeaderSize = 10 << 20 | ||
233 | |||
234 | +func maxMIMEHeaders() int64 { | ||
235 | + // multipartMaxHeaders is the maximum number of header entries NextPart will return, | ||
236 | + // as well as the maximum combined total of header entries Reader.ReadForm will return | ||
237 | + // in FileHeaders. | ||
238 | + multipartMaxHeaders := godebug.Get("multipartmaxheaders") | ||
239 | + if multipartMaxHeaders != "" { | ||
240 | + if v, err := strconv.ParseInt(multipartMaxHeaders, 10, 64); err == nil && v >= 0 { | ||
241 | + return v | ||
242 | + } | ||
243 | + } | ||
244 | + return 10000 | ||
245 | +} | ||
246 | + | ||
247 | // NextPart returns the next part in the multipart or an error. | ||
248 | // When there are no more parts, the error io.EOF is returned. | ||
249 | // | ||
250 | @@ -320,7 +335,7 @@ const maxMIMEHeaderSize = 10 << 20 | ||
251 | // has a value of "quoted-printable", that header is instead | ||
252 | // hidden and the body is transparently decoded during Read calls. | ||
253 | func (r *Reader) NextPart() (*Part, error) { | ||
254 | - return r.nextPart(false, maxMIMEHeaderSize) | ||
255 | + return r.nextPart(false, maxMIMEHeaderSize, maxMIMEHeaders()) | ||
256 | } | ||
257 | |||
258 | // NextRawPart returns the next part in the multipart or an error. | ||
259 | @@ -329,10 +344,10 @@ func (r *Reader) NextPart() (*Part, error) { | ||
260 | // Unlike NextPart, it does not have special handling for | ||
261 | // "Content-Transfer-Encoding: quoted-printable". | ||
262 | func (r *Reader) NextRawPart() (*Part, error) { | ||
263 | - return r.nextPart(true, maxMIMEHeaderSize) | ||
264 | + return r.nextPart(true, maxMIMEHeaderSize, maxMIMEHeaders()) | ||
265 | } | ||
266 | |||
267 | -func (r *Reader) nextPart(rawPart bool, maxMIMEHeaderSize int64) (*Part, error) { | ||
268 | +func (r *Reader) nextPart(rawPart bool, maxMIMEHeaderSize, maxMIMEHeaders int64) (*Part, error) { | ||
269 | if r.currentPart != nil { | ||
270 | r.currentPart.Close() | ||
271 | } | ||
272 | @@ -357,7 +372,7 @@ func (r *Reader) nextPart(rawPart bool, maxMIMEHeaderSize int64) (*Part, error) | ||
273 | |||
274 | if r.isBoundaryDelimiterLine(line) { | ||
275 | r.partsRead++ | ||
276 | - bp, err := newPart(r, rawPart, maxMIMEHeaderSize) | ||
277 | + bp, err := newPart(r, rawPart, maxMIMEHeaderSize, maxMIMEHeaders) | ||
278 | if err != nil { | ||
279 | return nil, err | ||
280 | } | ||
281 | diff --git a/src/mime/multipart/readmimeheader.go b/src/mime/multipart/readmimeheader.go | ||
282 | index 6836928..25aa6e2 100644 | ||
283 | --- a/src/mime/multipart/readmimeheader.go | ||
284 | +++ b/src/mime/multipart/readmimeheader.go | ||
285 | @@ -11,4 +11,4 @@ import ( | ||
286 | // readMIMEHeader is defined in package net/textproto. | ||
287 | // | ||
288 | //go:linkname readMIMEHeader net/textproto.readMIMEHeader | ||
289 | -func readMIMEHeader(r *textproto.Reader, lim int64) (textproto.MIMEHeader, error) | ||
290 | +func readMIMEHeader(r *textproto.Reader, maxMemory, maxHeaders int64) (textproto.MIMEHeader, error) | ||
291 | diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go | ||
292 | index 9af4c49..c6569c8 100644 | ||
293 | --- a/src/net/textproto/reader.go | ||
294 | +++ b/src/net/textproto/reader.go | ||
295 | @@ -483,12 +483,12 @@ func (r *Reader) ReadDotLines() ([]string, error) { | ||
296 | // } | ||
297 | // | ||
298 | func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) { | ||
299 | - return readMIMEHeader(r, math.MaxInt64) | ||
300 | + return readMIMEHeader(r, math.MaxInt64, math.MaxInt64) | ||
301 | } | ||
302 | |||
303 | // readMIMEHeader is a version of ReadMIMEHeader which takes a limit on the header size. | ||
304 | // It is called by the mime/multipart package. | ||
305 | -func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) { | ||
306 | +func readMIMEHeader(r *Reader, maxMemory, maxHeaders int64) (MIMEHeader, error) { | ||
307 | // Avoid lots of small slice allocations later by allocating one | ||
308 | // large one ahead of time which we'll cut up into smaller | ||
309 | // slices. If this isn't big enough later, we allocate small ones. | ||
310 | @@ -506,7 +506,7 @@ func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) { | ||
311 | // Account for 400 bytes of overhead for the MIMEHeader, plus 200 bytes per entry. | ||
312 | // Benchmarking map creation as of go1.20, a one-entry MIMEHeader is 416 bytes and large | ||
313 | // MIMEHeaders average about 200 bytes per entry. | ||
314 | - lim -= 400 | ||
315 | + maxMemory -= 400 | ||
316 | const mapEntryOverhead = 200 | ||
317 | |||
318 | // The first line cannot start with a leading space. | ||
319 | @@ -538,6 +538,11 @@ func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) { | ||
320 | continue | ||
321 | } | ||
322 | |||
323 | + maxHeaders-- | ||
324 | + if maxHeaders < 0 { | ||
325 | + return nil, errors.New("message too large") | ||
326 | + } | ||
327 | + | ||
328 | // backport 5c55ac9bf1e5f779220294c843526536605f42ab | ||
329 | // | ||
330 | // value is computed as | ||
331 | @@ -557,11 +562,11 @@ func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) { | ||
332 | |||
333 | vv := m[key] | ||
334 | if vv == nil { | ||
335 | - lim -= int64(len(key)) | ||
336 | - lim -= mapEntryOverhead | ||
337 | + maxMemory -= int64(len(key)) | ||
338 | + maxMemory -= mapEntryOverhead | ||
339 | } | ||
340 | - lim -= int64(len(value)) | ||
341 | - if lim < 0 { | ||
342 | + maxMemory -= int64(len(value)) | ||
343 | + if maxMemory < 0 { | ||
344 | // TODO: This should be a distinguishable error (ErrMessageTooLarge) | ||
345 | // to allow mime/multipart to detect it. | ||
346 | return m, errors.New("message too large") | ||
347 | -- | ||
348 | 2.35.5 | ||
349 | |||