admin: rate-limit pprof/profile and pprof/trace endpoints

/debug/pprof/profile and /debug/pprof/trace each block for up to 30
   seconds while collecting a CPU or execution trace. Without any
   concurrency guard, any process that can reach the admin socket can
   hammer these endpoints to cause sustained CPU overhead and degrade
   server performance.

   Add pprofRateLimited(), a semaphore-based wrapper (capacity 1) that
   allows at most one in-flight profiling request at a time and returns
   429 Too Many Requests to any concurrent caller. The fast snapshot
   endpoints (index, cmdline, symbol) are left unwrapped
This commit is contained in:
Gaurav Poudel 2026-02-15 01:07:25 +05:30
parent 929d0e502a
commit b38b107000

View file

@ -263,9 +263,9 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, _ Co
// register debugging endpoints
addRouteWithMetrics("/debug/pprof/", handlerLabel, http.HandlerFunc(pprof.Index))
addRouteWithMetrics("/debug/pprof/cmdline", handlerLabel, http.HandlerFunc(pprof.Cmdline))
addRouteWithMetrics("/debug/pprof/profile", handlerLabel, http.HandlerFunc(pprof.Profile))
addRouteWithMetrics("/debug/pprof/profile", handlerLabel, pprofRateLimited(http.HandlerFunc(pprof.Profile)))
addRouteWithMetrics("/debug/pprof/symbol", handlerLabel, http.HandlerFunc(pprof.Symbol))
addRouteWithMetrics("/debug/pprof/trace", handlerLabel, http.HandlerFunc(pprof.Trace))
addRouteWithMetrics("/debug/pprof/trace", handlerLabel, pprofRateLimited(http.HandlerFunc(pprof.Trace)))
addRouteWithMetrics("/debug/vars", handlerLabel, expvar.Handler())
// register third-party module endpoints
@ -1356,6 +1356,24 @@ func (e APIError) Error() string {
return e.Message
}
// pprofSem limits concurrent CPU-intensive pprof operations (profile, trace)
// to prevent a DoS via repeated 30-second profiling sessions.
var pprofSem = make(chan struct{}, 1)
// pprofRateLimited wraps an http.Handler so that at most one request is
// served at a time. Additional concurrent callers receive 429.
func pprofRateLimited(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
select {
case pprofSem <- struct{}{}:
defer func() { <-pprofSem }()
h.ServeHTTP(w, r)
default:
http.Error(w, "too many profiling requests; try again later", http.StatusTooManyRequests)
}
})
}
// parseAdminListenAddr extracts a singular listen address from either addr
// or defaultAddr, returning the network and the address of the listener.
func parseAdminListenAddr(addr string, defaultAddr string) (NetworkAddress, error) {