diff options
| author | Soumya Sambu <soumya.sambu@windriver.com> | 2023-12-20 12:03:38 +0000 |
|---|---|---|
| committer | Steve Sakoman <steve@sakoman.com> | 2023-12-22 16:36:55 -1000 |
| commit | 7262c0f2351cd08f049c7d050e8672c45e1952c0 (patch) | |
| tree | 3b70c3b29d9fbd0277a21a3bdc7d767461499a2f | |
| parent | 558325482c9538acf87eb0014babe0fc26f0471f (diff) | |
| download | poky-7262c0f2351cd08f049c7d050e8672c45e1952c0.tar.gz | |
go: Fix CVE-2023-39326
A malicious HTTP sender can use chunk extensions to cause a receiver
reading from a request or response body to read many more bytes from
the network than are in the body. A malicious HTTP client can further
exploit this to cause a server to automatically read a large amount
of data (up to about 1GiB) when a handler fails to read the entire
body of a request. Chunk extensions are a little-used HTTP feature
which permit including additional metadata in a request or response
body sent using the chunked encoding. The net/http chunked encoding
reader discards this metadata. A sender can exploit this by inserting
a large metadata segment with each byte transferred. The chunk reader
now produces an error if the ratio of real body to encoded bytes grows
too small.
References:
https://nvd.nist.gov/vuln/detail/CVE-2023-39326
https://security-tracker.debian.org/tracker/CVE-2023-39326
(From OE-Core rev: 448df3bb9277287dd8586987199223b7314fdd01)
Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
| -rw-r--r-- | meta/recipes-devtools/go/go-1.17.13.inc | 1 | ||||
| -rw-r--r-- | meta/recipes-devtools/go/go-1.20/CVE-2023-39326.patch | 182 |
2 files changed, 183 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 330f571d22..95c4461d3e 100644 --- a/meta/recipes-devtools/go/go-1.17.13.inc +++ b/meta/recipes-devtools/go/go-1.17.13.inc | |||
| @@ -47,6 +47,7 @@ SRC_URI += "\ | |||
| 47 | file://CVE-2023-29409.patch \ | 47 | file://CVE-2023-29409.patch \ |
| 48 | file://CVE-2023-39319.patch \ | 48 | file://CVE-2023-39319.patch \ |
| 49 | file://CVE-2023-39318.patch \ | 49 | file://CVE-2023-39318.patch \ |
| 50 | file://CVE-2023-39326.patch \ | ||
| 50 | " | 51 | " |
| 51 | SRC_URI[main.sha256sum] = "a1a48b23afb206f95e7bbaa9b898d965f90826f6f1d1fc0c1d784ada0cd300fd" | 52 | SRC_URI[main.sha256sum] = "a1a48b23afb206f95e7bbaa9b898d965f90826f6f1d1fc0c1d784ada0cd300fd" |
| 52 | 53 | ||
diff --git a/meta/recipes-devtools/go/go-1.20/CVE-2023-39326.patch b/meta/recipes-devtools/go/go-1.20/CVE-2023-39326.patch new file mode 100644 index 0000000000..ca78e552c2 --- /dev/null +++ b/meta/recipes-devtools/go/go-1.20/CVE-2023-39326.patch | |||
| @@ -0,0 +1,182 @@ | |||
| 1 | From 6446af942e2e2b161c4ec1b60d9703a2b55dc4dd Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Damien Neil <dneil@google.com> | ||
| 3 | Date: Tue, 7 Nov 2023 10:47:56 -0800 | ||
| 4 | Subject: [PATCH] net/http: limit chunked data overhead | ||
| 5 | |||
| 6 | The chunked transfer encoding adds some overhead to | ||
| 7 | the content transferred. When writing one byte per | ||
| 8 | chunk, for example, there are five bytes of overhead | ||
| 9 | per byte of data transferred: "1\r\nX\r\n" to send "X". | ||
| 10 | |||
| 11 | Chunks may include "chunk extensions", | ||
| 12 | which we skip over and do not use. | ||
| 13 | For example: "1;chunk extension here\r\nX\r\n". | ||
| 14 | |||
| 15 | A malicious sender can use chunk extensions to add | ||
| 16 | about 4k of overhead per byte of data. | ||
| 17 | (The maximum chunk header line size we will accept.) | ||
| 18 | |||
| 19 | Track the amount of overhead read in chunked data, | ||
| 20 | and produce an error if it seems excessive. | ||
| 21 | |||
| 22 | Updates #64433 | ||
| 23 | Fixes #64434 | ||
| 24 | Fixes CVE-2023-39326 | ||
| 25 | |||
| 26 | Change-Id: I40f8d70eb6f9575fb43f506eb19132ccedafcf39 | ||
| 27 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/2076135 | ||
| 28 | Reviewed-by: Tatiana Bradley <tatianabradley@google.com> | ||
| 29 | Reviewed-by: Roland Shoemaker <bracewell@google.com> | ||
| 30 | (cherry picked from commit 3473ae72ee66c60744665a24b2fde143e8964d4f) | ||
| 31 | Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/2095407 | ||
| 32 | Run-TryBot: Roland Shoemaker <bracewell@google.com> | ||
| 33 | TryBot-Result: Security TryBots <security-trybots@go-security-trybots.iam.gserviceaccount.com> | ||
| 34 | Reviewed-by: Damien Neil <dneil@google.com> | ||
| 35 | Reviewed-on: https://go-review.googlesource.com/c/go/+/547355 | ||
| 36 | Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> | ||
| 37 | LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> | ||
| 38 | |||
| 39 | CVE: CVE-2023-39326 | ||
| 40 | |||
| 41 | Upstream-Status: Backport [https://github.com/golang/go/commit/6446af942e2e2b161c4ec1b60d9703a2b55dc4dd] | ||
| 42 | |||
| 43 | Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com> | ||
| 44 | --- | ||
| 45 | src/net/http/internal/chunked.go | 36 +++++++++++++--- | ||
| 46 | src/net/http/internal/chunked_test.go | 59 +++++++++++++++++++++++++++ | ||
| 47 | 2 files changed, 89 insertions(+), 6 deletions(-) | ||
| 48 | |||
| 49 | diff --git a/src/net/http/internal/chunked.go b/src/net/http/internal/chunked.go | ||
| 50 | index f06e572..ddbaacb 100644 | ||
| 51 | --- a/src/net/http/internal/chunked.go | ||
| 52 | +++ b/src/net/http/internal/chunked.go | ||
| 53 | @@ -39,7 +39,8 @@ type chunkedReader struct { | ||
| 54 | n uint64 // unread bytes in chunk | ||
| 55 | err error | ||
| 56 | buf [2]byte | ||
| 57 | - checkEnd bool // whether need to check for \r\n chunk footer | ||
| 58 | + checkEnd bool // whether need to check for \r\n chunk footer | ||
| 59 | + excess int64 // "excessive" chunk overhead, for malicious sender detection | ||
| 60 | } | ||
| 61 | |||
| 62 | func (cr *chunkedReader) beginChunk() { | ||
| 63 | @@ -49,10 +50,38 @@ func (cr *chunkedReader) beginChunk() { | ||
| 64 | if cr.err != nil { | ||
| 65 | return | ||
| 66 | } | ||
| 67 | + cr.excess += int64(len(line)) + 2 // header, plus \r\n after the chunk data | ||
| 68 | + line = trimTrailingWhitespace(line) | ||
| 69 | + line, cr.err = removeChunkExtension(line) | ||
| 70 | + if cr.err != nil { | ||
| 71 | + return | ||
| 72 | + } | ||
| 73 | cr.n, cr.err = parseHexUint(line) | ||
| 74 | if cr.err != nil { | ||
| 75 | return | ||
| 76 | } | ||
| 77 | + // A sender who sends one byte per chunk will send 5 bytes of overhead | ||
| 78 | + // for every byte of data. ("1\r\nX\r\n" to send "X".) | ||
| 79 | + // We want to allow this, since streaming a byte at a time can be legitimate. | ||
| 80 | + // | ||
| 81 | + // A sender can use chunk extensions to add arbitrary amounts of additional | ||
| 82 | + // data per byte read. ("1;very long extension\r\nX\r\n" to send "X".) | ||
| 83 | + // We don't want to disallow extensions (although we discard them), | ||
| 84 | + // but we also don't want to allow a sender to reduce the signal/noise ratio | ||
| 85 | + // arbitrarily. | ||
| 86 | + // | ||
| 87 | + // We track the amount of excess overhead read, | ||
| 88 | + // and produce an error if it grows too large. | ||
| 89 | + // | ||
| 90 | + // Currently, we say that we're willing to accept 16 bytes of overhead per chunk, | ||
| 91 | + // plus twice the amount of real data in the chunk. | ||
| 92 | + cr.excess -= 16 + (2 * int64(cr.n)) | ||
| 93 | + if cr.excess < 0 { | ||
| 94 | + cr.excess = 0 | ||
| 95 | + } | ||
| 96 | + if cr.excess > 16*1024 { | ||
| 97 | + cr.err = errors.New("chunked encoding contains too much non-data") | ||
| 98 | + } | ||
| 99 | if cr.n == 0 { | ||
| 100 | cr.err = io.EOF | ||
| 101 | } | ||
| 102 | @@ -133,11 +162,6 @@ func readChunkLine(b *bufio.Reader) ([]byte, error) { | ||
| 103 | if len(p) >= maxLineLength { | ||
| 104 | return nil, ErrLineTooLong | ||
| 105 | } | ||
| 106 | - p = trimTrailingWhitespace(p) | ||
| 107 | - p, err = removeChunkExtension(p) | ||
| 108 | - if err != nil { | ||
| 109 | - return nil, err | ||
| 110 | - } | ||
| 111 | return p, nil | ||
| 112 | } | ||
| 113 | |||
| 114 | diff --git a/src/net/http/internal/chunked_test.go b/src/net/http/internal/chunked_test.go | ||
| 115 | index 08152ed..5fbeb08 100644 | ||
| 116 | --- a/src/net/http/internal/chunked_test.go | ||
| 117 | +++ b/src/net/http/internal/chunked_test.go | ||
| 118 | @@ -211,3 +211,62 @@ func TestChunkReadPartial(t *testing.T) { | ||
| 119 | } | ||
| 120 | |||
| 121 | } | ||
| 122 | + | ||
| 123 | +func TestChunkReaderTooMuchOverhead(t *testing.T) { | ||
| 124 | + // If the sender is sending 100x as many chunk header bytes as chunk data, | ||
| 125 | + // we should reject the stream at some point. | ||
| 126 | + chunk := []byte("1;") | ||
| 127 | + for i := 0; i < 100; i++ { | ||
| 128 | + chunk = append(chunk, 'a') // chunk extension | ||
| 129 | + } | ||
| 130 | + chunk = append(chunk, "\r\nX\r\n"...) | ||
| 131 | + const bodylen = 1 << 20 | ||
| 132 | + r := NewChunkedReader(&funcReader{f: func(i int) ([]byte, error) { | ||
| 133 | + if i < bodylen { | ||
| 134 | + return chunk, nil | ||
| 135 | + } | ||
| 136 | + return []byte("0\r\n"), nil | ||
| 137 | + }}) | ||
| 138 | + _, err := io.ReadAll(r) | ||
| 139 | + if err == nil { | ||
| 140 | + t.Fatalf("successfully read body with excessive overhead; want error") | ||
| 141 | + } | ||
| 142 | +} | ||
| 143 | + | ||
| 144 | +func TestChunkReaderByteAtATime(t *testing.T) { | ||
| 145 | + // Sending one byte per chunk should not trip the excess-overhead detection. | ||
| 146 | + const bodylen = 1 << 20 | ||
| 147 | + r := NewChunkedReader(&funcReader{f: func(i int) ([]byte, error) { | ||
| 148 | + if i < bodylen { | ||
| 149 | + return []byte("1\r\nX\r\n"), nil | ||
| 150 | + } | ||
| 151 | + return []byte("0\r\n"), nil | ||
| 152 | + }}) | ||
| 153 | + got, err := io.ReadAll(r) | ||
| 154 | + if err != nil { | ||
| 155 | + t.Errorf("unexpected error: %v", err) | ||
| 156 | + } | ||
| 157 | + if len(got) != bodylen { | ||
| 158 | + t.Errorf("read %v bytes, want %v", len(got), bodylen) | ||
| 159 | + } | ||
| 160 | +} | ||
| 161 | + | ||
| 162 | +type funcReader struct { | ||
| 163 | + f func(iteration int) ([]byte, error) | ||
| 164 | + i int | ||
| 165 | + b []byte | ||
| 166 | + err error | ||
| 167 | +} | ||
| 168 | + | ||
| 169 | +func (r *funcReader) Read(p []byte) (n int, err error) { | ||
| 170 | + if len(r.b) == 0 && r.err == nil { | ||
| 171 | + r.b, r.err = r.f(r.i) | ||
| 172 | + r.i++ | ||
| 173 | + } | ||
| 174 | + n = copy(p, r.b) | ||
| 175 | + r.b = r.b[n:] | ||
| 176 | + if len(r.b) > 0 { | ||
| 177 | + return n, nil | ||
| 178 | + } | ||
| 179 | + return n, r.err | ||
| 180 | +} | ||
| 181 | -- | ||
| 182 | 2.40.0 | ||
