summaryrefslogtreecommitdiffstats
path: root/meta/recipes-devtools/go/go-1.14/CVE-2023-24536_3.patch
blob: 58c0a484eebf4ec90a4379a883ec641d6f08802e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
From 7917b5f31204528ea72e0629f0b7d52b35b27538 Mon Sep 17 00:00:00 2001
From: Damien Neil <dneil@google.com>
Date: Mon, 20 Mar 2023 10:43:19 -0700
Subject: [PATCH] [release-branch.go1.19] mime/multipart: limit parsed mime message sizes

The parsed forms of MIME headers and multipart forms can consume
substantially more memory than the size of the input data.
A malicious input containing a very large number of headers or
form parts can cause excessively large memory allocations.

Set limits on the size of MIME data:

Reader.NextPart and Reader.NextRawPart limit the the number
of headers in a part to 10000.

Reader.ReadForm limits the total number of headers in all
FileHeaders to 10000.

Both of these limits may be set with with
GODEBUG=multipartmaxheaders=<values>.

Reader.ReadForm limits the number of parts in a form to 1000.
This limit may be set with GODEBUG=multipartmaxparts=<value>.

Thanks for Jakob Ackermann (@das7pad) for reporting this issue.

For CVE-2023-24536
For #59153
For #59269

Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802455
Run-TryBot: Damien Neil <dneil@google.com>
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Julie Qiu <julieqiu@google.com>
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1801087
Reviewed-by: Damien Neil <dneil@google.com>
Run-TryBot: Roland Shoemaker <bracewell@google.com>
Change-Id: If134890d75f0d95c681d67234daf191ba08e6424
Reviewed-on: https://go-review.googlesource.com/c/go/+/481985
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>

