BUG: content-length smaller than prefetched body size will lead requestStream Read() PANIC
DrakeOu opened this issue · 1 comments
Steps to Reproduce:
- Enable StreamRequestBody to use StreamBody() for reading the body stream data.
- Set MaxRequestBodySize to the lowest possible value (1).
- Send a PUT HTTP request with a Content-Length smaller than the actual body size being transported, but ensure the Content-Length is smaller than 1024.
- Implement a reader to read out the stream data until io.EOF. This will cause a panic.
Demo Code:
package main
import (
"fmt"
"github.com/valyala/fasthttp"
)
func main() {
// Server request handler
requestHandler := func(ctx *fasthttp.RequestCtx) {
stream := ctx.RequestBodyStream()
buf := make([]byte, 10)
for {
n, err := stream.Read(buf)
if err != nil {
break
}
fmt.Printf("Read %d bytes: %s\n", n, buf[:n])
}
}
// Start the server
server := fasthttp.Server{
Handler: requestHandler,
MaxRequestBodySize: 1,
StreamRequestBody: true,
}
if err := server.ListenAndServe("localhost:20020"); err != nil {
fmt.Printf("Error in ListenAndServe: %s", err)
}
}
Related Code in requestStream Class:
func (rs *requestStream) Read(p []byte) (int, error) {
var (
n int
err error
)
if rs.header.ContentLength() == -1 {
// ... chunk logic
}
if rs.totalBytesRead == rs.header.ContentLength() {
return 0, io.EOF
}
prefetchedSize := int(rs.prefetchedBytes.Size())
if prefetchedSize > rs.totalBytesRead {
left := prefetchedSize - rs.totalBytesRead
if len(p) > left {
p = p[:left]
}
n, err := rs.prefetchedBytes.Read(p)
rs.totalBytesRead += n
if n == rs.header.ContentLength() {
return n, io.EOF
}
return n, err
}
left := rs.header.ContentLength() - rs.totalBytesRead
if len(p) > left {
p = p[:left]
}
n, err = rs.reader.Read(p)
rs.totalBytesRead += n
if err != nil {
return n, err
}
if rs.totalBytesRead == rs.header.ContentLength() {
err = io.EOF
}
return n, err
}
Issue Description:
Given an HTTP request with Content-Length set to 10 and a body size greater than 10 (e.g., 12), the problem arises with the prefetched buffer. The prefetched buffer is a fixed size of 1024 bytes and attempts to fill the buffer, ignoring the real Content-Length value, resulting in a prefetched buffer size of 12.
SO The first read returns n as 12 and err as nil, causing an inconsistency between the size and Content-Length. The second read results in a left value of -2, leading to a slice operation panic:
if len(p) > left {
p = p[:left]
}
The core problem is the prefetched buffer logic and subsequent logic not properly handling this inconsistency, resulting in a negative size for the second read. Maybe some additional logic to properly handling should return io.EOF when the body size exceeds Content-Length.
The magic number 1024 is defined here (fasthttp/http.go:2300):
func readBodyIdentity(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, error) {
dst = dst[:cap(dst)]
if len(dst) == 0 {
dst = make([]byte, 1024)
}
...
}
This issue might be related to #1469.
Another way to reproduce is to call requestCtx.Body() when StreamBody enabled and Content-Length smaller than bodySize, reason is the same.