0001-client-define-a-dummy-hostname-to-use-for-local-conn.patch 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. From 8ced4331e5e3a6760465a8ce2bd42c66d3232c96 Mon Sep 17 00:00:00 2001
  2. From: Sebastiaan van Stijn <github@gone.nl>
  3. Date: Wed, 12 Jul 2023 14:15:38 +0200
  4. Subject: [PATCH] client: define a "dummy" hostname to use for local
  5. connections
  6. Go 1.20.6 and 1.19.11 include a security check of the http Host header:
  7. https://github.com/golang/go/issues/60374
  8. This is a backported patch to fix this issue.
  9. Issue: https://github.com/moby/moby/issues/45935
  10. Upstream PR: https://github.com/moby/moby/pull/45942
  11. The upstream PR has been merged and will be included in v24.0.5.
  12. Signed-off-by: Christian Stewart <christian@aperture.us>
  13. ---
  14. For local communications (npipe://, unix://), the hostname is not used,
  15. but we need valid and meaningful hostname.
  16. The current code used the client's `addr` as hostname in some cases, which
  17. could contain the path for the unix-socket (`/var/run/docker.sock`), which
  18. gets rejected by go1.20.6 and go1.19.11 because of a security fix for
  19. [CVE-2023-29406 ][1], which was implemented in https://go.dev/issue/60374.
  20. Prior versions go Go would clean the host header, and strip slashes in the
  21. process, but go1.20.6 and go1.19.11 no longer do, and reject the host
  22. header.
  23. This patch introduces a `DummyHost` const, and uses this dummy host for
  24. cases where we don't need an actual hostname.
  25. Before this patch (using go1.20.6):
  26. make GO_VERSION=1.20.6 TEST_FILTER=TestAttach test-integration
  27. === RUN TestAttachWithTTY
  28. attach_test.go:46: assertion failed: error is not nil: http: invalid Host header
  29. --- FAIL: TestAttachWithTTY (0.11s)
  30. === RUN TestAttachWithoutTTy
  31. attach_test.go:46: assertion failed: error is not nil: http: invalid Host header
  32. --- FAIL: TestAttachWithoutTTy (0.02s)
  33. FAIL
  34. With this patch applied:
  35. make GO_VERSION=1.20.6 TEST_FILTER=TestAttach test-integration
  36. INFO: Testing against a local daemon
  37. === RUN TestAttachWithTTY
  38. --- PASS: TestAttachWithTTY (0.12s)
  39. === RUN TestAttachWithoutTTy
  40. --- PASS: TestAttachWithoutTTy (0.02s)
  41. PASS
  42. [1]: https://github.com/advisories/GHSA-f8f7-69v5-w4vx
  43. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
  44. (cherry picked from commit 92975f0c11f0566cc3c36659f5e3bb9faf5cb176)
  45. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
  46. ---
  47. client/client.go | 30 ++++++++++++++++++++++++++++++
  48. client/hijack.go | 6 +++++-
  49. client/request.go | 10 ++++------
  50. client/request_test.go | 4 ++--
  51. 4 files changed, 41 insertions(+), 9 deletions(-)
  52. diff --git a/client/client.go b/client/client.go
  53. index 1c081a51ae..54fa36cca8 100644
  54. --- a/client/client.go
  55. +++ b/client/client.go
  56. @@ -56,6 +56,36 @@ import (
  57. "github.com/pkg/errors"
  58. )
  59. +// DummyHost is a hostname used for local communication.
  60. +//
  61. +// It acts as a valid formatted hostname for local connections (such as "unix://"
  62. +// or "npipe://") which do not require a hostname. It should never be resolved,
  63. +// but uses the special-purpose ".localhost" TLD (as defined in [RFC 2606, Section 2]
  64. +// and [RFC 6761, Section 6.3]).
  65. +//
  66. +// [RFC 7230, Section 5.4] defines that an empty header must be used for such
  67. +// cases:
  68. +//
  69. +// If the authority component is missing or undefined for the target URI,
  70. +// then a client MUST send a Host header field with an empty field-value.
  71. +//
  72. +// However, [Go stdlib] enforces the semantics of HTTP(S) over TCP, does not
  73. +// allow an empty header to be used, and requires req.URL.Scheme to be either
  74. +// "http" or "https".
  75. +//
  76. +// For further details, refer to:
  77. +//
  78. +// - https://github.com/docker/engine-api/issues/189
  79. +// - https://github.com/golang/go/issues/13624
  80. +// - https://github.com/golang/go/issues/61076
  81. +// - https://github.com/moby/moby/issues/45935
  82. +//
  83. +// [RFC 2606, Section 2]: https://www.rfc-editor.org/rfc/rfc2606.html#section-2
  84. +// [RFC 6761, Section 6.3]: https://www.rfc-editor.org/rfc/rfc6761#section-6.3
  85. +// [RFC 7230, Section 5.4]: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
  86. +// [Go stdlib]: https://github.com/golang/go/blob/6244b1946bc2101b01955468f1be502dbadd6807/src/net/http/transport.go#L558-L569
  87. +const DummyHost = "api.moby.localhost"
  88. +
  89. // ErrRedirect is the error returned by checkRedirect when the request is non-GET.
  90. var ErrRedirect = errors.New("unexpected redirect in response")
  91. diff --git a/client/hijack.go b/client/hijack.go
  92. index 6bdacab10a..4dcaaca4c5 100644
  93. --- a/client/hijack.go
  94. +++ b/client/hijack.go
  95. @@ -64,7 +64,11 @@ func fallbackDial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
  96. }
  97. func (cli *Client) setupHijackConn(ctx context.Context, req *http.Request, proto string) (net.Conn, string, error) {
  98. - req.Host = cli.addr
  99. + req.URL.Host = cli.addr
  100. + if cli.proto == "unix" || cli.proto == "npipe" {
  101. + // Override host header for non-tcp connections.
  102. + req.Host = DummyHost
  103. + }
  104. req.Header.Set("Connection", "Upgrade")
  105. req.Header.Set("Upgrade", proto)
  106. diff --git a/client/request.go b/client/request.go
  107. index c799095c12..bcedcf3bd9 100644
  108. --- a/client/request.go
  109. +++ b/client/request.go
  110. @@ -96,16 +96,14 @@ func (cli *Client) buildRequest(method, path string, body io.Reader, headers hea
  111. return nil, err
  112. }
  113. req = cli.addHeaders(req, headers)
  114. + req.URL.Scheme = cli.scheme
  115. + req.URL.Host = cli.addr
  116. if cli.proto == "unix" || cli.proto == "npipe" {
  117. - // For local communications, it doesn't matter what the host is. We just
  118. - // need a valid and meaningful host name. (See #189)
  119. - req.Host = "docker"
  120. + // Override host header for non-tcp connections.
  121. + req.Host = DummyHost
  122. }
  123. - req.URL.Host = cli.addr
  124. - req.URL.Scheme = cli.scheme
  125. -
  126. if expectedPayload && req.Header.Get("Content-Type") == "" {
  127. req.Header.Set("Content-Type", "text/plain")
  128. }
  129. diff --git a/client/request_test.go b/client/request_test.go
  130. index 6e5a6e81f2..50b09d954c 100644
  131. --- a/client/request_test.go
  132. +++ b/client/request_test.go
  133. @@ -29,12 +29,12 @@ func TestSetHostHeader(t *testing.T) {
  134. }{
  135. {
  136. "unix:///var/run/docker.sock",
  137. - "docker",
  138. + DummyHost,
  139. "/var/run/docker.sock",
  140. },
  141. {
  142. "npipe:////./pipe/docker_engine",
  143. - "docker",
  144. + DummyHost,
  145. "//./pipe/docker_engine",
  146. },
  147. {
  148. --
  149. 2.41.0