diff --git a/admin.go b/admin.go index 46f1bbda389..b33ccc6eac6 100644 --- a/admin.go +++ b/admin.go @@ -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) {