Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions internal/driver/webui.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options, d
"/source": http.HandlerFunc(ui.source),
"/peek": http.HandlerFunc(ui.peek),
"/flamegraph": http.HandlerFunc(ui.stackView),
"/saveconfig": http.HandlerFunc(ui.saveConfig),
"/deleteconfig": http.HandlerFunc(ui.deleteConfig),
"/saveconfig": configHandler(http.MethodPost, ui.saveConfig),
"/deleteconfig": configHandler(http.MethodDelete, ui.deleteConfig),
"/download": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/vnd.google.protobuf+gzip")
w.Header().Set("Content-Disposition", "attachment;filename=profile.pb.gz")
Expand Down Expand Up @@ -449,6 +449,21 @@ func (ui *webInterface) peek(w http.ResponseWriter, req *http.Request) {
})
}

// configHandler wraps a handler that modifies the settings file. It only
// accepts the given method and rejects cross-origin browser requests, so
// that other web sites open in the user's browser cannot change or delete
// saved configurations.
func configHandler(method string, h http.HandlerFunc) http.Handler {
return http.NewCrossOriginProtection().Handler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The code uses http.NewCrossOriginProtection(), but the http package imported in this file is "net/http" from the standard library, which does not contain this function. This will cause a compilation error.\n\nTo fix this, you need to import the custom http package from github.com/google/pprof/internal/http under a different name (e.g., pprofhttp) to avoid conflicting with "net/http", and then use pprofhttp.NewCrossOriginProtection() here.\n\nFor example, add this to your imports:\ngo\npprofhttp \"github.com/google/pprof/internal/http\"\n

\treturn pprofhttp.NewCrossOriginProtection().Handler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is a false positive. http.NewCrossOriginProtection is in the standard net/http since Go 1.25, and go.mod already requires 1.26. There is no internal/http package in this repo. The branch builds and TestConfigHandlersRejectCrossOrigin passes.

if req.Method != method {
w.Header().Set("Allow", method)
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
h(w, req)
}))
}

// saveConfig saves URL configuration.
func (ui *webInterface) saveConfig(w http.ResponseWriter, req *http.Request) {
if err := setConfig(ui.settingsFile, *req.URL); err != nil {
Expand Down
60 changes: 60 additions & 0 deletions internal/driver/webui_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,66 @@ func TestWebInterface(t *testing.T) {
wg.Wait()
}

func TestConfigHandlersRejectCrossOrigin(t *testing.T) {
// Keep the settings file out of the user's config directory.
dir := t.TempDir()
for _, env := range []string{"HOME", "home", "XDG_CONFIG_HOME", "AppData"} {
t.Setenv(env, dir)
}
fname, err := settingsFileName()
if err != nil {
t.Fatal(err)
}
server := makeTestServer(t, makeFakeProfile())

hasConfig := func(name string) bool {
s, err := readSettings(fname)
if err != nil {
t.Fatal(err)
}
for _, c := range s.Configs {
if c.Name == name {
return true
}
}
return false
}

for _, c := range []struct {
desc, method, path string
header map[string]string
wantCode int
wantConfig bool
}{
{"get save", http.MethodGet, "/saveconfig?config=c1", nil, http.StatusMethodNotAllowed, false},
{"cross-site save", http.MethodPost, "/saveconfig?config=c1", map[string]string{"Sec-Fetch-Site": "cross-site"}, http.StatusForbidden, false},
{"cross-origin save", http.MethodPost, "/saveconfig?config=c1", map[string]string{"Origin": "http://example.com"}, http.StatusForbidden, false},
{"same-origin save", http.MethodPost, "/saveconfig?config=c1", map[string]string{"Sec-Fetch-Site": "same-origin"}, http.StatusOK, true},
{"get delete", http.MethodGet, "/deleteconfig?config=c1", nil, http.StatusMethodNotAllowed, true},
{"cross-site delete", http.MethodDelete, "/deleteconfig?config=c1", map[string]string{"Sec-Fetch-Site": "cross-site"}, http.StatusForbidden, true},
{"same-origin delete", http.MethodDelete, "/deleteconfig?config=c1", map[string]string{"Sec-Fetch-Site": "same-origin"}, http.StatusOK, false},
} {
req, err := http.NewRequest(c.method, server.URL+c.path, nil)
if err != nil {
t.Fatal(err)
}
for k, v := range c.header {
req.Header.Set(k, v)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
res.Body.Close()
if res.StatusCode != c.wantCode {
t.Errorf("%s: got status %d, want %d", c.desc, res.StatusCode, c.wantCode)
}
if got := hasConfig("c1"); got != c.wantConfig {
t.Errorf("%s: config saved = %v, want %v", c.desc, got, c.wantConfig)
}
}
}

// Implement fake object file support.

const addrBase = 0x1000
Expand Down
Loading