Skip to content

safety: 44 bare type assertions from sync.Pool without comma-ok — panics on pool corruption #2231

Description

@pageton

Description

Throughout the codebase, sync.Pool.Get() results are cast to concrete types using bare type assertions (no comma-ok):

zr := v.(*brotli.Reader)   // panics if v is wrong type
sw := v.(stackless.Writer)  // panics if v is wrong type

If a pool is ever corrupted (wrong type Put back, double-release, or nil stored where a typed value is expected), these panic immediately with no recovery context — crashing the server.

Affected Locations (44 instances)

File Line Assertion
stream.go 39 v.(*bufio.Writer)
brotli.go 31, 53, 73, 148 v.(*brotli.Reader), v.(stackless.Writer), v.(*brotli.Writer), ctxv.(*compressCtx)
compress.go 31, 54, 86, 116, 193, 300, 398, 428 v.(*gzip.Reader), v.(io.ReadCloser), v.(stackless.Writer), v.(*gzip.Writer), ctxv.(*compressCtx), v.(*zlib.Writer)
zstd.go 33, 53, 76, 128 v.(*zstd.Decoder), v.(stackless.Writer), v.(*zstd.Encoder), ctxv.(*compressCtx)
http.go 1700, 1718 v.(*statsWriter), v.(*bufio.Writer)
server.go 42, 2278, 2727, 2829, 2847, 2866 v.(*Server), v.(*atomic.Int64), v.(*hijackConn), v.(*bufio.Reader), v.(*bufio.Writer), v.(*RequestCtx)
timer.go 40 v.(*time.Timer)
peripconn.go 67, 81 v.(*perIPTLSConn), v.(*perIPConn)
fs.go 655, 1933 v.(*fsSmallFileReader), v.(*sync.Mutex)
client.go 1343, 1365, 1933, 2000, 2028, 2654 v.(*Request), v.(*Response), v.(*clientConn), v.(*bufio.Writer), v.(*bufio.Reader), v.(*pipelineWork)
fasthttpproxy/dialer.go 252 v.(*proxyInfo)
workerpool.go 195 vch.(*workerChan)
stackless/func.go 67 v.(*funcWork)
stackless/writer.go 123 ctx.(*writer)

Current State

All are guarded by preceding nil checks and pool types are controlled (only known types are Put back). However:

  1. No compile-time safety — a refactoring mistake silently becomes a runtime panic
  2. forcetypeassert linter is explicitly disabled in .golangci.yml (line 15), which would catch exactly this class of bug
  3. No debug context when panic occurs — bare assertion panics give only the type mismatch, not which pool or why

Suggested Fix

Option A (minimal): Re-enable the forcetypeassert linter with //nolint:forcetypeassert annotations on the known-safe pool assertions. This ensures new non-pool assertions are caught in CI.

Option B (robust): Wrap pool assertions in a helper:

func poolGet[T any](p *sync.Pool) T {
    v := p.Get()
    if v == nil {
        var zero T
        return zero
    }
    t, ok := v.(T)
    if !ok {
        panic(fmt.Sprintf("BUG: pool corruption: expected %T, got %T", zero, v))
    }
    return t
}

This provides a clear diagnostic message instead of a bare type assertion panic.

Impact

  • Low probability today (controlled pool types), but HIGH severity when triggered — unhandled panic crashes the server
  • Disabling forcetypeassert means future regressions in non-pool code won't be caught by CI

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions