Skip to content

Commit 37ec872

Browse files
[AUTO-CHERRYPICK] fix CVE-2024-41110 in moby-engine - branch main (#9966)
Co-authored-by: Rohit Rawat <rohitrawat@microsoft.com>
1 parent 3328395 commit 37ec872

2 files changed

Lines changed: 206 additions & 0 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
From 45dd4676120739a9e1773a5cf859a533f7718e83 Mon Sep 17 00:00:00 2001
2+
From: Rohit Rawat <xordux@gmail.com>
3+
Date: Mon, 29 Jul 2024 09:12:42 +0000
4+
Subject: [PATCH] CVE-2024-41110 Authz plugin security fixes for 0-length
5+
content and path validation Signed-off-by: Jameson Hyde <jameson.hyde@docker.com>
6+
7+
8+
---
9+
pkg/authorization/authz.go | 38 +++++++++++--
10+
pkg/authorization/authz_unix_test.go | 84 +++++++++++++++++++++++++++-
11+
2 files changed, 115 insertions(+), 7 deletions(-)
12+
13+
diff --git a/pkg/authorization/authz.go b/pkg/authorization/authz.go
14+
index 590ac8d..68ed8bb 100644
15+
--- a/pkg/authorization/authz.go
16+
+++ b/pkg/authorization/authz.go
17+
@@ -7,6 +7,8 @@ import (
18+
"io"
19+
"mime"
20+
"net/http"
21+
+ "net/url"
22+
+ "regexp"
23+
"strings"
24+
25+
"github.com/docker/docker/pkg/ioutils"
26+
@@ -52,10 +54,23 @@ type Ctx struct {
27+
authReq *Request
28+
}
29+
30+
+func isChunked(r *http.Request) bool {
31+
+ // RFC 7230 specifies that content length is to be ignored if Transfer-Encoding is chunked
32+
+ if strings.EqualFold(r.Header.Get("Transfer-Encoding"), "chunked") {
33+
+ return true
34+
+ }
35+
+ for _, v := range r.TransferEncoding {
36+
+ if strings.EqualFold(v, "chunked") {
37+
+ return true
38+
+ }
39+
+ }
40+
+ return false
41+
+}
42+
+
43+
// AuthZRequest authorized the request to the docker daemon using authZ plugins
44+
func (ctx *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error {
45+
var body []byte
46+
- if sendBody(ctx.requestURI, r.Header) && r.ContentLength > 0 && r.ContentLength < maxBodySize {
47+
+ if sendBody(ctx.requestURI, r.Header) && (r.ContentLength > 0 || isChunked(r)) && r.ContentLength < maxBodySize {
48+
var err error
49+
body, r.Body, err = drainBody(r.Body)
50+
if err != nil {
51+
@@ -108,7 +123,6 @@ func (ctx *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error {
52+
if sendBody(ctx.requestURI, rm.Header()) {
53+
ctx.authReq.ResponseBody = rm.RawBody()
54+
}
55+
-
56+
for _, plugin := range ctx.plugins {
57+
logrus.Debugf("AuthZ response using plugin %s", plugin.Name())
58+
59+
@@ -146,10 +160,26 @@ func drainBody(body io.ReadCloser) ([]byte, io.ReadCloser, error) {
60+
return nil, newBody, err
61+
}
62+
63+
+func isAuthEndpoint(urlPath string) (bool, error) {
64+
+ // eg www.test.com/v1.24/auth/optional?optional1=something&optional2=something (version optional)
65+
+ matched, err := regexp.MatchString(`^[^\/]*\/(v\d[\d\.]*\/)?auth.*`, urlPath)
66+
+ if err != nil {
67+
+ return false, err
68+
+ }
69+
+ return matched, nil
70+
+}
71+
+
72+
// sendBody returns true when request/response body should be sent to AuthZPlugin
73+
-func sendBody(url string, header http.Header) bool {
74+
+func sendBody(inURL string, header http.Header) bool {
75+
+ u, err := url.Parse(inURL)
76+
+ // Assume no if the URL cannot be parsed - an empty request will still be forwarded to the plugin and should be rejected
77+
+ if err != nil {
78+
+ return false
79+
+ }
80+
+
81+
// Skip body for auth endpoint
82+
- if strings.HasSuffix(url, "/auth") {
83+
+ isAuth, err := isAuthEndpoint(u.Path)
84+
+ if isAuth || err != nil {
85+
return false
86+
}
87+
88+
diff --git a/pkg/authorization/authz_unix_test.go b/pkg/authorization/authz_unix_test.go
89+
index 835cb70..8bfe44e 100644
90+
--- a/pkg/authorization/authz_unix_test.go
91+
+++ b/pkg/authorization/authz_unix_test.go
92+
@@ -175,8 +175,8 @@ func TestDrainBody(t *testing.T) {
93+
94+
func TestSendBody(t *testing.T) {
95+
var (
96+
- url = "nothing.com"
97+
testcases = []struct {
98+
+ url string
99+
contentType string
100+
expected bool
101+
}{
102+
@@ -220,15 +220,93 @@ func TestSendBody(t *testing.T) {
103+
contentType: "",
104+
expected: false,
105+
},
106+
+ {
107+
+ url: "nothing.com/auth",
108+
+ contentType: "",
109+
+ expected: false,
110+
+ },
111+
+ {
112+
+ url: "nothing.com/auth",
113+
+ contentType: "application/json;charset=UTF8",
114+
+ expected: false,
115+
+ },
116+
+ {
117+
+ url: "nothing.com/auth?p1=test",
118+
+ contentType: "application/json;charset=UTF8",
119+
+ expected: false,
120+
+ },
121+
+ {
122+
+ url: "nothing.com/test?p1=/auth",
123+
+ contentType: "application/json;charset=UTF8",
124+
+ expected: true,
125+
+ },
126+
+ {
127+
+ url: "nothing.com/something/auth",
128+
+ contentType: "application/json;charset=UTF8",
129+
+ expected: true,
130+
+ },
131+
+ {
132+
+ url: "nothing.com/auth/test",
133+
+ contentType: "application/json;charset=UTF8",
134+
+ expected: false,
135+
+ },
136+
+ {
137+
+ url: "nothing.com/v1.24/auth/test",
138+
+ contentType: "application/json;charset=UTF8",
139+
+ expected: false,
140+
+ },
141+
+ {
142+
+ url: "nothing.com/v1/auth/test",
143+
+ contentType: "application/json;charset=UTF8",
144+
+ expected: false,
145+
+ },
146+
+ {
147+
+ url: "www.nothing.com/v1.24/auth/test",
148+
+ contentType: "application/json;charset=UTF8",
149+
+ expected: false,
150+
+ },
151+
+ {
152+
+ url: "https://www.nothing.com/v1.24/auth/test",
153+
+ contentType: "application/json;charset=UTF8",
154+
+ expected: false,
155+
+ },
156+
+ {
157+
+ url: "http://nothing.com/v1.24/auth/test",
158+
+ contentType: "application/json;charset=UTF8",
159+
+ expected: false,
160+
+ },
161+
+ {
162+
+ url: "www.nothing.com/test?p1=/auth",
163+
+ contentType: "application/json;charset=UTF8",
164+
+ expected: true,
165+
+ },
166+
+ {
167+
+ url: "http://www.nothing.com/test?p1=/auth",
168+
+ contentType: "application/json;charset=UTF8",
169+
+ expected: true,
170+
+ },
171+
+ {
172+
+ url: "www.nothing.com/something/auth",
173+
+ contentType: "application/json;charset=UTF8",
174+
+ expected: true,
175+
+ },
176+
+ {
177+
+ url: "https://www.nothing.com/something/auth",
178+
+ contentType: "application/json;charset=UTF8",
179+
+ expected: true,
180+
+ },
181+
}
182+
)
183+
184+
for _, testcase := range testcases {
185+
header := http.Header{}
186+
header.Set("Content-Type", testcase.contentType)
187+
+ if testcase.url == "" {
188+
+ testcase.url = "nothing.com"
189+
+ }
190+
191+
- if b := sendBody(url, header); b != testcase.expected {
192+
- t.Fatalf("Unexpected Content-Type; Expected: %t, Actual: %t", testcase.expected, b)
193+
+ if b := sendBody(testcase.url, header); b != testcase.expected {
194+
+ t.Fatalf("sendBody failed: url: %s, content-type: %s; Expected: %t, Actual: %t", testcase.url, testcase.contentType, testcase.expected, b)
195+
}
196+
}
197+
}
198+
--
199+
2.33.8
200+

SPECS/moby-engine/moby-engine.spec

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Patch2: CVE-2024-23652.patch
2222
Patch3: CVE-2023-45288.patch
2323
Patch4: CVE-2023-44487.patch
2424
Patch5: enable-docker-proxy-libexec-search.patch
25+
Patch6: CVE-2024-41110.patch
2526

2627
%{?systemd_requires}
2728

@@ -122,8 +123,13 @@ fi
122123
%{_unitdir}/*
123124

124125
%changelog
126+
<<<<<<< HEAD
125127
* Wed Jul 17 2024 Muhammad Falak R Wani <mwani@microsoft.com> - 24.0.9-7
126128
- Drop requirement on a specific version of golang
129+
=======
130+
* Mon Jul 29 2024 Rohit Rawat <rohitrawat@microsoft.com> - 24.0.9-7
131+
- Fix for CVE-2024-41110
132+
>>>>>>> dd2c6a30e (fix CVE-2024-41110 in moby-engine (#9951))
127133

128134
* Tue Jun 25 2024 Henry Beberman <henry.beberman@microsoft.com> - 24.0.9-6
129135
- Backport upstream change to search /usr/libexec for docker-proxy without daemon.json

0 commit comments

Comments
 (0)