Upstream-Status: Backport [https://github.com/golang/go/commit/7917b5f31204528ea72e0629f0b7d52b35b27538]
CVE: CVE-2023-24536
Signed-off-by: Vijay Anusuri <vanusuri@mvista.com>
---
 src/mime/multipart/formdata.go       | 19 ++++++++-
 src/mime/multipart/formdata_test.go  | 61 ++++++++++++++++++++++++++++
 src/mime/multipart/multipart.go      | 31 ++++++++++----
 src/mime/multipart/readmimeheader.go |  2 +-
 src/net/textproto/reader.go          | 19 +++++----
 5 files changed, 115 insertions(+), 17 deletions(-)

diff --git a/src/mime/multipart/formdata.go b/src/mime/multipart/formdata.go
index 216cccb..0b508ae 100644
--- a/src/mime/multipart/formdata.go
+++ b/src/mime/multipart/formdata.go
@@ -13,6 +13,7 @@ import (
 	"math"
 	"net/textproto"
 	"os"
+	"strconv"
 )
 
 // ErrMessageTooLarge is returned by ReadForm if the message form
@@ -42,6 +43,15 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) {
 	numDiskFiles := 0
 	multipartFiles := godebug.Get("multipartfiles")
 	combineFiles := multipartFiles != "distinct"
+	maxParts := 1000
+	multipartMaxParts := godebug.Get("multipartmaxparts")
+	if multipartMaxParts != "" {
+		if v, err := strconv.Atoi(multipartMaxParts); err == nil && v >= 0 {
+			maxParts = v
+		}
+	}
+	maxHeaders := maxMIMEHeaders()
+
 	defer func() {
 		if file != nil {
 			if cerr := file.Close(); err == nil {
@@ -87,13 +97,17 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) {
 	}
 	var copyBuf []byte
 	for {
-		p, err := r.nextPart(false, maxMemoryBytes)
+		p, err := r.nextPart(false, maxMemoryBytes, maxHeaders)
 		if err == io.EOF {
 			break
 		}
 		if err != nil {
 			return nil, err
 		}
+		if maxParts <= 0 {
+			return nil, ErrMessageTooLarge
+		}
+		maxParts--
 
 		name := p.FormName()
 		if name == "" {
@@ -137,6 +151,9 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) {
 		if maxMemoryBytes < 0 {
 			return nil, ErrMessageTooLarge
 		}
+		for _, v := range p.Header {
+			maxHeaders -= int64(len(v))
+		}
 		fh := &FileHeader{
 			Filename: filename,
 			Header:   p.Header,
diff --git a/src/mime/multipart/formdata_test.go b/src/mime/multipart/formdata_test.go
index 8ed26e0..c78eeb7 100644
--- a/src/mime/multipart/formdata_test.go
+++ b/src/mime/multipart/formdata_test.go
@@ -360,6 +360,67 @@ func testReadFormManyFiles(t *testing.T, distinct bool) {
 	}
 }
 
+func TestReadFormLimits(t *testing.T) {
+	for _, test := range []struct {
+		values           int
+		files            int
+		extraKeysPerFile int
+		wantErr          error
+		godebug          string
+	}{
+		{values: 1000},
+		{values: 1001, wantErr: ErrMessageTooLarge},
+		{values: 500, files: 500},
+		{values: 501, files: 500, wantErr: ErrMessageTooLarge},
+		{files: 1000},
+		{files: 1001, wantErr: ErrMessageTooLarge},
+		{files: 1, extraKeysPerFile: 9998}, // plus Content-Disposition and Content-Type
+		{files: 1, extraKeysPerFile: 10000, wantErr: ErrMessageTooLarge},
+		{godebug: "multipartmaxparts=100", values: 100},
+		{godebug: "multipartmaxparts=100", values: 101, wantErr: ErrMessageTooLarge},
+		{godebug: "multipartmaxheaders=100", files: 2, extraKeysPerFile: 48},
+		{godebug: "multipartmaxheaders=100", files: 2, extraKeysPerFile: 50, wantErr: ErrMessageTooLarge},
+	} {
+		name := fmt.Sprintf("values=%v/files=%v/extraKeysPerFile=%v", test.values, test.files, test.extraKeysPerFile)
+		if test.godebug != "" {
+			name += fmt.Sprintf("/godebug=%v", test.godebug)
+		}
+		t.Run(name, func(t *testing.T) {
+			if test.godebug != "" {
+				t.Setenv("GODEBUG", test.godebug)
+			}
+			var buf bytes.Buffer
+			fw := NewWriter(&buf)
+			for i := 0; i < test.values; i++ {
+				w, _ := fw.CreateFormField(fmt.Sprintf("field%v", i))
+				fmt.Fprintf(w, "value %v", i)
+			}
+			for i := 0; i < test.files; i++ {
+				h := make(textproto.MIMEHeader)
+				h.Set("Content-Disposition",
+					fmt.Sprintf(`form-data; name="file%v"; filename="file%v"`, i, i))
+				h.Set("Content-Type", "application/octet-stream")
+				for j := 0; j < test.extraKeysPerFile; j++ {
+					h.Set(fmt.Sprintf("k%v", j), "v")
+				}
+				w, _ := fw.CreatePart(h)
+				fmt.Fprintf(w, "value %v", i)
+			}
+			if err := fw.Close(); err != nil {
+				t.Fatal(err)
+			}
+			fr := NewReader(bytes.NewReader(buf.Bytes()), fw.Boundary())
+			form, err := fr.ReadForm(1 << 10)
+			if err == nil {
+				defer form.RemoveAll()
+			}
+			if err != test.wantErr {
+				t.Errorf("ReadForm = %v, want %v", err, test.wantErr)
+			}
+		})
+	}
+}
+
 func BenchmarkReadForm(b *testing.B) {
 	for _, test := range []struct {
 		name string
diff --git a/src/mime/multipart/multipart.go b/src/mime/multipart/multipart.go
index 958cef8..94464a8 100644
--- a/src/mime/multipart/multipart.go
+++ b/src/mime/multipart/multipart.go
@@ -16,11 +16,13 @@ import (
 	"bufio"
 	"bytes"
 	"fmt"
+	"internal/godebug"
 	"io"
 	"io/ioutil"
 	"mime"
 	"mime/quotedprintable"
 	"net/textproto"
+	"strconv"
 	"strings"
 )
 
@@ -121,12 +123,12 @@ func (r *stickyErrorReader) Read(p []byte) (n int, _ error) {
 	return n, r.err
 }
 
-func newPart(mr *Reader, rawPart bool, maxMIMEHeaderSize int64) (*Part, error) {
+func newPart(mr *Reader, rawPart bool, maxMIMEHeaderSize, maxMIMEHeaders int64) (*Part, error) {
 	bp := &Part{
 		Header: make(map[string][]string),
 		mr:     mr,
 	}
-	if err := bp.populateHeaders(maxMIMEHeaderSize); err != nil {
+	if err := bp.populateHeaders(maxMIMEHeaderSize, maxMIMEHeaders); err != nil {
 		return nil, err
 	}
 	bp.r = partReader{bp}
@@ -142,9 +144,9 @@ func newPart(mr *Reader, rawPart bool, maxMIMEHeaderSize int64) (*Part, error) {
 	return bp, nil
 }
 
-func (bp *Part) populateHeaders(maxMIMEHeaderSize int64) error {
+func (bp *Part) populateHeaders(maxMIMEHeaderSize, maxMIMEHeaders int64) error {
 	r := textproto.NewReader(bp.mr.bufReader)
-	header, err := readMIMEHeader(r, maxMIMEHeaderSize)
+	header, err := readMIMEHeader(r, maxMIMEHeaderSize, maxMIMEHeaders)
 	if err == nil {
 		bp.Header = header
 	}
@@ -306,6 +308,19 @@ type Reader struct {
 // including header keys, values, and map overhead.
 const maxMIMEHeaderSize = 10 << 20
 
+func maxMIMEHeaders() int64 {
+	// multipartMaxHeaders is the maximum number of header entries NextPart will return,
+	// as well as the maximum combined total of header entries Reader.ReadForm will return
+	// in FileHeaders.
+	multipartMaxHeaders := godebug.Get("multipartmaxheaders")
+	if multipartMaxHeaders != "" {
+		if v, err := strconv.ParseInt(multipartMaxHeaders, 10, 64); err == nil && v >= 0 {
+			return v
+		}
+	}
+	return 10000
+}
+
 // NextPart returns the next part in the multipart or an error.
 // When there are no more parts, the error io.EOF is returned.
 //
@@ -313,7 +328,7 @@ const maxMIMEHeaderSize = 10 << 20
 // has a value of "quoted-printable", that header is instead
 // hidden and the body is transparently decoded during Read calls.
 func (r *Reader) NextPart() (*Part, error) {
-	return r.nextPart(false, maxMIMEHeaderSize)
+	return r.nextPart(false, maxMIMEHeaderSize, maxMIMEHeaders())
 }
 
 // NextRawPart returns the next part in the multipart or an error.
@@ -322,10 +337,10 @@ func (r *Reader) NextPart() (*Part, error) {
 // Unlike NextPart, it does not have special handling for
 // "Content-Transfer-Encoding: quoted-printable".
 func (r *Reader) NextRawPart() (*Part, error) {
-	return r.nextPart(true, maxMIMEHeaderSize)
+	return r.nextPart(true, maxMIMEHeaderSize, maxMIMEHeaders())
 }
 
-func (r *Reader) nextPart(rawPart bool, maxMIMEHeaderSize int64) (*Part, error) {
+func (r *Reader) nextPart(rawPart bool, maxMIMEHeaderSize, maxMIMEHeaders int64) (*Part, error) {
 	if r.currentPart != nil {
 		r.currentPart.Close()
 	}
@@ -350,7 +365,7 @@ func (r *Reader) nextPart(rawPart bool, maxMIMEHeaderSize int64) (*Part, error)
 
 		if r.isBoundaryDelimiterLine(line) {
 			r.partsRead++
-			bp, err := newPart(r, rawPart, maxMIMEHeaderSize)
+			bp, err := newPart(r, rawPart, maxMIMEHeaderSize, maxMIMEHeaders)
 			if err != nil {
 				return nil, err
 			}
diff --git a/src/mime/multipart/readmimeheader.go b/src/mime/multipart/readmimeheader.go
index 6836928..25aa6e2 100644
--- a/src/mime/multipart/readmimeheader.go
+++ b/src/mime/multipart/readmimeheader.go
@@ -11,4 +11,4 @@ import (
 // readMIMEHeader is defined in package net/textproto.
 //
 //go:linkname readMIMEHeader net/textproto.readMIMEHeader
-func readMIMEHeader(r *textproto.Reader, lim int64) (textproto.MIMEHeader, error)
+func readMIMEHeader(r *textproto.Reader, maxMemory, maxHeaders int64) (textproto.MIMEHeader, error)
diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go
index 1c79f0a..ad2d777 100644
--- a/src/net/textproto/reader.go
+++ b/src/net/textproto/reader.go
@@ -484,12 +484,12 @@ func (r *Reader) ReadDotLines() ([]string, error) {
 //	}
 //
 func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) {
-	return readMIMEHeader(r, math.MaxInt64)
+	return readMIMEHeader(r, math.MaxInt64, math.MaxInt64)
 }
 
 // readMIMEHeader is a version of ReadMIMEHeader which takes a limit on the header size.
 // It is called by the mime/multipart package.
-func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) {
+func readMIMEHeader(r *Reader, maxMemory, maxHeaders int64) (MIMEHeader, error) {
 	// Avoid lots of small slice allocations later by allocating one
 	// large one ahead of time which we'll cut up into smaller
 	// slices. If this isn't big enough later, we allocate small ones.
@@ -507,7 +507,7 @@ func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) {
 	// Account for 400 bytes of overhead for the MIMEHeader, plus 200 bytes per entry.
 	// Benchmarking map creation as of go1.20, a one-entry MIMEHeader is 416 bytes and large
 	// MIMEHeaders average about 200 bytes per entry.
-	lim -= 400
+	maxMemory -= 400
 	const mapEntryOverhead = 200
 
 	// The first line cannot start with a leading space.
@@ -539,6 +539,11 @@ func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) {
 			continue
 		}
 
+		maxHeaders--
+		if maxHeaders < 0 {
+			return nil, errors.New("message too large")
+		}
+
 		// backport 5c55ac9bf1e5f779220294c843526536605f42ab
 		//
 		// value is computed as
@@ -557,11 +562,11 @@ func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) {
 
 		vv := m[key]
 		if vv == nil {
-			lim -= int64(len(key))
-			lim -= mapEntryOverhead
+			maxMemory -= int64(len(key))
+			maxMemory -= mapEntryOverhead
 		}
-		lim -= int64(len(value))
-		if lim < 0 {
+		maxMemory -= int64(len(value))
+		if maxMemory < 0 {
 			// TODO: This should be a distinguishable error (ErrMessageTooLarge)
 			// to allow mime/multipart to detect it.
 			return m, errors.New("message too large")
-- 
2.25.1