rc: don't expose pprof debug handlers on an unauthenticated server GHSA-mfvx-7rcj-9m5g CVE-PENDING

The pprof debug handlers were accessible without authentication disclosing the
process command line (which can carry backend credentials passed on the command
line) and runtime profiles.

Mount the pprof handlers only when when auth is configured or --rc-no-auth was
passed - so they obey the same rule as the rc endpoints.

Addresses GHSA-mfvx-7rcj-9m5g finding 1.
This commit is contained in:
Nick Craig-Wood
2026-07-31 13:21:59 +01:00
parent 21d8cd3b92
commit faaf716e9b
3 changed files with 56 additions and 10 deletions
+8
View File
@@ -2742,6 +2742,14 @@ curl -H "Content-Type: application/json" -X POST -d '{"potato":2,"sausage":1}' '
If you use the `--rc` flag this will also enable the use of the go
profiling tools on the same port.
The profiling endpoints follow the same authentication rules as the
rest of the rc: they are only served if authentication has been set up
(`--rc-user`/`--rc-pass` or `--rc-htpasswd`) or the
[`--rc-no-auth`](#--rc-no-auth) flag is in use. For debugging on the
default localhost port the easiest thing is to use `--rc --rc-no-auth`
(but see the warning about using `--rc-no-auth` on a non-loopback
bind).
To use these, first [install go](https://golang.org/doc/install).
### Debugging memory use
+5 -2
View File
@@ -126,8 +126,11 @@ func newServer(ctx context.Context, opt *rc.Options, mux *http.ServeMux) (*Serve
middleware.SetHeader("Server", "rclone/"+fs.Version),
)
// Add the debug handler which is installed in the default mux
router.Handle("/debug/pprof/*", mux)
// Add the debug handler which is installed in the default mux.
// Only do this if auth is enabled.
if s.noAuth || s.server.UsingAuth() {
router.Handle("/debug/pprof/*", mux)
}
// FIXME split these up into individual functions
router.Get("/*", s.handler)
+43 -8
View File
@@ -977,27 +977,62 @@ func TestRCAsync(t *testing.T) {
testServer(t, tests, &opt)
}
// Check the debug handlers are attached
// The debug/pprof handlers expose the process command line, heap and
// goroutine dumps, so require auth.
func TestRCDebug(t *testing.T) {
tests := []testRun{{
index := testRun{
Name: "index",
URL: "debug/pprof/",
Method: "GET",
ContentType: "text/html",
Status: http.StatusOK,
Contains: regexp.MustCompile(`Types of profiles available`),
}, {
}
goroutines := testRun{
Name: "goroutines",
URL: "debug/pprof/goroutine?debug=1",
Method: "GET",
ContentType: "text/html",
Status: http.StatusOK,
Contains: regexp.MustCompile(`goroutine profile`),
}}
opt := newTestOpt()
opt.Serve = true
opt.Files = ""
testServer(t, tests, &opt)
}
// With --rc-no-auth the debug handlers are attached and open
t.Run("NoAuth", func(t *testing.T) {
opt := newTestOpt()
opt.Serve = true
opt.Files = ""
opt.NoAuth = true
testServer(t, []testRun{index, goroutines}, &opt)
})
// With auth configured the debug handlers are attached but require credentials
t.Run("WithAuth", func(t *testing.T) {
authed := index
authed.User, authed.Pass = "user", "pass"
unauthed := index
unauthed.Name = "index-no-credentials"
unauthed.Status = http.StatusUnauthorized
unauthed.Contains = regexp.MustCompile(`Unauthorized`)
opt := newTestOpt()
opt.Serve = true
opt.Files = ""
opt.Auth.BasicUser = "user"
opt.Auth.BasicPass = "pass"
testServer(t, []testRun{authed, unauthed}, &opt)
})
// On a default unauthenticated server the debug handlers must not be
// mounted, so they bypass nothing - they simply aren't there
t.Run("FailClosed", func(t *testing.T) {
blocked := index
blocked.Status = http.StatusNotFound
blocked.Contains = regexp.MustCompile(`Not Found`)
opt := newTestOpt()
opt.Serve = true
opt.Files = ""
testServer(t, []testRun{blocked}, &opt)
})
}
func TestServeModTime(t *testing.T) {