From bff621d5399e5ff2930d21f403bb2f274febd2e4 Mon Sep 17 00:00:00 2001 From: Ming Liu Date: Mon, 12 Feb 2024 13:15:11 +0100 Subject: go: add a complementary fix for CVE-2023-29406 The original CVE-2023-29406.patch is not complete, causing docker failures at runtime, backport a complementary fix from golang upstream. Signed-off-by: Ming Liu Signed-off-by: Steve Sakoman --- meta/recipes-devtools/go/go-1.14.inc | 3 +- .../go/go-1.14/CVE-2023-29406-1.patch | 212 +++++++++++++++++++++ .../go/go-1.14/CVE-2023-29406-2.patch | 114 +++++++++++ .../go/go-1.14/CVE-2023-29406.patch | 212 --------------------- 4 files changed, 328 insertions(+), 213 deletions(-) create mode 100644 meta/recipes-devtools/go/go-1.14/CVE-2023-29406-1.patch create mode 100644 meta/recipes-devtools/go/go-1.14/CVE-2023-29406-2.patch delete mode 100644 meta/recipes-devtools/go/go-1.14/CVE-2023-29406.patch (limited to 'meta') diff --git a/meta/recipes-devtools/go/go-1.14.inc b/meta/recipes-devtools/go/go-1.14.inc index 42a9ac8435..4fbf9d7590 100644 --- a/meta/recipes-devtools/go/go-1.14.inc +++ b/meta/recipes-devtools/go/go-1.14.inc @@ -71,7 +71,8 @@ SRC_URI += "\ file://CVE-2023-29402.patch \ file://CVE-2023-29404.patch \ file://CVE-2023-29400.patch \ - file://CVE-2023-29406.patch \ + file://CVE-2023-29406-1.patch \ + file://CVE-2023-29406-2.patch \ file://CVE-2023-29409.patch \ file://CVE-2022-41725-pre1.patch \ file://CVE-2022-41725-pre2.patch \ diff --git a/meta/recipes-devtools/go/go-1.14/CVE-2023-29406-1.patch b/meta/recipes-devtools/go/go-1.14/CVE-2023-29406-1.patch new file mode 100644 index 0000000000..080def4682 --- /dev/null +++ b/meta/recipes-devtools/go/go-1.14/CVE-2023-29406-1.patch @@ -0,0 +1,212 @@ +From 5fa6923b1ea891400153d04ddf1545e23b40041b Mon Sep 17 00:00:00 2001 +From: Damien Neil +Date: Wed, 28 Jun 2023 13:20:08 -0700 +Subject: [PATCH] [release-branch.go1.19] net/http: validate Host header before + sending + +Verify that the Host header we send is valid. +Avoids surprising behavior such as a Host of "go.dev\r\nX-Evil:oops" +adding an X-Evil header to HTTP/1 requests. + +Add a test, skip the test for HTTP/2. HTTP/2 is not vulnerable to +header injection in the way HTTP/1 is, but x/net/http2 doesn't validate +the header and will go into a retry loop when the server rejects it. +CL 506995 adds the necessary validation to x/net/http2. + +Updates #60374 +Fixes #61075 +For CVE-2023-29406 + +Change-Id: I05cb6866a9bead043101954dfded199258c6dd04 +Reviewed-on: https://go-review.googlesource.com/c/go/+/506996 +Reviewed-by: Tatiana Bradley +TryBot-Result: Gopher Robot +Run-TryBot: Damien Neil +(cherry picked from commit 499458f7ca04087958987a33c2703c3ef03e27e2) +Reviewed-on: https://go-review.googlesource.com/c/go/+/507358 +Run-TryBot: Tatiana Bradley +Reviewed-by: Roland Shoemaker + +Upstream-Status: Backport [https://github.com/golang/go/commit/5fa6923b1ea891400153d04ddf1545e23b40041b] +CVE: CVE-2023-29406 +Signed-off-by: Vivek Kumbhar +--- + src/net/http/http_test.go | 29 --------------------- + src/net/http/request.go | 47 ++++++++-------------------------- + src/net/http/request_test.go | 11 ++------ + src/net/http/transport_test.go | 18 +++++++++++++ + 4 files changed, 31 insertions(+), 74 deletions(-) + +diff --git a/src/net/http/http_test.go b/src/net/http/http_test.go +index f4ea52d..ea38cb4 100644 +--- a/src/net/http/http_test.go ++++ b/src/net/http/http_test.go +@@ -49,35 +49,6 @@ func TestForeachHeaderElement(t *testing.T) { + } + } + +-func TestCleanHost(t *testing.T) { +- tests := []struct { +- in, want string +- }{ +- {"www.google.com", "www.google.com"}, +- {"www.google.com foo", "www.google.com"}, +- {"www.google.com/foo", "www.google.com"}, +- {" first character is a space", ""}, +- {"[1::6]:8080", "[1::6]:8080"}, +- +- // Punycode: +- {"гофер.рф/foo", "xn--c1ae0ajs.xn--p1ai"}, +- {"bücher.de", "xn--bcher-kva.de"}, +- {"bücher.de:8080", "xn--bcher-kva.de:8080"}, +- // Verify we convert to lowercase before punycode: +- {"BÜCHER.de", "xn--bcher-kva.de"}, +- {"BÜCHER.de:8080", "xn--bcher-kva.de:8080"}, +- // Verify we normalize to NFC before punycode: +- {"gophér.nfc", "xn--gophr-esa.nfc"}, // NFC input; no work needed +- {"goph\u0065\u0301r.nfd", "xn--gophr-esa.nfd"}, // NFD input +- } +- for _, tt := range tests { +- got := cleanHost(tt.in) +- if tt.want != got { +- t.Errorf("cleanHost(%q) = %q, want %q", tt.in, got, tt.want) +- } +- } +-} +- + // Test that cmd/go doesn't link in the HTTP server. + // + // This catches accidental dependencies between the HTTP transport and +diff --git a/src/net/http/request.go b/src/net/http/request.go +index cb2edd2..2706300 100644 +--- a/src/net/http/request.go ++++ b/src/net/http/request.go +@@ -18,7 +18,6 @@ import ( + "io/ioutil" + "mime" + "mime/multipart" +- "net" + "net/http/httptrace" + "net/textproto" + "net/url" +@@ -26,7 +25,8 @@ import ( + "strconv" + "strings" + "sync" +- ++ ++ "golang.org/x/net/http/httpguts" + "golang.org/x/net/idna" + ) + +@@ -557,12 +557,19 @@ func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitF + // is not given, use the host from the request URL. + // + // Clean the host, in case it arrives with unexpected stuff in it. +- host := cleanHost(r.Host) ++ host := r.Host + if host == "" { + if r.URL == nil { + return errMissingHost + } +- host = cleanHost(r.URL.Host) ++ host = r.URL.Host ++ } ++ host, err = httpguts.PunycodeHostPort(host) ++ if err != nil { ++ return err ++ } ++ if !httpguts.ValidHostHeader(host) { ++ return errors.New("http: invalid Host header") + } + + // According to RFC 6874, an HTTP client, proxy, or other +@@ -717,38 +724,6 @@ func idnaASCII(v string) (string, error) { + return idna.Lookup.ToASCII(v) + } + +-// cleanHost cleans up the host sent in request's Host header. +-// +-// It both strips anything after '/' or ' ', and puts the value +-// into Punycode form, if necessary. +-// +-// Ideally we'd clean the Host header according to the spec: +-// https://tools.ietf.org/html/rfc7230#section-5.4 (Host = uri-host [ ":" port ]") +-// https://tools.ietf.org/html/rfc7230#section-2.7 (uri-host -> rfc3986's host) +-// https://tools.ietf.org/html/rfc3986#section-3.2.2 (definition of host) +-// But practically, what we are trying to avoid is the situation in +-// issue 11206, where a malformed Host header used in the proxy context +-// would create a bad request. So it is enough to just truncate at the +-// first offending character. +-func cleanHost(in string) string { +- if i := strings.IndexAny(in, " /"); i != -1 { +- in = in[:i] +- } +- host, port, err := net.SplitHostPort(in) +- if err != nil { // input was just a host +- a, err := idnaASCII(in) +- if err != nil { +- return in // garbage in, garbage out +- } +- return a +- } +- a, err := idnaASCII(host) +- if err != nil { +- return in // garbage in, garbage out +- } +- return net.JoinHostPort(a, port) +-} +- + // removeZone removes IPv6 zone identifier from host. + // E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080" + func removeZone(host string) string { +diff --git a/src/net/http/request_test.go b/src/net/http/request_test.go +index 461d66e..0d417ff 100644 +--- a/src/net/http/request_test.go ++++ b/src/net/http/request_test.go +@@ -676,15 +676,8 @@ func TestRequestBadHost(t *testing.T) { + } + req.Host = "foo.com with spaces" + req.URL.Host = "foo.com with spaces" +- req.Write(logWrites{t, &got}) +- want := []string{ +- "GET /after HTTP/1.1\r\n", +- "Host: foo.com\r\n", +- "User-Agent: " + DefaultUserAgent + "\r\n", +- "\r\n", +- } +- if !reflect.DeepEqual(got, want) { +- t.Errorf("Writes = %q\n Want = %q", got, want) ++ if err := req.Write(logWrites{t, &got}); err == nil { ++ t.Errorf("Writing request with invalid Host: succeded, want error") + } + } + +diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go +index fa0c370..0afb6b9 100644 +--- a/src/net/http/transport_test.go ++++ b/src/net/http/transport_test.go +@@ -6249,3 +6249,21 @@ func TestIssue32441(t *testing.T) { + t.Error(err) + } + } ++ ++func TestRequestSanitization(t *testing.T) { ++ setParallel(t) ++ defer afterTest(t) ++ ++ ts := newClientServerTest(t, h1Mode, HandlerFunc(func(rw ResponseWriter, req *Request) { ++ if h, ok := req.Header["X-Evil"]; ok { ++ t.Errorf("request has X-Evil header: %q", h) ++ } ++ })).ts ++ defer ts.Close() ++ req, _ := NewRequest("GET", ts.URL, nil) ++ req.Host = "go.dev\r\nX-Evil:evil" ++ resp, _ := ts.Client().Do(req) ++ if resp != nil { ++ resp.Body.Close() ++ } ++} +-- +2.25.1 diff --git a/meta/recipes-devtools/go/go-1.14/CVE-2023-29406-2.patch b/meta/recipes-devtools/go/go-1.14/CVE-2023-29406-2.patch new file mode 100644 index 0000000000..637f46a537 --- /dev/null +++ b/meta/recipes-devtools/go/go-1.14/CVE-2023-29406-2.patch @@ -0,0 +1,114 @@ +From c08a5fa413a34111c9a37fd9e545de27ab0978b1 Mon Sep 17 00:00:00 2001 +From: Damien Neil +Date: Wed, 19 Jul 2023 10:30:46 -0700 +Subject: [PATCH] [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 changed +this behavior to reject invalid Host headers entirely. +Unfortunately, Docker appears to rely on the previous behavior. + +When sending a HTTP/1 request with an invalid Host, send an empty +Host header. This is safer than truncation: If you care about the +Host, then you should get the one you set; if you don't care, +then an empty Host should be fine. + +Continue to fully validate Host headers sent to a proxy, +since proxies generally can't productively forward requests +without a Host. + +For #60374 +Fixes #61431 +Fixes #61825 + +Change-Id: If170c7dd860aa20eb58fe32990fc93af832742b6 +Reviewed-on: https://go-review.googlesource.com/c/go/+/511155 +TryBot-Result: Gopher Robot +Reviewed-by: Roland Shoemaker +Run-TryBot: Damien Neil +(cherry picked from commit b9153f6ef338baee5fe02a867c8fbc83a8b29dd1) +Reviewed-on: https://go-review.googlesource.com/c/go/+/518855 +Auto-Submit: Dmitri Shuralyov +Run-TryBot: Roland Shoemaker +Reviewed-by: Russ Cox + +Upstream-Status: Backport [https://github.com/golang/go/commit/c08a5fa413a34111c9a37fd9e545de27ab0978b1] +CVE: CVE-2023-29406 +Signed-off-by: Ming Liu +--- + src/net/http/request.go | 23 ++++++++++++++++++++++- + src/net/http/request_test.go | 17 ++++++++++++----- + 2 files changed, 34 insertions(+), 6 deletions(-) + +diff --git a/src/net/http/request.go b/src/net/http/request.go +index 3100037386..91cb8a66b9 100644 +--- a/src/net/http/request.go ++++ b/src/net/http/request.go +@@ -582,8 +582,29 @@ func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitF + if err != nil { + return err + } ++ // Validate that the Host header is a valid header in general, ++ // but don't validate the host itself. This is sufficient to avoid ++ // header or request smuggling via the Host field. ++ // The server can (and will, if it's a net/http server) reject ++ // the request if it doesn't consider the host valid. + if !httpguts.ValidHostHeader(host) { +- return errors.New("http: invalid Host header") ++ // Historically, we would truncate the Host header after '/' or ' '. ++ // Some users have relied on this truncation to convert a network ++ // address such as Unix domain socket path into a valid, ignored ++ // Host header (see https://go.dev/issue/61431). ++ // ++ // We don't preserve the truncation, because sending an altered ++ // header field opens a smuggling vector. Instead, zero out the ++ // Host header entirely if it isn't valid. (An empty Host is valid; ++ // see RFC 9112 Section 3.2.) ++ // ++ // Return an error if we're sending to a proxy, since the proxy ++ // probably can't do anything useful with an empty Host header. ++ if !usingProxy { ++ host = "" ++ } else { ++ return errors.New("http: invalid Host header") ++ } + } + + // According to RFC 6874, an HTTP client, proxy, or other +diff --git a/src/net/http/request_test.go b/src/net/http/request_test.go +index fddc85d6a9..dd1e2dc2a1 100644 +--- a/src/net/http/request_test.go ++++ b/src/net/http/request_test.go +@@ -770,16 +770,23 @@ func TestRequestWriteBufferedWriter(t *testing.T) { + } + } + +-func TestRequestBadHost(t *testing.T) { ++func TestRequestBadHostHeader(t *testing.T) { + got := []string{} + req, err := NewRequest("GET", "http://foo/after", nil) + if err != nil { + t.Fatal(err) + } +- req.Host = "foo.com with spaces" +- req.URL.Host = "foo.com with spaces" +- if err := req.Write(logWrites{t, &got}); err == nil { +- t.Errorf("Writing request with invalid Host: succeded, want error") ++ req.Host = "foo.com\nnewline" ++ req.URL.Host = "foo.com\nnewline" ++ req.Write(logWrites{t, &got}) ++ want := []string{ ++ "GET /after HTTP/1.1\r\n", ++ "Host: \r\n", ++ "User-Agent: " + DefaultUserAgent + "\r\n", ++ "\r\n", ++ } ++ if !reflect.DeepEqual(got, want) { ++ t.Errorf("Writes = %q\n Want = %q", got, want) + } + } + +-- +2.34.1 + diff --git a/meta/recipes-devtools/go/go-1.14/CVE-2023-29406.patch b/meta/recipes-devtools/go/go-1.14/CVE-2023-29406.patch deleted file mode 100644 index 080def4682..0000000000 --- a/meta/recipes-devtools/go/go-1.14/CVE-2023-29406.patch +++ /dev/null @@ -1,212 +0,0 @@ -From 5fa6923b1ea891400153d04ddf1545e23b40041b Mon Sep 17 00:00:00 2001 -From: Damien Neil -Date: Wed, 28 Jun 2023 13:20:08 -0700 -Subject: [PATCH] [release-branch.go1.19] net/http: validate Host header before - sending - -Verify that the Host header we send is valid. -Avoids surprising behavior such as a Host of "go.dev\r\nX-Evil:oops" -adding an X-Evil header to HTTP/1 requests. - -Add a test, skip the test for HTTP/2. HTTP/2 is not vulnerable to -header injection in the way HTTP/1 is, but x/net/http2 doesn't validate -the header and will go into a retry loop when the server rejects it. -CL 506995 adds the necessary validation to x/net/http2. - -Updates #60374 -Fixes #61075 -For CVE-2023-29406 - -Change-Id: I05cb6866a9bead043101954dfded199258c6dd04 -Reviewed-on: https://go-review.googlesource.com/c/go/+/506996 -Reviewed-by: Tatiana Bradley -TryBot-Result: Gopher Robot -Run-TryBot: Damien Neil -(cherry picked from commit 499458f7ca04087958987a33c2703c3ef03e27e2) -Reviewed-on: https://go-review.googlesource.com/c/go/+/507358 -Run-TryBot: Tatiana Bradley -Reviewed-by: Roland Shoemaker - -Upstream-Status: Backport [https://github.com/golang/go/commit/5fa6923b1ea891400153d04ddf1545e23b40041b] -CVE: CVE-2023-29406 -Signed-off-by: Vivek Kumbhar ---- - src/net/http/http_test.go | 29 --------------------- - src/net/http/request.go | 47 ++++++++-------------------------- - src/net/http/request_test.go | 11 ++------ - src/net/http/transport_test.go | 18 +++++++++++++ - 4 files changed, 31 insertions(+), 74 deletions(-) - -diff --git a/src/net/http/http_test.go b/src/net/http/http_test.go -index f4ea52d..ea38cb4 100644 ---- a/src/net/http/http_test.go -+++ b/src/net/http/http_test.go -@@ -49,35 +49,6 @@ func TestForeachHeaderElement(t *testing.T) { - } - } - --func TestCleanHost(t *testing.T) { -- tests := []struct { -- in, want string -- }{ -- {"www.google.com", "www.google.com"}, -- {"www.google.com foo", "www.google.com"}, -- {"www.google.com/foo", "www.google.com"}, -- {" first character is a space", ""}, -- {"[1::6]:8080", "[1::6]:8080"}, -- -- // Punycode: -- {"гофер.рф/foo", "xn--c1ae0ajs.xn--p1ai"}, -- {"bücher.de", "xn--bcher-kva.de"}, -- {"bücher.de:8080", "xn--bcher-kva.de:8080"}, -- // Verify we convert to lowercase before punycode: -- {"BÜCHER.de", "xn--bcher-kva.de"}, -- {"BÜCHER.de:8080", "xn--bcher-kva.de:8080"}, -- // Verify we normalize to NFC before punycode: -- {"gophér.nfc", "xn--gophr-esa.nfc"}, // NFC input; no work needed -- {"goph\u0065\u0301r.nfd", "xn--gophr-esa.nfd"}, // NFD input -- } -- for _, tt := range tests { -- got := cleanHost(tt.in) -- if tt.want != got { -- t.Errorf("cleanHost(%q) = %q, want %q", tt.in, got, tt.want) -- } -- } --} -- - // Test that cmd/go doesn't link in the HTTP server. - // - // This catches accidental dependencies between the HTTP transport and -diff --git a/src/net/http/request.go b/src/net/http/request.go -index cb2edd2..2706300 100644 ---- a/src/net/http/request.go -+++ b/src/net/http/request.go -@@ -18,7 +18,6 @@ import ( - "io/ioutil" - "mime" - "mime/multipart" -- "net" - "net/http/httptrace" - "net/textproto" - "net/url" -@@ -26,7 +25,8 @@ import ( - "strconv" - "strings" - "sync" -- -+ -+ "golang.org/x/net/http/httpguts" - "golang.org/x/net/idna" - ) - -@@ -557,12 +557,19 @@ func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitF - // is not given, use the host from the request URL. - // - // Clean the host, in case it arrives with unexpected stuff in it. -- host := cleanHost(r.Host) -+ host := r.Host - if host == "" { - if r.URL == nil { - return errMissingHost - } -- host = cleanHost(r.URL.Host) -+ host = r.URL.Host -+ } -+ host, err = httpguts.PunycodeHostPort(host) -+ if err != nil { -+ return err -+ } -+ if !httpguts.ValidHostHeader(host) { -+ return errors.New("http: invalid Host header") - } - - // According to RFC 6874, an HTTP client, proxy, or other -@@ -717,38 +724,6 @@ func idnaASCII(v string) (string, error) { - return idna.Lookup.ToASCII(v) - } - --// cleanHost cleans up the host sent in request's Host header. --// --// It both strips anything after '/' or ' ', and puts the value --// into Punycode form, if necessary. --// --// Ideally we'd clean the Host header according to the spec: --// https://tools.ietf.org/html/rfc7230#section-5.4 (Host = uri-host [ ":" port ]") --// https://tools.ietf.org/html/rfc7230#section-2.7 (uri-host -> rfc3986's host) --// https://tools.ietf.org/html/rfc3986#section-3.2.2 (definition of host) --// But practically, what we are trying to avoid is the situation in --// issue 11206, where a malformed Host header used in the proxy context --// would create a bad request. So it is enough to just truncate at the --// first offending character. --func cleanHost(in string) string { -- if i := strings.IndexAny(in, " /"); i != -1 { -- in = in[:i] -- } -- host, port, err := net.SplitHostPort(in) -- if err != nil { // input was just a host -- a, err := idnaASCII(in) -- if err != nil { -- return in // garbage in, garbage out -- } -- return a -- } -- a, err := idnaASCII(host) -- if err != nil { -- return in // garbage in, garbage out -- } -- return net.JoinHostPort(a, port) --} -- - // removeZone removes IPv6 zone identifier from host. - // E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080" - func removeZone(host string) string { -diff --git a/src/net/http/request_test.go b/src/net/http/request_test.go -index 461d66e..0d417ff 100644 ---- a/src/net/http/request_test.go -+++ b/src/net/http/request_test.go -@@ -676,15 +676,8 @@ func TestRequestBadHost(t *testing.T) { - } - req.Host = "foo.com with spaces" - req.URL.Host = "foo.com with spaces" -- req.Write(logWrites{t, &got}) -- want := []string{ -- "GET /after HTTP/1.1\r\n", -- "Host: foo.com\r\n", -- "User-Agent: " + DefaultUserAgent + "\r\n", -- "\r\n", -- } -- if !reflect.DeepEqual(got, want) { -- t.Errorf("Writes = %q\n Want = %q", got, want) -+ if err := req.Write(logWrites{t, &got}); err == nil { -+ t.Errorf("Writing request with invalid Host: succeded, want error") - } - } - -diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go -index fa0c370..0afb6b9 100644 ---- a/src/net/http/transport_test.go -+++ b/src/net/http/transport_test.go -@@ -6249,3 +6249,21 @@ func TestIssue32441(t *testing.T) { - t.Error(err) - } - } -+ -+func TestRequestSanitization(t *testing.T) { -+ setParallel(t) -+ defer afterTest(t) -+ -+ ts := newClientServerTest(t, h1Mode, HandlerFunc(func(rw ResponseWriter, req *Request) { -+ if h, ok := req.Header["X-Evil"]; ok { -+ t.Errorf("request has X-Evil header: %q", h) -+ } -+ })).ts -+ defer ts.Close() -+ req, _ := NewRequest("GET", ts.URL, nil) -+ req.Host = "go.dev\r\nX-Evil:evil" -+ resp, _ := ts.Client().Do(req) -+ if resp != nil { -+ resp.Body.Close() -+ } -+} --- -2.25.1 -- cgit 1.2.3-korg