summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSakib Sajal <sakib.sajal@windriver.com>2023-08-01 17:18:11 -0700
committerSteve Sakoman <steve@sakoman.com>2023-08-07 04:40:44 -1000
commit1ba43f2c8858ffb0a05644b455daf5ea75aa2fe5 (patch)
tree65354063944cf70c59f3665a05bbbdb1defed31f
parentae7992e3b7b688e1e06b20c92aaa60af01f6bbbb (diff)
downloadpoky-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.inc3
-rw-r--r--meta/recipes-devtools/go/go-1.19/CVE-2023-24536_1.patch137
-rw-r--r--meta/recipes-devtools/go/go-1.19/CVE-2023-24536_2.patch187
-rw-r--r--meta/recipes-devtools/go/go-1.19/CVE-2023-24536_3.patch349
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"
41SRC_URI[main.sha256sum] = "a1a48b23afb206f95e7bbaa9b898d965f90826f6f1d1fc0c1d784ada0cd300fd" 44SRC_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 @@
1From f8d691d335c6ac14bcbae6886b5bf8ca8bf1e6a5 Mon Sep 17 00:00:00 2001
2From: Damien Neil <dneil@google.com>
3Date: Thu, 16 Mar 2023 14:18:04 -0700
4Subject: [PATCH 1/3] mime/multipart: avoid excessive copy buffer allocations
5 in ReadForm
6
7When copying form data to disk with io.Copy,
8allocate only one copy buffer and reuse it rather than
9creating two buffers per file (one from io.multiReader.WriteTo,
10and a second one from os.File.ReadFrom).
11
12Thanks to Jakob Ackermann (@das7pad) for reporting this issue.
13
14For CVE-2023-24536
15For #59153
16For #59269
17
18Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802453
19Run-TryBot: Damien Neil <dneil@google.com>
20Reviewed-by: Julie Qiu <julieqiu@google.com>
21Reviewed-by: Roland Shoemaker <bracewell@google.com>
22Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802395
23Run-TryBot: Roland Shoemaker <bracewell@google.com>
24Reviewed-by: Damien Neil <dneil@google.com>
25Change-Id: Ie405470c92abffed3356913b37d813e982c96c8b
26Reviewed-on: https://go-review.googlesource.com/c/go/+/481983
27Run-TryBot: Michael Knyszek <mknyszek@google.com>
28TryBot-Result: Gopher Robot <gobot@golang.org>
29Auto-Submit: Michael Knyszek <mknyszek@google.com>
30Reviewed-by: Matthew Dempsky <mdempsky@google.com>
31
32CVE: CVE-2023-24536
33Upstream-Status: Backport [ef41a4e2face45e580c5836eaebd51629fc23f15]
34Signed-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
40diff --git a/src/mime/multipart/formdata.go b/src/mime/multipart/formdata.go
41index 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
78diff --git a/src/mime/multipart/formdata_test.go b/src/mime/multipart/formdata_test.go
79index 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--
1362.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 @@
1From 4174a87b600c58e8cc00d9d18d0c507c67ca5d41 Mon Sep 17 00:00:00 2001
2From: Damien Neil <dneil@google.com>
3Date: Thu, 16 Mar 2023 16:56:12 -0700
4Subject: [PATCH 2/3] net/textproto, mime/multipart: improve accounting of
5 non-file data
6
7For requests containing large numbers of small parts,
8memory consumption of a parsed form could be about 250%
9over the estimated size.
10
11When considering the size of parsed forms, account for the size of
12FileHeader structs and increase the estimate of memory consumed by
13map entries.
14
15Thanks to Jakob Ackermann (@das7pad) for reporting this issue.
16
17For CVE-2023-24536
18For #59153
19For #59269
20
21Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802454
22Run-TryBot: Damien Neil <dneil@google.com>
23Reviewed-by: Roland Shoemaker <bracewell@google.com>
24Reviewed-by: Julie Qiu <julieqiu@google.com>
25Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802396
26Run-TryBot: Roland Shoemaker <bracewell@google.com>
27Reviewed-by: Damien Neil <dneil@google.com>
28Change-Id: I31bc50e9346b4eee6fbe51a18c3c57230cc066db
29Reviewed-on: https://go-review.googlesource.com/c/go/+/481984
30Reviewed-by: Matthew Dempsky <mdempsky@google.com>
31Auto-Submit: Michael Knyszek <mknyszek@google.com>
32TryBot-Result: Gopher Robot <gobot@golang.org>
33Run-TryBot: Michael Knyszek <mknyszek@google.com>
34
35CVE: CVE-2023-24536
36Upstream-Status: Backport [7a359a651c7ebdb29e0a1c03102fce793e9f58f0]
37Signed-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
44diff --git a/src/mime/multipart/formdata.go b/src/mime/multipart/formdata.go
45index 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 }
82diff --git a/src/mime/multipart/formdata_test.go b/src/mime/multipart/formdata_test.go
83index 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,
159diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go
160index 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--
1862.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 @@
1From ec763bc936f76cec0fe71a791c6bb7d4ac5f3e46 Mon Sep 17 00:00:00 2001
2From: Damien Neil <dneil@google.com>
3Date: Mon, 20 Mar 2023 10:43:19 -0700
4Subject: [PATCH 3/3] mime/multipart: limit parsed mime message sizes
5
6The parsed forms of MIME headers and multipart forms can consume
7substantially more memory than the size of the input data.
8A malicious input containing a very large number of headers or
9form parts can cause excessively large memory allocations.
10
11Set limits on the size of MIME data:
12
13Reader.NextPart and Reader.NextRawPart limit the the number
14of headers in a part to 10000.
15
16Reader.ReadForm limits the total number of headers in all
17FileHeaders to 10000.
18
19Both of these limits may be set with with
20GODEBUG=multipartmaxheaders=<values>.
21
22Reader.ReadForm limits the number of parts in a form to 1000.
23This limit may be set with GODEBUG=multipartmaxparts=<value>.
24
25Thanks for Jakob Ackermann (@das7pad) for reporting this issue.
26
27For CVE-2023-24536
28For #59153
29For #59269
30
31Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802455
32Run-TryBot: Damien Neil <dneil@google.com>
33Reviewed-by: Roland Shoemaker <bracewell@google.com>
34Reviewed-by: Julie Qiu <julieqiu@google.com>
35Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1801087
36Reviewed-by: Damien Neil <dneil@google.com>
37Run-TryBot: Roland Shoemaker <bracewell@google.com>
38Change-Id: If134890d75f0d95c681d67234daf191ba08e6424
39Reviewed-on: https://go-review.googlesource.com/c/go/+/481985
40Run-TryBot: Michael Knyszek <mknyszek@google.com>
41Auto-Submit: Michael Knyszek <mknyszek@google.com>
42TryBot-Result: Gopher Robot <gobot@golang.org>
43Reviewed-by: Matthew Dempsky <mdempsky@google.com>
44
45CVE: CVE-2023-24536
46Upstream-Status: Backport [7917b5f31204528ea72e0629f0b7d52b35b27538]
47Signed-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
56diff --git a/src/mime/multipart/formdata.go b/src/mime/multipart/formdata.go
57index 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,
113diff --git a/src/mime/multipart/formdata_test.go b/src/mime/multipart/formdata_test.go
114index 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
185diff --git a/src/mime/multipart/multipart.go b/src/mime/multipart/multipart.go
186index 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 }
281diff --git a/src/mime/multipart/readmimeheader.go b/src/mime/multipart/readmimeheader.go
282index 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)
291diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go
292index 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--
3482.35.5
349