From b38b10700010afbe8f585490e49bc115afa2c3e2 Mon Sep 17 00:00:00 2001 From: Gaurav Poudel Date: Sun, 15 Feb 2026 01:07:25 +0530 Subject: [PATCH] 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 --- admin.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) 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) {