From: Damien Neil Date: Tue, 4 Mar 2025 23:20:28 +0000 (-0800) Subject: [release-branch.go1.24] net/http: don't modify caller's tls.Config.NextProtos X-Git-Tag: go1.24.2~12 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=fd29397dca4f393a8a9ce6e9c952fa292e07a7f0;p=gostls13.git [release-branch.go1.24] net/http: don't modify caller's tls.Config.NextProtos Clone the input slice before adjusting NextProtos to add or remove "http/1.1" and "h2" entries, so as not to modify a slice that the caller might be using. (We clone the tls.Config that contains the slice, but that's a shallow clone.) For #72100 Fixes #72103 Change-Id: I9f228b8fb6f6f2ca5023179ec114929c002dbda9 Reviewed-on: https://go-review.googlesource.com/c/go/+/654875 Reviewed-by: Dmitri Shuralyov Reviewed-by: Brad Fitzpatrick Auto-Submit: Damien Neil Reviewed-by: Jonathan Amsterdam LUCI-TryBot-Result: Go LUCI Reviewed-on: https://go-review.googlesource.com/c/go/+/657215 --- diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go index 0c46b1ecc3..517cfdd92e 100644 --- a/src/net/http/serve_test.go +++ b/src/net/http/serve_test.go @@ -13,6 +13,7 @@ import ( "compress/zlib" "context" "crypto/tls" + "crypto/x509" "encoding/json" "errors" "fmt" @@ -7303,3 +7304,71 @@ func testServerReadAfterHandlerAbort100Continue(t *testing.T, mode testMode) { readyc <- struct{}{} // server starts reading from the request body readyc <- struct{}{} // server finishes reading from the request body } + +// Issue #72100: Verify that we don't modify the caller's TLS.Config.NextProtos slice. +func TestServerTLSNextProtos(t *testing.T) { + run(t, testServerTLSNextProtos, []testMode{https1Mode, http2Mode}) +} +func testServerTLSNextProtos(t *testing.T, mode testMode) { + CondSkipHTTP2(t) + + cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey) + if err != nil { + t.Fatal(err) + } + leafCert, err := x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + t.Fatal(err) + } + certpool := x509.NewCertPool() + certpool.AddCert(leafCert) + + protos := new(Protocols) + switch mode { + case https1Mode: + protos.SetHTTP1(true) + case http2Mode: + protos.SetHTTP2(true) + } + + wantNextProtos := []string{"http/1.1", "h2", "other"} + nextProtos := slices.Clone(wantNextProtos) + + // We don't use httptest here because it overrides the tls.Config. + srv := &Server{ + TLSConfig: &tls.Config{ + Certificates: []tls.Certificate{cert}, + NextProtos: nextProtos, + }, + Handler: HandlerFunc(func(w ResponseWriter, req *Request) {}), + Protocols: protos, + } + tr := &Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: certpool, + NextProtos: nextProtos, + }, + Protocols: protos, + } + + listener := newLocalListener(t) + srvc := make(chan error, 1) + go func() { + srvc <- srv.ServeTLS(listener, "", "") + }() + t.Cleanup(func() { + srv.Close() + <-srvc + }) + + client := &Client{Transport: tr} + resp, err := client.Get("https://" + listener.Addr().String()) + if err != nil { + t.Fatal(err) + } + resp.Body.Close() + + if !slices.Equal(nextProtos, wantNextProtos) { + t.Fatalf("after running test: original NextProtos slice = %v, want %v", nextProtos, wantNextProtos) + } +} diff --git a/src/net/http/server.go b/src/net/http/server.go index 1e8e1437d2..ad2edf99a2 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -3521,6 +3521,12 @@ func (s *Server) protocols() Protocols { // adjustNextProtos adds or removes "http/1.1" and "h2" entries from // a tls.Config.NextProtos list, according to the set of protocols in protos. func adjustNextProtos(nextProtos []string, protos Protocols) []string { + // Make a copy of NextProtos since it might be shared with some other tls.Config. + // (tls.Config.Clone doesn't do a deep copy.) + // + // We could avoid an allocation in the common case by checking to see if the slice + // is already in order, but this is just one small allocation per connection. + nextProtos = slices.Clone(nextProtos) var have Protocols nextProtos = slices.DeleteFunc(nextProtos, func(s string) bool { switch s {