Double Free in Chi (Go)
Double Free in Chi with Go
A double free occurs when a program frees the same memory allocation twice without updating the pointer. In Go applications using the net/http router Chi, this can happen when handlers incorrectly manage the lifecycle of request-scoped resources that are allocated on the heap and later freed during request processing.
Chi itself does not perform memory management for application data, but it provides constructs that can lead to double free vulnerabilities if developers misuse request-scoped structs or reuse references across request cycles. For example, if a handler stores a pointer to a struct in a global context and later frees it both in a deferred function and again in a cleanup routine triggered by route reuse, a double free can occur.
Consider a Chi middleware that parses JSON into a struct and stores it in the request context. If the same context value is accessed across multiple middleware chains or if the pointer is reused after being freed, the Go runtime may attempt to free the underlying memory more than once, resulting in undefined behavior. This is especially likely when using third-party context keys without strict naming discipline.
Additionally, Chi's support for composable routes means that a root route might allocate a resource that is later shared across nested subroutes. If a nested route frees the resource after handling a request and the parent route also attempts to free it upon shutdown, both calls may target the same pointer, causing a double free.
These scenarios are not bugs in Chi itself but stem from application-level misuse of Go's concurrency model and memory semantics. Because Chi routes are typically processed in separate goroutines, improper sharing of mutable state can amplify the risk. A double free in a Chi-based service may crash the process or, in rare cases, be exploited to bypass memory safety checks.
Real-world examples include services that reuse request contexts across background jobs or logging systems that access request-scoped data after it has been explicitly cleared, leading to premature or duplicate deallocations.
Go-Specific Remediation in Chi
To prevent double free vulnerabilities in Chi, developers must ensure that any heap-allocated data stored in request contexts or middleware is freed exactly once, typically during the final phase of request handling.
Use Go's standard patterns: avoid global mutable state, do not reuse context keys with pointers, and always defer cleanup after the request context is no longer valid.
func handler(w http.ResponseWriter, r *http.Request) {
// Allocate resource on heap
data := &MyData{Value: "example"}
// Store pointer in request context
ctx := context.WithValue(r.Context(), "key", data)
// Process request
// Free resource exactly once, after use
defer func() {
if stored := ctx.Value("key"); stored != nil {
// Type assertion safe because we control allocation
if d, ok := stored.(*MyData); ok {
// Explicitly set to nil after free
d = nil
}
}
}()
// Write response before context is destroyed
fmt.Fprint(w, "OK")
}
Never store pointers in global variables that persist across requests. Instead, use request-local contexts and ensure that cleanup occurs only once per request lifecycle.
When using middleware, avoid chaining handlers that mutate the same context value. If a middleware needs to read data set by a previous handler, it should do so without altering or freeing it. For example:
// Middleware A: parses and stores
func parseMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
data := &MyData{Value: "parsed"}
ctx := context.WithValue(r.Context(), "key", data)
next.ServeHTTP(w, r.WithContext(ctx))
// Do NOT free here
})
}
// Middleware B: reads only
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if v := ctx.Value("key"); v != nil {
// Only read, never free
_ = v.(*MyData)
}
next.ServeHTTP(w, r)
})
}
By adhering to these practices, developers prevent double free errors even under high concurrency. The Go garbage collector cannot protect against manual memory mismanagement, so discipline in resource lifecycle management is essential.
Additionally, use tools like go vet, staticcheck, and the race detector to identify potential data races or improper context usage that could lead to double frees in concurrent Chi routes.
Frequently Asked Questions
Can Chi cause a double free vulnerability on its own?
How can I detect double free risks in my Chi application?
go test -race to detect data races, use staticcheck to find potential memory issues, and audit context usage across middleware chains. Ensure that any pointer stored in a request context is only freed once, typically at the end of the request lifecycle, and never accessed after being freed.