go: net/http: go 1.20.6 host validation breaks setting Host to a unix socket address
What version of Go are you using (go version
)?
$ go version go version go1.20.6 linux/amd64
Does this issue reproduce with the latest release?
yes
What operating system and processor architecture are you using (go env
)?
can include this if requested
What did you do?
Create a request where the Host header is a unix socket address. Here’s some runnable sample code:
package main
import (
"context"
"log"
"net"
"net/http"
"os"
"time"
)
func handler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(204)
}
func main() {
_ = os.Remove("/tmp/mysocket.sock")
socket, err := net.Listen("unix", "/tmp/mysocket.sock")
if err != nil {
panic(err)
}
s := &http.Server{
Handler: http.HandlerFunc(handler),
}
go func() {
c := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", "/tmp/mysocket.sock")
},
},
}
req, _ := http.NewRequest("GET", "http://localhost.com", nil)
req.Host = "unix:///tmp/mysocket.sock"
resp, err := c.Do(req)
if err != nil {
log.Fatal(err)
}
log.Fatal("STATUS ", resp.Status)
}()
log.Fatal(s.Serve(socket))
}
What did you expect to see?
In Go 1.20.5, this prints:
2023/07/18 20:19:59 STATUS 204 No Content
exit status 1
What did you see instead?
In Go 1.20.6, this prints:
2023/07/18 20:20:29 Get "http://localhost.com": http: invalid Host header
exit status 1
Additional Info
Related discussion: https://github.com/golang/go/issues/60374
My understanding is that this is an intentional change, to fix a security bug where the Host header contains newline characters. Here’s the CVE: https://nvd.nist.gov/vuln/detail/CVE-2023-29406
Unforuntately, this also breaks CLIs in the Go ecosystem that set the Host header to a unix socket, for example : https://github.com/moby/moby/issues/45935
Many projects silently upgrade from Go 1.20.5 -> 1.20.6, so we’re starting to see this change break tons of projects in the go ecosystem.
My humble request is that the security fix on the Go 1.20 release branch could be more narrowly targeted at the security issue, and allow this Host header format, to unbreak the ecosystem. The Go 1.21 release line can more safely rollout the backwards-incompatible part of the change.
About this issue
- Original URL
- State: closed
- Created a year ago
- Reactions: 26
- Comments: 29 (12 by maintainers)
Commits related to this issue
- Pin Go to 1.20.5 to work around testcontainers/Go breakage (#40) Pin Go to version `1.20.5` to fix [testcontainer](https://golang.testcontainers.org/) tests. Go `1.20.6` introduced changes in `Ho... — committed to xataio/pgroll by andrew-farries a year ago
- Fixing the tests because of https://github.com/golang/go/issues/61431 — committed to OctopusSolutionsEngineering/OctopusTerraformExport by mcasperson a year ago
- fix(caddyManager): add host for caddy admin unix socket address Starting from Go 1.20.6 (net/http: go 1.20.6 host validation breaks setting Host to a unix socket address golang/go#61431) calling uni... — committed to xiayesuifeng/gopanel by xiayesuifeng a year ago
- Fix for bug https://github.com/golang/go/issues/61431 — committed to OctopusDeployLabs/terraform-provider-octopusdeploy by mcasperson a year ago
- Adding support for pod auth (#534) * Adding support for pod auth * Bumped dependencies and exposed more pod auth fields * Tests now pass * Fix for bug https://github.com/golang/go/issues/614... — committed to OctopusDeployLabs/terraform-provider-octopusdeploy by mcasperson a year ago
- [release-branch.go1.21] net/http: permit requests with invalid Host headers Historically, the Transport has silently truncated invalid Host headers at the first '/' or ' ' character. CL 506996 change... — committed to golang/go by neild a year ago
- [release-branch.go1.20] net/http: permit requests with invalid Host headers Historically, the Transport has silently truncated invalid Host headers at the first '/' or ' ' character. CL 506996 change... — committed to golang/go by neild a year ago
- [release-branch.go1.19] net/http: permit requests with invalid Host headers Historically, the Transport has silently truncated invalid Host headers at the first '/' or ' ' character. CL 506996 change... — committed to golang/go by neild a year ago
That’s my thought as well. It was getting this on a patch release that really caused the pain. We expect some things to break during major version updates.
I feel that discussing if this is allowed or not is not the most important aspect. As it worked before, it created an API and created expectations (see relevant xkcd). The fallout it created relates to a lot of docker-based technologies which make heavy use of sockets.
Examples I could find/affect me are:
https://github.com/k3d-io/k3d/issues/1321 https://github.com/testcontainers/testcontainers-go/issues/1359
As a lot of people are scrambling to fix the fallout, I think the good outcome will be a better understanding of potential downstream issues of „overcorrections“ and a better understanding of the standards in any case.
If we are making HTTP requests to a unix socket, with this change, how are we supposed to stay compliant?
RFC 2616, Section 14.26:
Since there is no Internet host name, the Host header “MUST” be given with an empty value.
Previously,
" "
(space) worked by sheer luck and allowed us to stay compliant.Now if we set the Host header to a non-empty value, we are not only violating the RFC but we also make CORS validation & DNS rebinding mitigation tricky, since those checks require accurate hostnames, if any. So if we invent one like
localhost
orfoo.local
, we risk enabling an CORS breach or DNS rebinding attack.I don’t know that we can use an IP address like 127.0.0.1 or ::1 because we don’t know which, if any, IP versions a host supports (hence their use of Unix sockets).
Anyone know if there’s something I’m missing/overlooking about this patch so that we can still achieve our objective?
https://go.dev/cl/511155 changes the transport to send an empty Host header when the host is invalid, except in the case of a request sent to a proxy. Returning an error seems more useful than sending a destination-free request to a proxy. Sending an empty Host offers less potential for request smuggling than truncating at the first invalid character.
Are there any scenarios that I’m missing that this won’t cover?
DNS rebinding mitigation does, which is also important; but all the same, we need a host (even if it’s used in the Origin header), if we cannot leave it blank.
For docker: docker 24.0.5 was just released and should work with go1.20.6
I suggest that:
Host
string as long as it is valid to send in a header) for Go 1.22, guarded by aGODEBUG
and enabled by default only at Go 1.22 and above.(The “Go 1.22 and above” part can be implemented by adding an entry to
internal/godebugs/table.go
withChanged: 22
.)I don’t think we need a GODEBUG; we can reduce the validation of outgoing Host headers to just checking that it’s a valid header value, not that it’s a valid Host header specifically. That’s enough to ensure the outbound request is, at worst, something the server will reject.
I would say any client connecting to an HTTP server over a unix socket. So to answer the question, possibly given the breadth of use and the fact that it uses http with UDS. But really Docker is just trying to set a meaningful value AND not setting
req.Host
does not work.https://go.dev/play/p/JAHc0RFCMRy
Per #56986, the new validation should probably at least have a
GODEBUG
setting that allows (part or all of) the old behavior for incremental migration.This looks like misusing the
Host
header for what is arguably proxy/dialer functionality?Aside: the file transport from
net/http.NewFileTransport
processes requests with URLs likefile:///tmp/my.sock
with an emptyHost
.Not entirely sure how common this behavior is, but I will note that RFC 2616 Section 14.23 does seem to explicitly disallow this: