Skip to content

net/http: go1.24 breaks compatibility by modifying in-place the tls.Config{NextProtos} #72100

Closed
@dethi

Description

@dethi

Go version

go version go1.24.0 darwin/arm64

Output of go env in your module/workspace:

AR='ar'
CC='cc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='c++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/thibault/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/thibault/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/c5/msv003gn2s9dqdr8twx25br80000gq/T/go-build3704636302=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/thibault/go/src/repo/go.mod'
GOMODCACHE='/Users/thibault/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/thibault/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/homebrew/Cellar/go/1.24.0/libexec'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/thibault/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.24.0/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.24.0'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

Setup:

$ openssl req -new -subj "/C=US/ST=Utah/CN=localhost" -newkey rsa:2048 -nodes -keyout localhost.key -out localhost.csr
$ openssl x509 -req -days 365 -in localhost.csr -signkey localhost.key -out localhost.crt

Run the following program locally: https://go.dev/play/p/sqxPDLLHzwX

Simulate a client connecting on the gRPC port with TLS1.3, ALPN H2:

$ openssl s_client -connect localhost:8446 -tls1_3 -alpn h2

Observe the following error:

Connecting to 127.0.0.1
CONNECTED(00000005)
40C8F9F701000000:error:0A000460:SSL routines:ssl3_read_bytes:tlsv1 alert no application protocol:ssl/record/rec_layer_s3.c:908:SSL alert number 120

Repeat the same test using Go 1.23 or older, TLS handshake will happen successfully.

What did you see happen?

net/http server added Protocols to control the allowed protocols. The server modifies the TLS Config NextProtos based on this field (func adjustNextProtos).

This is a different behaviour than before and cause issue when two servers (in my example net/http and gRPC server) utilise the same TLS Config. The TLS Config is soft-cloned in both server, but net/http use slices.DeleteFunc which delete in-place values from the slice, thus reflecting the same change on the gRPC server.

In the example I shared, h2 is disabled on the net/http server (using the old way), and so adjustNextProtos removes it from the TLS Config NextProtos. On the other side, gRPC server append h2 if it's not already present. Because the TLS Config is a shallow clone, the outcome is racy.

What did you expect to see?

The expectation is that the gRPC server has h2 set in the NextProtos and so that the ALPN negotiation works correctly. This was the case up until Go 1.24.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugReportIssues describing a possible bug in the Go implementation.FixPendingIssues that have a fix which has not yet been reviewed or submitted.NeedsFixThe path to resolution is known, but the work has not been done.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions