summaryrefslogtreecommitdiffstats
path: root/meta/recipes-devtools/go/go-1.14/CVE-2023-45290.patch
blob: ddc2f67c964e9ca218ac7a63fd8e1c015084c4fe (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
From bf80213b121074f4ad9b449410a4d13bae5e9be0 Mon Sep 17 00:00:00 2001
From: Damien Neil <dneil@google.com>
Date: Tue, 16 Jan 2024 15:37:52 -0800
Subject: [PATCH] [release-branch.go1.21] net/textproto, mime/multipart: avoid
 unbounded read in MIME header

mime/multipart.Reader.ReadForm allows specifying the maximum amount
of memory that will be consumed by the form. While this limit is
correctly applied to the parsed form data structure, it was not
being applied to individual header lines in a form.

For example, when presented with a form containing a header line
that never ends, ReadForm will continue to read the line until it
runs out of memory.

Limit the amount of data consumed when reading a header.

Fixes CVE-2023-45290
Fixes #65389
For #65383

Change-Id: I7f9264d25752009e95f6b2c80e3d76aaf321d658
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/2134435
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/2173776
Reviewed-by: Carlos Amedee <amedee@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/569240
Auto-Submit: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>

Upstream-Status: Backport [https://github.com/golang/go/commit/bf80213b121074f4ad9b449410a4d13bae5e9be0]
CVE: CVE-2023-45290
Signed-off-by: Vijay Anusuri <vanusuri@mvista.com>
---
 src/mime/multipart/formdata_test.go | 42 +++++++++++++++++++++++++
 src/net/textproto/reader.go         | 48 ++++++++++++++++++++---------
 src/net/textproto/reader_test.go    | 12 ++++++++
 3 files changed, 87 insertions(+), 15 deletions(-)

diff --git a/src/mime/multipart/formdata_test.go b/src/mime/multipart/formdata_test.go
index c78eeb7..f729da6 100644
--- a/src/mime/multipart/formdata_test.go
+++ b/src/mime/multipart/formdata_test.go
@@ -421,6 +421,48 @@ func TestReadFormLimits(t *testing.T) {
 	}
 }
 
+func TestReadFormEndlessHeaderLine(t *testing.T) {
+	for _, test := range []struct {
+		name   string
+		prefix string
+	}{{
+		name:   "name",
+		prefix: "X-",
+	}, {
+		name:   "value",
+		prefix: "X-Header: ",
+	}, {
+		name:   "continuation",
+		prefix: "X-Header: foo\r\n  ",
+	}} {
+		t.Run(test.name, func(t *testing.T) {
+			const eol = "\r\n"
+			s := `--boundary` + eol
+			s += `Content-Disposition: form-data; name="a"` + eol
+			s += `Content-Type: text/plain` + eol
+			s += test.prefix
+			fr := io.MultiReader(
+				strings.NewReader(s),
+				neverendingReader('X'),
+			)
+			r := NewReader(fr, "boundary")
+			_, err := r.ReadForm(1 << 20)
+			if err != ErrMessageTooLarge {
+				t.Fatalf("ReadForm(1 << 20): %v, want ErrMessageTooLarge", err)
+			}
+		})
+	}
+}
+
+type neverendingReader byte
+
+func (r neverendingReader) Read(p []byte) (n int, err error) {
+	for i := range p {
+		p[i] = byte(r)
+	}
+	return len(p), nil
+}
+
 func BenchmarkReadForm(b *testing.B) {
 	for _, test := range []struct {
 		name string
diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go
index ad2d777..cea6613 100644
--- a/src/net/textproto/reader.go
+++ b/src/net/textproto/reader.go
@@ -17,6 +17,10 @@ import (
 	"sync"
 )
 
+// TODO: This should be a distinguishable error (ErrMessageTooLarge)
+// to allow mime/multipart to detect it.
+var errMessageTooLarge = errors.New("message too large")
+
 // A Reader implements convenience methods for reading requests
 // or responses from a text protocol network connection.
 type Reader struct {
@@ -38,13 +42,13 @@ func NewReader(r *bufio.Reader) *Reader {
 // ReadLine reads a single line from r,
 // eliding the final \n or \r\n from the returned string.
 func (r *Reader) ReadLine() (string, error) {
-	line, err := r.readLineSlice()
+	line, err := r.readLineSlice(-1)
 	return string(line), err
 }
 
 // ReadLineBytes is like ReadLine but returns a []byte instead of a string.
 func (r *Reader) ReadLineBytes() ([]byte, error) {
-	line, err := r.readLineSlice()
+	line, err := r.readLineSlice(-1)
 	if line != nil {
 		buf := make([]byte, len(line))
 		copy(buf, line)
@@ -53,7 +57,10 @@ func (r *Reader) ReadLineBytes() ([]byte, error) {
 	return line, err
 }
 
-func (r *Reader) readLineSlice() ([]byte, error) {
+// readLineSlice reads a single line from r,
+// up to lim bytes long (or unlimited if lim is less than 0),
+// eliding the final \r or \r\n from the returned string.
+func (r *Reader) readLineSlice(lim int64) ([]byte, error) {
 	r.closeDot()
 	var line []byte
 	for {
@@ -61,6 +68,9 @@ func (r *Reader) readLineSlice() ([]byte, error) {
 		if err != nil {
 			return nil, err
 		}
+		if lim >= 0 && int64(len(line))+int64(len(l)) > lim {
+			return nil, errMessageTooLarge
+		}
 		// Avoid the copy if the first call produced a full line.
 		if line == nil && !more {
 			return l, nil
@@ -93,7 +103,7 @@ func (r *Reader) readLineSlice() ([]byte, error) {
 // A line consisting of only white space is never continued.
 //
 func (r *Reader) ReadContinuedLine() (string, error) {
-	line, err := r.readContinuedLineSlice(noValidation)
+	line, err := r.readContinuedLineSlice(-1, noValidation)
 	return string(line), err
 }
 
@@ -114,7 +124,7 @@ func trim(s []byte) []byte {
 // ReadContinuedLineBytes is like ReadContinuedLine but
 // returns a []byte instead of a string.
 func (r *Reader) ReadContinuedLineBytes() ([]byte, error) {
-	line, err := r.readContinuedLineSlice(noValidation)
+	line, err := r.readContinuedLineSlice(-1, noValidation)
 	if line != nil {
 		buf := make([]byte, len(line))
 		copy(buf, line)
@@ -127,13 +137,14 @@ func (r *Reader) ReadContinuedLineBytes() ([]byte, error) {
 // returning a byte slice with all lines. The validateFirstLine function
 // is run on the first read line, and if it returns an error then this
 // error is returned from readContinuedLineSlice.
-func (r *Reader) readContinuedLineSlice(validateFirstLine func([]byte) error) ([]byte, error) {
+// It reads up to lim bytes of data (or unlimited if lim is less than 0).
+func (r *Reader) readContinuedLineSlice(lim int64, validateFirstLine func([]byte) error) ([]byte, error) {
 	if validateFirstLine == nil {
 		return nil, fmt.Errorf("missing validateFirstLine func")
 	}
 
 	// Read the first line.
-	line, err := r.readLineSlice()
+	line, err := r.readLineSlice(lim)
 	if err != nil {
 		return nil, err
 	}
@@ -161,13 +172,21 @@ func (r *Reader) readContinuedLineSlice(validateFirstLine func([]byte) error) ([
 	// copy the slice into buf.
 	r.buf = append(r.buf[:0], trim(line)...)
 
+	if lim < 0 {
+		lim = math.MaxInt64
+	}
+	lim -= int64(len(r.buf))
+
 	// Read continuation lines.
 	for r.skipSpace() > 0 {
-		line, err := r.readLineSlice()
+		r.buf = append(r.buf, ' ')
+		if int64(len(r.buf)) >= lim {
+			return nil, errMessageTooLarge
+		}
+		line, err := r.readLineSlice(lim - int64(len(r.buf)))
 		if err != nil {
 			break
 		}
-		r.buf = append(r.buf, ' ')
 		r.buf = append(r.buf, trim(line)...)
 	}
 	return r.buf, nil
@@ -512,7 +531,8 @@ func readMIMEHeader(r *Reader, maxMemory, maxHeaders int64) (MIMEHeader, error)
 
 	// The first line cannot start with a leading space.
 	if buf, err := r.R.Peek(1); err == nil && (buf[0] == ' ' || buf[0] == '\t') {
-		line, err := r.readLineSlice()
+		const errorLimit = 80 // arbitrary limit on how much of the line we'll quote
+		line, err := r.readLineSlice(errorLimit)
 		if err != nil {
 			return m, err
 		}
@@ -520,7 +540,7 @@ func readMIMEHeader(r *Reader, maxMemory, maxHeaders int64) (MIMEHeader, error)
 	}
 
 	for {
-		kv, err := r.readContinuedLineSlice(mustHaveFieldNameColon)
+		kv, err := r.readContinuedLineSlice(maxMemory, mustHaveFieldNameColon)
 		if len(kv) == 0 {
 			return m, err
 		}
@@ -541,7 +561,7 @@ func readMIMEHeader(r *Reader, maxMemory, maxHeaders int64) (MIMEHeader, error)
 
 		maxHeaders--
 		if maxHeaders < 0 {
-			return nil, errors.New("message too large")
+			return nil, errMessageTooLarge
 		}
 
 		// backport 5c55ac9bf1e5f779220294c843526536605f42ab
@@ -567,9 +587,7 @@ func readMIMEHeader(r *Reader, maxMemory, maxHeaders int64) (MIMEHeader, error)
 		}
 		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")
+			return m, errMessageTooLarge
 		}
 		if vv == nil && len(strs) > 0 {
 			// More than likely this will be a single-element key.
diff --git a/src/net/textproto/reader_test.go b/src/net/textproto/reader_test.go
index 3ae0de1..db1ed91 100644
--- a/src/net/textproto/reader_test.go
+++ b/src/net/textproto/reader_test.go
@@ -34,6 +34,18 @@ func TestReadLine(t *testing.T) {
 	}
 }
 
+func TestReadLineLongLine(t *testing.T) {
+	line := strings.Repeat("12345", 10000)
+	r := reader(line + "\r\n")
+	s, err := r.ReadLine()
+	if err != nil {
+		t.Fatalf("Line 1: %v", err)
+	}
+	if s != line {
+		t.Fatalf("%v-byte line does not match expected %v-byte line", len(s), len(line))
+	}
+}
+
 func TestReadContinuedLine(t *testing.T) {
 	r := reader("line1\nline\n 2\nline3\n")
 	s, err := r.ReadContinuedLine()
-- 
2.25.1