{"record":{"id":"77ca6f37edac2096","repo":"valyala/fasthttp","slug":"cannot-unread-r-char-at-the-end-of-chunk-size","errorCode":null,"errorMessage":"cannot unread '\\r' char at the end of chunk size: %w","messagePattern":"cannot unread '\\\\r' char at the end of chunk size: %w","errorType":"exception","errorClass":"ErrBrokenChunk","httpStatus":null,"severity":"error","filePath":"http.go","lineNumber":2985,"sourceCode":"\nfunc parseChunkSize(r *bufio.Reader) (int, error) {\n\tn, err := readHexInt(r)\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\tinExt := false\n\tafterSizeOWS := false\n\tfor {\n\t\tc, err := r.ReadByte()\n\t\tif err != nil {\n\t\t\treturn -1, ErrBrokenChunk{\n\t\t\t\terror: fmt.Errorf(\"cannot read '\\\\r' char at the end of chunk size: %w\", err),\n\t\t\t}\n\t\t}\n\t\tif c == '\\r' {\n\t\t\tif err := r.UnreadByte(); err != nil {\n\t\t\t\treturn -1, ErrBrokenChunk{\n\t\t\t\t\terror: fmt.Errorf(\"cannot unread '\\\\r' char at the end of chunk size: %w\", err),\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\t// Security: Don't allow newlines in chunk extensions.\n\t\t// This can lead to request smuggling issues with some reverse proxies.\n\t\tif c == '\\n' {\n\t\t\treturn -1, ErrBrokenChunk{\n\t\t\t\terror: errors.New(\"invalid character '\\\\n' after chunk size\"),\n\t\t\t}\n\t\t}\n\t\tif inExt {\n\t\t\tcontinue\n\t\t}\n\t\tswitch c {\n\t\tcase ' ', '\\t':\n\t\t\tafterSizeOWS = true\n\t\t\tcontinue","sourceCodeStart":2967,"sourceCodeEnd":3003,"githubUrl":"https://github.com/valyala/fasthttp/blob/c96f600972c6f4a7a30d664257b340ebe9d60124/http.go#L2967-L3003","documentation":"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.","triggerScenarios":"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.","commonSituations":"Sharing a fasthttp Request/Response or its bufio.Reader across goroutines; passing a custom bufio-like reader into low-level fasthttp APIs.","solutions":["Ensure the request, response and their bufio readers are used by only one goroutine at a time.","Use standard bufio.Reader; do not substitute custom reader types in fasthttp internals.","Handle ErrBrokenChunk with errors.As and close/recycle the connection, since its parse state is unreliable.","If reproducible with stock fasthttp only, report upstream with a minimal repro."],"exampleFix":"// before (shared across goroutines)\ngo handle(resp)\ngo handle(resp) // resp reader shared -> corrupted state\n// after\nfor resp := range responses {\n    go handle(resp) // one reader per goroutine\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isUnreadBrokenChunk(err error) bool {\n    var bc fasthttp.ErrBrokenChunk\n    if !errors.As(err, &bc) { return false }\n    return strings.Contains(bc.error.Error(), \"cannot unread\")\n}","tryCatchPattern":"var bc fasthttp.ErrBrokenChunk\nif errors.As(err, &bc) && strings.Contains(bc.error.Error(), \"cannot unread\") {\n    // bufio reader state corrupted: do not reuse the reader/conn\n    _ = conn.Close()\n}","preventionTips":["Never share fasthttp Request/Response or bufio.Reader across goroutines.","Use only standard bufio.Reader with fasthttp internals.","Recycle request/response objects strictly one-at-a-time via Acquire/Release.","Close the connection after this error; parse state is unreliable."],"tags":["fasthttp","chunked-encoding","bufio","concurrency"],"backgroundTag":"broken-chunked-body","analyzedSha":"c96f600972c6f4a7a30d664257b340ebe9d60124","analyzedAt":"2026-08-31T22:48:28.265Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}