summaryrefslogtreecommitdiffstats
path: root/meta/recipes-devtools/go/go-1.14/CVE-2023-39326.patch
blob: 998af361e89104d92d55f3910e0681e9c823b8b1 (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
From 6446af942e2e2b161c4ec1b60d9703a2b55dc4dd Mon Sep 17 00:00:00 2001
From: Damien Neil <dneil@google.com>
Date: Tue, 7 Nov 2023 10:47:56 -0800
Subject: [PATCH] [release-branch.go1.20] net/http: limit chunked data overhead

The chunked transfer encoding adds some overhead to
the content transferred. When writing one byte per
chunk, for example, there are five bytes of overhead
per byte of data transferred: "1\r\nX\r\n" to send "X".

Chunks may include "chunk extensions",
which we skip over and do not use.
For example: "1;chunk extension here\r\nX\r\n".

A malicious sender can use chunk extensions to add
about 4k of overhead per byte of data.
(The maximum chunk header line size we will accept.)

Track the amount of overhead read in chunked data,
and produce an error if it seems excessive.

Updates #64433
Fixes #64434
Fixes CVE-2023-39326

Change-Id: I40f8d70eb6f9575fb43f506eb19132ccedafcf39
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/2076135
Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
Reviewed-by: Roland Shoemaker <bracewell@google.com>
(cherry picked from commit 3473ae72ee66c60744665a24b2fde143e8964d4f)
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/2095407
Run-TryBot: Roland Shoemaker <bracewell@google.com>
TryBot-Result: Security TryBots <security-trybots@go-security-trybots.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/547355
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

Upstream-Status: Backport [https://github.com/golang/go/commit/6446af942e2e2b161c4ec1b60d9703a2b55dc4dd]
CVE: CVE-2023-39326
Signed-off-by: Vijay Anusuri <vanusuri@mvista.com>
---
 src/net/http/internal/chunked.go      | 36 +++++++++++++---
 src/net/http/internal/chunked_test.go | 59 +++++++++++++++++++++++++++
 2 files changed, 89 insertions(+), 6 deletions(-)

diff --git a/src/net/http/internal/chunked.go b/src/net/http/internal/chunked.go
index f06e572..ddbaacb 100644
--- a/src/net/http/internal/chunked.go
+++ b/src/net/http/internal/chunked.go
@@ -39,7 +39,8 @@ type chunkedReader struct {
 	n        uint64 // unread bytes in chunk
 	err      error
 	buf      [2]byte
-	checkEnd bool // whether need to check for \r\n chunk footer
+	checkEnd bool  // whether need to check for \r\n chunk footer
+	excess   int64 // "excessive" chunk overhead, for malicious sender detection
 }
 
 func (cr *chunkedReader) beginChunk() {
@@ -49,10 +50,38 @@ func (cr *chunkedReader) beginChunk() {
 	if cr.err != nil {
 		return
 	}
+	cr.excess += int64(len(line)) + 2 // header, plus \r\n after the chunk data
+	line = trimTrailingWhitespace(line)
+	line, cr.err = removeChunkExtension(line)
+	if cr.err != nil {
+		return
+	}
 	cr.n, cr.err = parseHexUint(line)
 	if cr.err != nil {
 		return
 	}
+	// A sender who sends one byte per chunk will send 5 bytes of overhead
+	// for every byte of data. ("1\r\nX\r\n" to send "X".)
+	// We want to allow this, since streaming a byte at a time can be legitimate.
+	//
+	// A sender can use chunk extensions to add arbitrary amounts of additional
+	// data per byte read. ("1;very long extension\r\nX\r\n" to send "X".)
+	// We don't want to disallow extensions (although we discard them),
+	// but we also don't want to allow a sender to reduce the signal/noise ratio
+	// arbitrarily.
+	//
+	// We track the amount of excess overhead read,
+	// and produce an error if it grows too large.
+	//
+	// Currently, we say that we're willing to accept 16 bytes of overhead per chunk,
+	// plus twice the amount of real data in the chunk.
+	cr.excess -= 16 + (2 * int64(cr.n))
+	if cr.excess < 0 {
+		cr.excess = 0
+	}
+	if cr.excess > 16*1024 {
+		cr.err = errors.New("chunked encoding contains too much non-data")
+	}
 	if cr.n == 0 {
 		cr.err = io.EOF
 	}
@@ -133,11 +162,6 @@ func readChunkLine(b *bufio.Reader) ([]byte, error) {
 	if len(p) >= maxLineLength {
 		return nil, ErrLineTooLong
 	}
-	p = trimTrailingWhitespace(p)
-	p, err = removeChunkExtension(p)
-	if err != nil {
-		return nil, err
-	}
 	return p, nil
 }
 
diff --git a/src/net/http/internal/chunked_test.go b/src/net/http/internal/chunked_test.go
index d067165..b20747d 100644
--- a/src/net/http/internal/chunked_test.go
+++ b/src/net/http/internal/chunked_test.go
@@ -212,3 +212,62 @@ func TestChunkReadPartial(t *testing.T) {
 	}
 
 }
+
+func TestChunkReaderTooMuchOverhead(t *testing.T) {
+	// If the sender is sending 100x as many chunk header bytes as chunk data,
+	// we should reject the stream at some point.
+	chunk := []byte("1;")
+	for i := 0; i < 100; i++ {
+		chunk = append(chunk, 'a') // chunk extension
+	}
+	chunk = append(chunk, "\r\nX\r\n"...)
+	const bodylen = 1 << 20
+	r := NewChunkedReader(&funcReader{f: func(i int) ([]byte, error) {
+		if i < bodylen {
+			return chunk, nil
+		}
+		return []byte("0\r\n"), nil
+	}})
+	_, err := io.ReadAll(r)
+	if err == nil {
+		t.Fatalf("successfully read body with excessive overhead; want error")
+	}
+}
+
+func TestChunkReaderByteAtATime(t *testing.T) {
+	// Sending one byte per chunk should not trip the excess-overhead detection.
+	const bodylen = 1 << 20
+	r := NewChunkedReader(&funcReader{f: func(i int) ([]byte, error) {
+		if i < bodylen {
+			return []byte("1\r\nX\r\n"), nil
+		}
+		return []byte("0\r\n"), nil
+	}})
+	got, err := io.ReadAll(r)
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if len(got) != bodylen {
+		t.Errorf("read %v bytes, want %v", len(got), bodylen)
+	}
+}
+
+type funcReader struct {
+	f   func(iteration int) ([]byte, error)
+	i   int
+	b   []byte
+	err error
+}
+
+func (r *funcReader) Read(p []byte) (n int, err error) {
+	if len(r.b) == 0 && r.err == nil {
+		r.b, r.err = r.f(r.i)
+		r.i++
+	}
+	n = copy(p, r.b)
+	r.b = r.b[n:]
+	if len(r.b) > 0 {
+		return n, nil
+	}
+	return n, r.err
+}
-- 
2.25.1