valyala/fasthttp · error · ErrBrokenChunk

cannot unread '\r' char at the end of chunk size: %w

Error message

cannot unread '\r' char at the end of chunk size: %w

What it means

While parsing the end of a chunk-size line, fasthttp read the '\r' byte and then failed to push it back with bufio.Reader.UnreadByte (needed so the following CRLF-reading helper sees it). ErrBrokenChunk wraps the UnreadByte error. This almost always means the bufio reader is in an unexpected state, e.g. a corrupted reader shared between goroutines or a custom bufio implementation.

Source

Thrown at http.go:2985

func parseChunkSize(r *bufio.Reader) (int, error) {
	n, err := readHexInt(r)
	if err != nil {
		return -1, err
	}
	inExt := false
	afterSizeOWS := false
	for {
		c, err := r.ReadByte()
		if err != nil {
			return -1, ErrBrokenChunk{
				error: fmt.Errorf("cannot read '\\r' char at the end of chunk size: %w", err),
			}
		}
		if c == '\r' {
			if err := r.UnreadByte(); err != nil {
				return -1, ErrBrokenChunk{
					error: fmt.Errorf("cannot unread '\\r' char at the end of chunk size: %w", err),
				}
			}
			break
		}
		// Security: Don't allow newlines in chunk extensions.
		// This can lead to request smuggling issues with some reverse proxies.
		if c == '\n' {
			return -1, ErrBrokenChunk{
				error: errors.New("invalid character '\\n' after chunk size"),
			}
		}
		if inExt {
			continue
		}
		switch c {
		case ' ', '\t':
			afterSizeOWS = true
			continue

View on GitHub (pinned to c96f600972)

Solutions

  1. Ensure the request, response and their bufio readers are used by only one goroutine at a time.
  2. Use standard bufio.Reader; do not substitute custom reader types in fasthttp internals.
  3. Handle ErrBrokenChunk with errors.As and close/recycle the connection, since its parse state is unreliable.
  4. If reproducible with stock fasthttp only, report upstream with a minimal repro.

Example fix

// before (shared across goroutines)
go handle(resp)
go handle(resp) // resp reader shared -> corrupted state
// after
for resp := range responses {
    go handle(resp) // one reader per goroutine
}
Defensive patterns

Strategy: type-guard

Type guard

func isUnreadBrokenChunk(err error) bool {
    var bc fasthttp.ErrBrokenChunk
    if !errors.As(err, &bc) { return false }
    return strings.Contains(bc.error.Error(), "cannot unread")
}

Try / catch

var bc fasthttp.ErrBrokenChunk
if errors.As(err, &bc) && strings.Contains(bc.error.Error(), "cannot unread") {
    // bufio reader state corrupted: do not reuse the reader/conn
    _ = conn.Close()
}

Prevention

When it happens

Trigger: Parsing a chunked body when ReadByte succeeds returning '\r' but UnreadByte fails — only possible with a non-standard bufio.Reader implementation or a reader corrupted by concurrent use.

Common situations: Sharing a fasthttp Request/Response or its bufio.Reader across goroutines; passing a custom bufio-like reader into low-level fasthttp APIs.

Related errors


AI-assisted analysis of valyala/fasthttp@c96f600972 (2026-08-31). Data as JSON: /api/errors/77ca6f37edac2096. Report an issue: GitHub.