Skip to content

Commit 51b7a84

Browse files
authored
Merge pull request #22148 from k8s-infra-cherrypick-robot/cherry-pick-22132-to-release-3.6
[release-3.6] Fix unbounded io.ReadAll on peer lease HTTP handler body
2 parents f85af38 + d4cbc7e commit 51b7a84

1 file changed

Lines changed: 31 additions & 3 deletions

File tree

server/lease/leasehttp/http.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ var (
3636
ErrLeaseHTTPTimeout = errors.New("waiting for node to catch up its applied index has timed out")
3737
)
3838

39+
// maxLeaseHTTPRequestSize bounds how much of a request body ServeHTTP will
40+
// read into memory. Legitimate LeaseKeepAliveRequest and LeaseInternalRequest
41+
// messages only carry a lease ID (and a bool flag), so this is generous
42+
// headroom, not a realistic size. Matches connReadLimitByte in
43+
// server/etcdserver/api/rafthttp/http.go for consistency.
44+
const maxLeaseHTTPRequestSize = 64 * 1024
45+
3946
// NewHandler returns an http Handler for lease renewals
4047
func NewHandler(l lease.Lessor, waitch func() <-chan struct{}) http.Handler {
4148
return &leaseHandler{l, waitch}
@@ -53,8 +60,13 @@ func (h *leaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
5360
}
5461

5562
defer r.Body.Close()
56-
b, err := io.ReadAll(r.Body)
63+
b, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxLeaseHTTPRequestSize))
5764
if err != nil {
65+
var maxBytesErr *http.MaxBytesError
66+
if errors.As(err, &maxBytesErr) {
67+
http.Error(w, "request body too large", http.StatusRequestEntityTooLarge)
68+
return
69+
}
5870
http.Error(w, "error reading body", http.StatusBadRequest)
5971
return
6072
}
@@ -262,8 +274,24 @@ func TimeToLiveHTTP(ctx context.Context, id lease.LeaseID, keys bool, url string
262274
return lresp, nil
263275
}
264276

277+
// maxLeaseHTTPResponseSize bounds how much of a lease HTTP response body
278+
// readResponse will read into memory. Unlike requests, a legitimate
279+
// LeaseInternalResponse can carry every key attached to a lease, so this
280+
// ceiling is deliberately generous -- it only guards against a misbehaving
281+
// or compromised peer forcing unbounded memory growth, not against large but
282+
// legitimate responses.
283+
const maxLeaseHTTPResponseSize = 100 * 1024 * 1024
284+
265285
func readResponse(resp *http.Response) (b []byte, err error) {
266-
b, err = io.ReadAll(resp.Body)
286+
b, err = io.ReadAll(io.LimitReader(resp.Body, maxLeaseHTTPResponseSize+1))
287+
if err != nil {
288+
httputil.GracefulClose(resp)
289+
return nil, err
290+
}
291+
if len(b) > maxLeaseHTTPResponseSize {
292+
resp.Body.Close()
293+
return nil, fmt.Errorf("lease: response body exceeds %d bytes limit", maxLeaseHTTPResponseSize)
294+
}
267295
httputil.GracefulClose(resp)
268-
return
296+
return b, nil
269297
}

0 commit comments

Comments
 (0)