diff options
Diffstat (limited to 'recipes-containers/docker/files/CVE-2024-29018.patch')
| -rw-r--r-- | recipes-containers/docker/files/CVE-2024-29018.patch | 344 |
1 files changed, 0 insertions, 344 deletions
diff --git a/recipes-containers/docker/files/CVE-2024-29018.patch b/recipes-containers/docker/files/CVE-2024-29018.patch deleted file mode 100644 index f3c800ff..00000000 --- a/recipes-containers/docker/files/CVE-2024-29018.patch +++ /dev/null | |||
| @@ -1,344 +0,0 @@ | |||
| 1 | From 20c205fd3a0081d005958eff690e2b34df1c5e5e Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Rob Murray <rob.murray@docker.com> | ||
| 3 | Date: Tue, 19 Mar 2024 11:19:30 +0000 | ||
| 4 | Subject: [PATCH 1/2] Environment variable to override resolv.conf path. | ||
| 5 | |||
| 6 | If env var DOCKER_TEST_RESOLV_CONF_PATH is set, treat it as an override | ||
| 7 | for the 'resolv.conf' path. | ||
| 8 | |||
| 9 | Added as part of resolv.conf refactoring, but needed by back-ported test | ||
| 10 | TestInternalNetworkDNS. | ||
| 11 | |||
| 12 | Signed-off-by: Rob Murray <rob.murray@docker.com> | ||
| 13 | |||
| 14 | CVE: CVE-2024-29018 | ||
| 15 | Upstream-Status: Backport [https://github.com/moby/moby/commit/e63daec8672d77ac0b2b5c262ef525c7cf17fd20] | ||
| 16 | Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com> | ||
| 17 | --- | ||
| 18 | daemon/container_operations_unix.go | 20 +-- | ||
| 19 | integration/networking/resolvconf_test.go | 142 ++++++++++++++++++++++ | ||
| 20 | libnetwork/endpoint.go | 12 +- | ||
| 21 | libnetwork/resolver.go | 17 ++- | ||
| 22 | libnetwork/sandbox_dns_unix.go | 9 +- | ||
| 23 | 5 files changed, 182 insertions(+), 18 deletions(-) | ||
| 24 | create mode 100644 integration/networking/resolvconf_test.go | ||
| 25 | |||
| 26 | diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go | ||
| 27 | index 6a23a4ca92..e9be1b4e72 100644 | ||
| 28 | --- a/daemon/container_operations_unix.go | ||
| 29 | +++ b/daemon/container_operations_unix.go | ||
| 30 | @@ -380,6 +380,7 @@ func serviceDiscoveryOnDefaultNetwork() bool { | ||
| 31 | |||
| 32 | func setupPathsAndSandboxOptions(container *container.Container, cfg *config.Config, sboxOptions *[]libnetwork.SandboxOption) error { | ||
| 33 | var err error | ||
| 34 | + var originResolvConfPath string | ||
| 35 | |||
| 36 | // Set the correct paths for /etc/hosts and /etc/resolv.conf, based on the | ||
| 37 | // networking-mode of the container. Note that containers with "container" | ||
| 38 | @@ -393,8 +394,8 @@ func setupPathsAndSandboxOptions(container *container.Container, cfg *config.Con | ||
| 39 | *sboxOptions = append( | ||
| 40 | *sboxOptions, | ||
| 41 | libnetwork.OptionOriginHostsPath("/etc/hosts"), | ||
| 42 | - libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf"), | ||
| 43 | ) | ||
| 44 | + originResolvConfPath = "/etc/resolv.conf" | ||
| 45 | case container.HostConfig.NetworkMode.IsUserDefined(): | ||
| 46 | // The container uses a user-defined network. We use the embedded DNS | ||
| 47 | // server for container name resolution and to act as a DNS forwarder | ||
| 48 | @@ -407,10 +408,7 @@ func setupPathsAndSandboxOptions(container *container.Container, cfg *config.Con | ||
| 49 | // If systemd-resolvd is used, the "upstream" DNS servers can be found in | ||
| 50 | // /run/systemd/resolve/resolv.conf. We do not query those DNS servers | ||
| 51 | // directly, as they can be dynamically reconfigured. | ||
| 52 | - *sboxOptions = append( | ||
| 53 | - *sboxOptions, | ||
| 54 | - libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf"), | ||
| 55 | - ) | ||
| 56 | + originResolvConfPath = "/etc/resolv.conf" | ||
| 57 | default: | ||
| 58 | // For other situations, such as the default bridge network, container | ||
| 59 | // discovery / name resolution is handled through /etc/hosts, and no | ||
| 60 | @@ -423,11 +421,15 @@ func setupPathsAndSandboxOptions(container *container.Container, cfg *config.Con | ||
| 61 | // DNS servers on the host can be dynamically updated. | ||
| 62 | // | ||
| 63 | // Copy the host's resolv.conf for the container (/run/systemd/resolve/resolv.conf or /etc/resolv.conf) | ||
| 64 | - *sboxOptions = append( | ||
| 65 | - *sboxOptions, | ||
| 66 | - libnetwork.OptionOriginResolvConfPath(cfg.GetResolvConf()), | ||
| 67 | - ) | ||
| 68 | + originResolvConfPath = cfg.GetResolvConf() | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + // Allow tests to point at their own resolv.conf file. | ||
| 72 | + if envPath := os.Getenv("DOCKER_TEST_RESOLV_CONF_PATH"); envPath != "" { | ||
| 73 | + log.G(context.TODO()).Infof("Using OriginResolvConfPath from env: %s", envPath) | ||
| 74 | + originResolvConfPath = envPath | ||
| 75 | } | ||
| 76 | + *sboxOptions = append(*sboxOptions, libnetwork.OptionOriginResolvConfPath(originResolvConfPath)) | ||
| 77 | |||
| 78 | container.HostsPath, err = container.GetRootResourcePath("hosts") | ||
| 79 | if err != nil { | ||
| 80 | diff --git a/integration/networking/resolvconf_test.go b/integration/networking/resolvconf_test.go | ||
| 81 | new file mode 100644 | ||
| 82 | index 0000000000..60c8b1bc9a | ||
| 83 | --- /dev/null | ||
| 84 | +++ b/integration/networking/resolvconf_test.go | ||
| 85 | @@ -0,0 +1,142 @@ | ||
| 86 | +package networking | ||
| 87 | + | ||
| 88 | +import ( | ||
| 89 | + "net" | ||
| 90 | + "os" | ||
| 91 | + "testing" | ||
| 92 | + | ||
| 93 | + containertypes "github.com/docker/docker/api/types/container" | ||
| 94 | + "github.com/docker/docker/integration/internal/container" | ||
| 95 | + "github.com/docker/docker/integration/internal/network" | ||
| 96 | + "github.com/docker/docker/testutil/daemon" | ||
| 97 | + "github.com/miekg/dns" | ||
| 98 | + "gotest.tools/v3/assert" | ||
| 99 | + is "gotest.tools/v3/assert/cmp" | ||
| 100 | + "gotest.tools/v3/skip" | ||
| 101 | +) | ||
| 102 | + | ||
| 103 | +// writeTempResolvConf writes a resolv.conf that only contains a single | ||
| 104 | +// nameserver line, with address addr. | ||
| 105 | +// It returns the name of the temp file. | ||
| 106 | +func writeTempResolvConf(t *testing.T, addr string) string { | ||
| 107 | + t.Helper() | ||
| 108 | + // Not using t.TempDir() here because in rootless mode, while the temporary | ||
| 109 | + // directory gets mode 0777, it's a subdir of an 0700 directory owned by root. | ||
| 110 | + // So, it's not accessible by the daemon. | ||
| 111 | + f, err := os.CreateTemp("", "resolv.conf") | ||
| 112 | + assert.NilError(t, err) | ||
| 113 | + t.Cleanup(func() { os.Remove(f.Name()) }) | ||
| 114 | + err = f.Chmod(0644) | ||
| 115 | + assert.NilError(t, err) | ||
| 116 | + f.Write([]byte("nameserver " + addr + "\n")) | ||
| 117 | + return f.Name() | ||
| 118 | +} | ||
| 119 | + | ||
| 120 | +const dnsRespAddr = "10.11.12.13" | ||
| 121 | + | ||
| 122 | +// startDaftDNS starts and returns a really, really daft DNS server that only | ||
| 123 | +// responds to type-A requests, and always with address dnsRespAddr. | ||
| 124 | +func startDaftDNS(t *testing.T, addr string) *dns.Server { | ||
| 125 | + serveDNS := func(w dns.ResponseWriter, query *dns.Msg) { | ||
| 126 | + if query.Question[0].Qtype == dns.TypeA { | ||
| 127 | + resp := &dns.Msg{} | ||
| 128 | + resp.SetReply(query) | ||
| 129 | + answer := &dns.A{ | ||
| 130 | + Hdr: dns.RR_Header{ | ||
| 131 | + Name: query.Question[0].Name, | ||
| 132 | + Rrtype: dns.TypeA, | ||
| 133 | + Class: dns.ClassINET, | ||
| 134 | + Ttl: 600, | ||
| 135 | + }, | ||
| 136 | + } | ||
| 137 | + answer.A = net.ParseIP(dnsRespAddr) | ||
| 138 | + resp.Answer = append(resp.Answer, answer) | ||
| 139 | + _ = w.WriteMsg(resp) | ||
| 140 | + } | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + conn, err := net.ListenUDP("udp", &net.UDPAddr{ | ||
| 144 | + IP: net.ParseIP(addr), | ||
| 145 | + Port: 53, | ||
| 146 | + }) | ||
| 147 | + assert.NilError(t, err) | ||
| 148 | + | ||
| 149 | + server := &dns.Server{Handler: dns.HandlerFunc(serveDNS), PacketConn: conn} | ||
| 150 | + go func() { | ||
| 151 | + _ = server.ActivateAndServe() | ||
| 152 | + }() | ||
| 153 | + | ||
| 154 | + return server | ||
| 155 | +} | ||
| 156 | + | ||
| 157 | +// Check that when a container is connected to an internal network, DNS | ||
| 158 | +// requests sent to daemon's internal DNS resolver are not forwarded to | ||
| 159 | +// an upstream resolver listening on a localhost address. | ||
| 160 | +// (Assumes the host does not already have a DNS server on 127.0.0.1.) | ||
| 161 | +func TestInternalNetworkDNS(t *testing.T) { | ||
| 162 | + skip.If(t, testEnv.DaemonInfo.OSType == "windows", "No resolv.conf on Windows") | ||
| 163 | + skip.If(t, testEnv.IsRootless, "Can't use resolver on host in rootless mode") | ||
| 164 | + ctx := setupTest(t) | ||
| 165 | + | ||
| 166 | + // Start a DNS server on the loopback interface. | ||
| 167 | + server := startDaftDNS(t, "127.0.0.1") | ||
| 168 | + defer server.Shutdown() | ||
| 169 | + | ||
| 170 | + // Set up a temp resolv.conf pointing at that DNS server, and a daemon using it. | ||
| 171 | + tmpFileName := writeTempResolvConf(t, "127.0.0.1") | ||
| 172 | + d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_RESOLV_CONF_PATH="+tmpFileName)) | ||
| 173 | + d.StartWithBusybox(ctx, t, "--experimental", "--ip6tables") | ||
| 174 | + defer d.Stop(t) | ||
| 175 | + | ||
| 176 | + c := d.NewClientT(t) | ||
| 177 | + defer c.Close() | ||
| 178 | + | ||
| 179 | + intNetName := "intnet" | ||
| 180 | + network.CreateNoError(ctx, t, c, intNetName, | ||
| 181 | + network.WithDriver("bridge"), | ||
| 182 | + network.WithInternal(), | ||
| 183 | + ) | ||
| 184 | + defer network.RemoveNoError(ctx, t, c, intNetName) | ||
| 185 | + | ||
| 186 | + extNetName := "extnet" | ||
| 187 | + network.CreateNoError(ctx, t, c, extNetName, | ||
| 188 | + network.WithDriver("bridge"), | ||
| 189 | + ) | ||
| 190 | + defer network.RemoveNoError(ctx, t, c, extNetName) | ||
| 191 | + | ||
| 192 | + // Create a container, initially with external connectivity. | ||
| 193 | + // Expect the external DNS server to respond to a request from the container. | ||
| 194 | + ctrId := container.Run(ctx, t, c, container.WithNetworkMode(extNetName)) | ||
| 195 | + defer c.ContainerRemove(ctx, ctrId, containertypes.RemoveOptions{Force: true}) | ||
| 196 | + res, err := container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"}) | ||
| 197 | + assert.NilError(t, err) | ||
| 198 | + assert.Check(t, is.Equal(res.ExitCode, 0)) | ||
| 199 | + assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr)) | ||
| 200 | + | ||
| 201 | + // Connect the container to the internal network as well. | ||
| 202 | + // External DNS should still be used. | ||
| 203 | + err = c.NetworkConnect(ctx, intNetName, ctrId, nil) | ||
| 204 | + assert.NilError(t, err) | ||
| 205 | + res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"}) | ||
| 206 | + assert.NilError(t, err) | ||
| 207 | + assert.Check(t, is.Equal(res.ExitCode, 0)) | ||
| 208 | + assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr)) | ||
| 209 | + | ||
| 210 | + // Disconnect from the external network. | ||
| 211 | + // Expect no access to the external DNS. | ||
| 212 | + err = c.NetworkDisconnect(ctx, extNetName, ctrId, true) | ||
| 213 | + assert.NilError(t, err) | ||
| 214 | + res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"}) | ||
| 215 | + assert.NilError(t, err) | ||
| 216 | + assert.Check(t, is.Equal(res.ExitCode, 1)) | ||
| 217 | + assert.Check(t, is.Contains(res.Stdout(), "SERVFAIL")) | ||
| 218 | + | ||
| 219 | + // Reconnect the external network. | ||
| 220 | + // Check that the external DNS server is used again. | ||
| 221 | + err = c.NetworkConnect(ctx, extNetName, ctrId, nil) | ||
| 222 | + assert.NilError(t, err) | ||
| 223 | + res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"}) | ||
| 224 | + assert.NilError(t, err) | ||
| 225 | + assert.Check(t, is.Equal(res.ExitCode, 0)) | ||
| 226 | + assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr)) | ||
| 227 | +} | ||
| 228 | diff --git a/libnetwork/endpoint.go b/libnetwork/endpoint.go | ||
| 229 | index d9c257dc68..3ca546a4ac 100644 | ||
| 230 | --- a/libnetwork/endpoint.go | ||
| 231 | +++ b/libnetwork/endpoint.go | ||
| 232 | @@ -538,8 +538,13 @@ func (ep *Endpoint) sbJoin(sb *Sandbox, options ...EndpointOption) (err error) { | ||
| 233 | return sb.setupDefaultGW() | ||
| 234 | } | ||
| 235 | |||
| 236 | - moveExtConn := sb.getGatewayEndpoint() != extEp | ||
| 237 | + currentExtEp := sb.getGatewayEndpoint() | ||
| 238 | + // Enable upstream forwarding if the sandbox gained external connectivity. | ||
| 239 | + if sb.resolver != nil { | ||
| 240 | + sb.resolver.SetForwardingPolicy(currentExtEp != nil) | ||
| 241 | + } | ||
| 242 | |||
| 243 | + moveExtConn := currentExtEp != extEp | ||
| 244 | if moveExtConn { | ||
| 245 | if extEp != nil { | ||
| 246 | log.G(context.TODO()).Debugf("Revoking external connectivity on endpoint %s (%s)", extEp.Name(), extEp.ID()) | ||
| 247 | @@ -735,6 +740,11 @@ func (ep *Endpoint) sbLeave(sb *Sandbox, force bool, options ...EndpointOption) | ||
| 248 | |||
| 249 | // New endpoint providing external connectivity for the sandbox | ||
| 250 | extEp = sb.getGatewayEndpoint() | ||
| 251 | + // Disable upstream forwarding if the sandbox lost external connectivity. | ||
| 252 | + if sb.resolver != nil { | ||
| 253 | + sb.resolver.SetForwardingPolicy(extEp != nil) | ||
| 254 | + } | ||
| 255 | + | ||
| 256 | if moveExtConn && extEp != nil { | ||
| 257 | log.G(context.TODO()).Debugf("Programming external connectivity on endpoint %s (%s)", extEp.Name(), extEp.ID()) | ||
| 258 | extN, err := extEp.getNetworkFromStore() | ||
| 259 | diff --git a/libnetwork/resolver.go b/libnetwork/resolver.go | ||
| 260 | index 9df2154499..5d5686fc86 100644 | ||
| 261 | --- a/libnetwork/resolver.go | ||
| 262 | +++ b/libnetwork/resolver.go | ||
| 263 | @@ -9,6 +9,7 @@ import ( | ||
| 264 | "strconv" | ||
| 265 | "strings" | ||
| 266 | "sync" | ||
| 267 | + "sync/atomic" | ||
| 268 | "time" | ||
| 269 | |||
| 270 | "github.com/containerd/log" | ||
| 271 | @@ -75,7 +76,7 @@ type Resolver struct { | ||
| 272 | tcpListen *net.TCPListener | ||
| 273 | err error | ||
| 274 | listenAddress string | ||
| 275 | - proxyDNS bool | ||
| 276 | + proxyDNS atomic.Bool | ||
| 277 | startCh chan struct{} | ||
| 278 | logger *log.Entry | ||
| 279 | |||
| 280 | @@ -85,15 +86,17 @@ type Resolver struct { | ||
| 281 | |||
| 282 | // NewResolver creates a new instance of the Resolver | ||
| 283 | func NewResolver(address string, proxyDNS bool, backend DNSBackend) *Resolver { | ||
| 284 | - return &Resolver{ | ||
| 285 | + r := &Resolver{ | ||
| 286 | backend: backend, | ||
| 287 | - proxyDNS: proxyDNS, | ||
| 288 | listenAddress: address, | ||
| 289 | err: fmt.Errorf("setup not done yet"), | ||
| 290 | startCh: make(chan struct{}, 1), | ||
| 291 | fwdSem: semaphore.NewWeighted(maxConcurrent), | ||
| 292 | logInverval: rate.Sometimes{Interval: logInterval}, | ||
| 293 | } | ||
| 294 | + r.proxyDNS.Store(proxyDNS) | ||
| 295 | + | ||
| 296 | + return r | ||
| 297 | } | ||
| 298 | |||
| 299 | func (r *Resolver) log(ctx context.Context) *log.Entry { | ||
| 300 | @@ -194,6 +197,12 @@ func (r *Resolver) SetExtServers(extDNS []extDNSEntry) { | ||
| 301 | } | ||
| 302 | } | ||
| 303 | |||
| 304 | +// SetForwardingPolicy re-configures the embedded DNS resolver to either enable or disable forwarding DNS queries to | ||
| 305 | +// external servers. | ||
| 306 | +func (r *Resolver) SetForwardingPolicy(policy bool) { | ||
| 307 | + r.proxyDNS.Store(policy) | ||
| 308 | +} | ||
| 309 | + | ||
| 310 | // NameServer returns the IP of the DNS resolver for the containers. | ||
| 311 | func (r *Resolver) NameServer() string { | ||
| 312 | return r.listenAddress | ||
| 313 | @@ -421,7 +430,7 @@ func (r *Resolver) serveDNS(w dns.ResponseWriter, query *dns.Msg) { | ||
| 314 | return | ||
| 315 | } | ||
| 316 | |||
| 317 | - if r.proxyDNS { | ||
| 318 | + if r.proxyDNS.Load() { | ||
| 319 | // If the user sets ndots > 0 explicitly and the query is | ||
| 320 | // in the root domain don't forward it out. We will return | ||
| 321 | // failure and let the client retry with the search domain | ||
| 322 | diff --git a/libnetwork/sandbox_dns_unix.go b/libnetwork/sandbox_dns_unix.go | ||
| 323 | index e30f394057..9f7a1c4671 100644 | ||
| 324 | --- a/libnetwork/sandbox_dns_unix.go | ||
| 325 | +++ b/libnetwork/sandbox_dns_unix.go | ||
| 326 | @@ -30,10 +30,11 @@ const ( | ||
| 327 | func (sb *Sandbox) startResolver(restore bool) { | ||
| 328 | sb.resolverOnce.Do(func() { | ||
| 329 | var err error | ||
| 330 | - // The embedded resolver is always started with proxyDNS set as true, even when the sandbox is only attached to | ||
| 331 | - // an internal network. This way, it's the driver responsibility to make sure `connect` syscall fails fast when | ||
| 332 | - // no external connectivity is available (eg. by not setting a default gateway). | ||
| 333 | - sb.resolver = NewResolver(resolverIPSandbox, true, sb) | ||
| 334 | + // The resolver is started with proxyDNS=false if the sandbox does not currently | ||
| 335 | + // have a gateway. So, if the Sandbox is only connected to an 'internal' network, | ||
| 336 | + // it will not forward DNS requests to external resolvers. The resolver's | ||
| 337 | + // proxyDNS setting is then updated as network Endpoints are added/removed. | ||
| 338 | + sb.resolver = NewResolver(resolverIPSandbox, sb.getGatewayEndpoint() != nil, sb) | ||
| 339 | defer func() { | ||
| 340 | if err != nil { | ||
| 341 | sb.resolver = nil | ||
| 342 | -- | ||
| 343 | 2.50.1 | ||
| 344 | |||
