Skip to content

Commit fd29397

Browse files
neildcherrymui
authored andcommitted
[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 <dmitshur@google.com> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Jonathan Amsterdam <jba@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-on: https://go-review.googlesource.com/c/go/+/657215
1 parent 4524009 commit fd29397

File tree

2 files changed

+75
-0
lines changed

2 files changed

+75
-0
lines changed

src/net/http/serve_test.go

+69
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"compress/zlib"
1414
"context"
1515
"crypto/tls"
16+
"crypto/x509"
1617
"encoding/json"
1718
"errors"
1819
"fmt"
@@ -7303,3 +7304,71 @@ func testServerReadAfterHandlerAbort100Continue(t *testing.T, mode testMode) {
73037304
readyc <- struct{}{} // server starts reading from the request body
73047305
readyc <- struct{}{} // server finishes reading from the request body
73057306
}
7307+
7308+
// Issue #72100: Verify that we don't modify the caller's TLS.Config.NextProtos slice.
7309+
func TestServerTLSNextProtos(t *testing.T) {
7310+
run(t, testServerTLSNextProtos, []testMode{https1Mode, http2Mode})
7311+
}
7312+
func testServerTLSNextProtos(t *testing.T, mode testMode) {
7313+
CondSkipHTTP2(t)
7314+
7315+
cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey)
7316+
if err != nil {
7317+
t.Fatal(err)
7318+
}
7319+
leafCert, err := x509.ParseCertificate(cert.Certificate[0])
7320+
if err != nil {
7321+
t.Fatal(err)
7322+
}
7323+
certpool := x509.NewCertPool()
7324+
certpool.AddCert(leafCert)
7325+
7326+
protos := new(Protocols)
7327+
switch mode {
7328+
case https1Mode:
7329+
protos.SetHTTP1(true)
7330+
case http2Mode:
7331+
protos.SetHTTP2(true)
7332+
}
7333+
7334+
wantNextProtos := []string{"http/1.1", "h2", "other"}
7335+
nextProtos := slices.Clone(wantNextProtos)
7336+
7337+
// We don't use httptest here because it overrides the tls.Config.
7338+
srv := &Server{
7339+
TLSConfig: &tls.Config{
7340+
Certificates: []tls.Certificate{cert},
7341+
NextProtos: nextProtos,
7342+
},
7343+
Handler: HandlerFunc(func(w ResponseWriter, req *Request) {}),
7344+
Protocols: protos,
7345+
}
7346+
tr := &Transport{
7347+
TLSClientConfig: &tls.Config{
7348+
RootCAs: certpool,
7349+
NextProtos: nextProtos,
7350+
},
7351+
Protocols: protos,
7352+
}
7353+
7354+
listener := newLocalListener(t)
7355+
srvc := make(chan error, 1)
7356+
go func() {
7357+
srvc <- srv.ServeTLS(listener, "", "")
7358+
}()
7359+
t.Cleanup(func() {
7360+
srv.Close()
7361+
<-srvc
7362+
})
7363+
7364+
client := &Client{Transport: tr}
7365+
resp, err := client.Get("https://" + listener.Addr().String())
7366+
if err != nil {
7367+
t.Fatal(err)
7368+
}
7369+
resp.Body.Close()
7370+
7371+
if !slices.Equal(nextProtos, wantNextProtos) {
7372+
t.Fatalf("after running test: original NextProtos slice = %v, want %v", nextProtos, wantNextProtos)
7373+
}
7374+
}

src/net/http/server.go

+6
Original file line numberDiff line numberDiff line change
@@ -3521,6 +3521,12 @@ func (s *Server) protocols() Protocols {
35213521
// adjustNextProtos adds or removes "http/1.1" and "h2" entries from
35223522
// a tls.Config.NextProtos list, according to the set of protocols in protos.
35233523
func adjustNextProtos(nextProtos []string, protos Protocols) []string {
3524+
// Make a copy of NextProtos since it might be shared with some other tls.Config.
3525+
// (tls.Config.Clone doesn't do a deep copy.)
3526+
//
3527+
// We could avoid an allocation in the common case by checking to see if the slice
3528+
// is already in order, but this is just one small allocation per connection.
3529+
nextProtos = slices.Clone(nextProtos)
35243530
var have Protocols
35253531
nextProtos = slices.DeleteFunc(nextProtos, func(s string) bool {
35263532
switch s {

0 commit comments

Comments
 (0)