* [meta-virtualization] [scarthgap] [PATCH] docker-moby: Fix CVE-2025-54410
@ 2025-09-08 7:09 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2025-09-19 2:34 ` Bruce Ashfield
0 siblings, 1 reply; 2+ messages in thread
From: Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2025-09-08 7:09 UTC (permalink / raw)
To: meta-virtualization; +Cc: vchavda, bruce.ashfield
From: Deepak Rathore <deeratho@cisco.com>
Upstream Repository: https://github.com/moby/moby.git
Bug Details: https://nvd.nist.gov/vuln/detail/CVE-2025-54410
Type: Security Fix
CVE: CVE-2025-54410
Score: 3.3
Patch:
[1] https://github.com/moby/moby/commit/a012739c2cf3
[2] https://github.com/moby/moby/commit/c761353e7c75
[3] https://github.com/moby/moby/commit/41f080df250a
[4] https://github.com/moby/moby/commit/651b2feb2731
Note:
- The main fix is implemented in [4], which is part of version 25.0.
- Commits [1], [2], and [3] are dependent patches also included in v25.0.
- The FirewalldReload() function, essential for the fix in [4], was
introduced in [3].
- Commit [1] adds the iptables.go file, enabling the implementation of
FirewalldReload() in [3].
- The TestInternalNwConnectivity() test was first introduced in [2] and
later updated in [3] to validate the firewall reload behavior.
Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
recipes-containers/docker/docker-moby_git.bb | 4 +
.../files/CVE-2025-54410-dependent_p1.patch | 38 ++
.../files/CVE-2025-54410-dependent_p2.patch | 143 ++++++
.../files/CVE-2025-54410-dependent_p3.patch | 451 ++++++++++++++++++
.../docker/files/CVE-2025-54410.patch | 50 ++
5 files changed, 686 insertions(+)
create mode 100644 recipes-containers/docker/files/CVE-2025-54410-dependent_p1.patch
create mode 100644 recipes-containers/docker/files/CVE-2025-54410-dependent_p2.patch
create mode 100644 recipes-containers/docker/files/CVE-2025-54410-dependent_p3.patch
create mode 100644 recipes-containers/docker/files/CVE-2025-54410.patch
diff --git a/recipes-containers/docker/docker-moby_git.bb b/recipes-containers/docker/docker-moby_git.bb
index d274b002..e4791319 100644
--- a/recipes-containers/docker/docker-moby_git.bb
+++ b/recipes-containers/docker/docker-moby_git.bb
@@ -58,6 +58,10 @@ SRC_URI = "\
file://0001-dynbinary-use-go-cross-compiler.patch;patchdir=src/import \
file://CVE-2024-36620.patch;patchdir=src/import \
file://CVE-2024-36621.patch;patchdir=src/import \
+ file://CVE-2025-54410-dependent_p1.patch;patchdir=src/import \
+ file://CVE-2025-54410-dependent_p2.patch;patchdir=src/import \
+ file://CVE-2025-54410-dependent_p3.patch;patchdir=src/import \
+ file://CVE-2025-54410.patch;patchdir=src/import \
"
DOCKER_COMMIT = "${SRCREV_moby}"
diff --git a/recipes-containers/docker/files/CVE-2025-54410-dependent_p1.patch b/recipes-containers/docker/files/CVE-2025-54410-dependent_p1.patch
new file mode 100644
index 00000000..f1a7db3d
--- /dev/null
+++ b/recipes-containers/docker/files/CVE-2025-54410-dependent_p1.patch
@@ -0,0 +1,38 @@
+From 4ff88c2b9d85e162676fd54b773a4a3957b5c622 Mon Sep 17 00:00:00 2001
+From: Rob Murray <rob.murray@docker.com>
+Date: Wed, 26 Mar 2025 14:32:15 +0000
+Subject: [PATCH 1/4] Add test util "FirewalldRunning"
+
+CVE: CVE-2025-54410
+Upstream-Status: Backport [https://github.com/moby/moby/commit/a012739c2cf3]
+
+Signed-off-by: Rob Murray <rob.murray@docker.com>
+(cherry picked from commit b8cacdf324c8309fb0555381889aa867391ba9fb)
+Signed-off-by: Andrey Epifanov <aepifanov@mirantis.com>
+(cherry picked from commit a012739c2cf347847d83ea77e30333528602c8a6)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ internal/testutils/networking/iptables.go | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+ create mode 100644 internal/testutils/networking/iptables.go
+
+diff --git a/internal/testutils/networking/iptables.go b/internal/testutils/networking/iptables.go
+new file mode 100644
+index 0000000000..2e6ada8363
+--- /dev/null
++++ b/internal/testutils/networking/iptables.go
+@@ -0,0 +1,12 @@
++package networking
++
++import (
++ "os/exec"
++ "strings"
++ "testing"
++)
++
++func FirewalldRunning() bool {
++ state, err := exec.Command("firewall-cmd", "--state").CombinedOutput()
++ return err == nil && strings.TrimSpace(string(state)) == "running"
++}
+--
+2.44.3
diff --git a/recipes-containers/docker/files/CVE-2025-54410-dependent_p2.patch b/recipes-containers/docker/files/CVE-2025-54410-dependent_p2.patch
new file mode 100644
index 00000000..e376338f
--- /dev/null
+++ b/recipes-containers/docker/files/CVE-2025-54410-dependent_p2.patch
@@ -0,0 +1,143 @@
+From 11125e7362281772f6d8cebaee3d8a1c07456e31 Mon Sep 17 00:00:00 2001
+From: Rob Murray <rob.murray@docker.com>
+Date: Wed, 7 Feb 2024 18:53:27 +0000
+Subject: [PATCH 2/4] Make 'internal' bridge networks accessible from host
+
+Prior to release 25.0.0, the bridge in an internal network was assigned
+an IP address - making the internal network accessible from the host,
+giving containers on the network access to anything listening on the
+bridge's address (or INADDR_ANY on the host).
+
+This change restores that behaviour. It does not restore the default
+route that was configured in the container, because packets sent outside
+the internal network's subnet have always been dropped. So, a 'connect()'
+to an address outside the subnet will still fail fast.
+
+CVE: CVE-2025-54410
+Upstream-Status: Backport [https://github.com/moby/moby/commit/c761353e7c75]
+
+Signed-off-by: Rob Murray <rob.murray@docker.com>
+(cherry picked from commit 419f5a637230570ec44c32f889f981f67ce15793)
+Signed-off-by: Albin Kerouanton <albinker@gmail.com>
+(cherry picked from commit c761353e7c75e4ffa90b80f3b5df40473d59620a)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ integration/networking/bridge_test.go | 57 +++++++++++++++++++
+ libnetwork/drivers/bridge/setup_ipv4_linux.go | 10 ++--
+ .../drivers/bridge/setup_verify_linux.go | 4 +-
+ 3 files changed, 63 insertions(+), 8 deletions(-)
+
+diff --git a/integration/networking/bridge_test.go b/integration/networking/bridge_test.go
+index e3d1fe2a36..8f54234406 100644
+--- a/integration/networking/bridge_test.go
++++ b/integration/networking/bridge_test.go
+@@ -477,3 +477,60 @@ func TestDefaultBridgeAddresses(t *testing.T) {
+ })
+ }
+ }
++
++// Test that a container on an 'internal' network has IP connectivity with
++// the host (on its own subnet, because the n/w bridge has an address on that
++// subnet, and it's in the host's namespace).
++// Regression test for https://github.com/moby/moby/issues/47329
++func TestInternalNwConnectivity(t *testing.T) {
++ skip.If(t, testEnv.DaemonInfo.OSType == "windows")
++
++ ctx := setupTest(t)
++
++ d := daemon.New(t)
++ d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables")
++ defer d.Stop(t)
++
++ c := d.NewClientT(t)
++ defer c.Close()
++
++ const bridgeName = "intnw"
++ const gw4 = "172.30.0.1"
++ const gw6 = "fda9:4130:4715::1234"
++ network.CreateNoError(ctx, t, c, bridgeName,
++ network.WithInternal(),
++ network.WithIPv6(),
++ network.WithIPAM("172.30.0.0/24", gw4),
++ network.WithIPAM("fda9:4130:4715::/64", gw6),
++ network.WithDriver("bridge"),
++ network.WithOption("com.docker.network.bridge.name", bridgeName),
++ )
++ defer network.RemoveNoError(ctx, t, c, bridgeName)
++
++ const ctrName = "intctr"
++ id := container.Run(ctx, t, c,
++ container.WithName(ctrName),
++ container.WithImage("busybox:latest"),
++ container.WithCmd("top"),
++ container.WithNetworkMode(bridgeName),
++ )
++ defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
++
++ execCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
++ defer cancel()
++
++ res := container.ExecT(execCtx, t, c, id, []string{"ping", "-c1", "-W3", gw4})
++ assert.Check(t, is.Equal(res.ExitCode, 0))
++ assert.Check(t, is.Equal(res.Stderr(), ""))
++ assert.Check(t, is.Contains(res.Stdout(), "1 packets transmitted, 1 packets received"))
++
++ res = container.ExecT(execCtx, t, c, id, []string{"ping6", "-c1", "-W3", gw6})
++ assert.Check(t, is.Equal(res.ExitCode, 0))
++ assert.Check(t, is.Equal(res.Stderr(), ""))
++ assert.Check(t, is.Contains(res.Stdout(), "1 packets transmitted, 1 packets received"))
++
++ // Addresses outside the internal subnet must not be accessible.
++ res = container.ExecT(execCtx, t, c, id, []string{"ping", "-c1", "-W3", "8.8.8.8"})
++ assert.Check(t, is.Equal(res.ExitCode, 1))
++ assert.Check(t, is.Contains(res.Stderr(), "Network is unreachable"))
++}
+diff --git a/libnetwork/drivers/bridge/setup_ipv4_linux.go b/libnetwork/drivers/bridge/setup_ipv4_linux.go
+index 0940745c23..6b925190ce 100644
+--- a/libnetwork/drivers/bridge/setup_ipv4_linux.go
++++ b/libnetwork/drivers/bridge/setup_ipv4_linux.go
+@@ -32,10 +32,6 @@ func setupBridgeIPv4(config *networkConfiguration, i *bridgeInterface) error {
+ // are decoupled, we should assign it only when it's really needed.
+ i.bridgeIPv4 = config.AddressIPv4
+
+- if config.Internal {
+- return nil
+- }
+-
+ if !config.InhibitIPv4 {
+ addrv4List, err := i.addresses(netlink.FAMILY_V4)
+ if err != nil {
+@@ -57,8 +53,10 @@ func setupBridgeIPv4(config *networkConfiguration, i *bridgeInterface) error {
+ }
+ }
+
+- // Store the default gateway
+- i.gatewayIPv4 = config.AddressIPv4.IP
++ if !config.Internal {
++ // Store the default gateway
++ i.gatewayIPv4 = config.AddressIPv4.IP
++ }
+
+ return nil
+ }
+diff --git a/libnetwork/drivers/bridge/setup_verify_linux.go b/libnetwork/drivers/bridge/setup_verify_linux.go
+index a39d750346..b7ddbcf814 100644
+--- a/libnetwork/drivers/bridge/setup_verify_linux.go
++++ b/libnetwork/drivers/bridge/setup_verify_linux.go
+@@ -20,12 +20,12 @@ func setupVerifyAndReconcileIPv4(config *networkConfiguration, i *bridgeInterfac
+ addrv4, _ := selectIPv4Address(addrsv4, config.AddressIPv4)
+
+ // Verify that the bridge has an IPv4 address.
+- if !config.Internal && addrv4.IPNet == nil {
++ if addrv4.IPNet == nil {
+ return &ErrNoIPAddr{}
+ }
+
+ // Verify that the bridge IPv4 address matches the requested configuration.
+- if config.AddressIPv4 != nil && addrv4.IPNet != nil && !addrv4.IP.Equal(config.AddressIPv4.IP) {
++ if config.AddressIPv4 != nil && !addrv4.IP.Equal(config.AddressIPv4.IP) {
+ return &IPv4AddrNoMatchError{IP: addrv4.IP, CfgIP: config.AddressIPv4.IP}
+ }
+
+--
+2.44.3
diff --git a/recipes-containers/docker/files/CVE-2025-54410-dependent_p3.patch b/recipes-containers/docker/files/CVE-2025-54410-dependent_p3.patch
new file mode 100644
index 00000000..16d4768f
--- /dev/null
+++ b/recipes-containers/docker/files/CVE-2025-54410-dependent_p3.patch
@@ -0,0 +1,451 @@
+From 211e9acdef48b1229591724ba83fe4b6ad86c7b6 Mon Sep 17 00:00:00 2001
+From: Rob Murray <rob.murray@docker.com>
+Date: Fri, 14 Feb 2025 16:23:49 +0000
+Subject: [PATCH 3/4] Restore iptables for current networks on firewalld reload
+
+Using iptables.OnReloaded to restore individual per-network rules
+on firewalld reload means rules for deleted networks pop back in
+to existence (because there was no way to delete the callbacks on
+network-delete).
+
+So, on firewalld reload, walk over current networks and ask them
+to restore their iptables rules.
+
+CVE: CVE-2025-54410
+Upstream-Status: Backport [https://github.com/moby/moby/commit/41f080df250a]
+
+Signed-off-by: Rob Murray <rob.murray@docker.com>
+(cherry picked from commit a527e5a546526117a0f3d7a33f3fcb8f4cd87c72)
+
+Test that firewalld reload doesn't re-create deleted iptables rules
+
+Signed-off-by: Rob Murray <rob.murray@docker.com>
+(cherry picked from commit c3fa7c17794feaf4e27238aa3c6de42c606765fa)
+
+Signed-off-by: Andrey Epifanov <aepifanov@mirantis.com>
+(cherry picked from commit 41f080df250aef805d34628d1719c369e9ff1585)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ integration/network/bridge_test.go | 65 +++++++++++++++++++
+ integration/networking/bridge_test.go | 4 ++
+ internal/testutils/networking/iptables.go | 48 ++++++++++++++
+ .../testutils/networking/iptables_test.go | 47 ++++++++++++++
+ libnetwork/drivers/bridge/bridge_linux.go | 65 +++++++++++++++++--
+ libnetwork/drivers/bridge/setup_firewalld.go | 41 ------------
+ .../drivers/bridge/setup_ip_tables_linux.go | 3 +
+ libnetwork/iptables/firewalld.go | 1 +
+ 8 files changed, 227 insertions(+), 47 deletions(-)
+ create mode 100644 internal/testutils/networking/iptables_test.go
+ delete mode 100644 libnetwork/drivers/bridge/setup_firewalld.go
+
+diff --git a/integration/network/bridge_test.go b/integration/network/bridge_test.go
+index b09ff5766c..09b7da9c2b 100644
+--- a/integration/network/bridge_test.go
++++ b/integration/network/bridge_test.go
+@@ -6,11 +6,17 @@ import (
+ "testing"
+ "time"
+
++ containertypes "github.com/docker/docker/api/types/container"
+ networktypes "github.com/docker/docker/api/types/network"
+ "github.com/docker/docker/api/types/versions"
+ ctr "github.com/docker/docker/integration/internal/container"
+ "github.com/docker/docker/integration/internal/network"
++ "github.com/docker/docker/internal/testutils/networking"
++ "github.com/docker/docker/libnetwork/drivers/bridge"
++ "github.com/docker/docker/testutil/daemon"
++ "github.com/docker/go-connections/nat"
+ "gotest.tools/v3/assert"
++ "gotest.tools/v3/icmd"
+ "gotest.tools/v3/skip"
+ )
+
+@@ -43,3 +49,62 @@ func TestCreateWithMultiNetworks(t *testing.T) {
+ ifacesWithAddress := strings.Count(res.Stdout.String(), "\n")
+ assert.Equal(t, ifacesWithAddress, 3)
+ }
++
++// TestFirewalldReloadNoZombies checks that when firewalld is reloaded, rules
++// belonging to deleted networks/containers do not reappear.
++func TestFirewalldReloadNoZombies(t *testing.T) {
++ skip.If(t, testEnv.DaemonInfo.OSType == "windows")
++ skip.If(t, !networking.FirewalldRunning(), "firewalld is not running")
++ skip.If(t, testEnv.IsRootless, "no firewalld in rootless netns")
++
++ ctx := setupTest(t)
++ d := daemon.New(t)
++ d.StartWithBusybox(ctx, t)
++ defer d.Stop(t)
++ c := d.NewClientT(t)
++
++ const bridgeName = "br-fwdreload"
++ removed := false
++ nw := network.CreateNoError(ctx, t, c, "testnet",
++ network.WithOption(bridge.BridgeName, bridgeName))
++ defer func() {
++ if !removed {
++ network.RemoveNoError(ctx, t, c, nw)
++ }
++ }()
++
++ cid := ctr.Run(ctx, t, c,
++ ctr.WithExposedPorts("80/tcp", "81/tcp"),
++ ctr.WithPortMap(nat.PortMap{"80/tcp": {{HostPort: "8000"}}}))
++ defer func() {
++ if !removed {
++ ctr.Remove(ctx, t, c, cid, containertypes.RemoveOptions{Force: true})
++ }
++ }()
++
++ iptablesSave := icmd.Command("iptables-save")
++ resBeforeDel := icmd.RunCmd(iptablesSave)
++ assert.NilError(t, resBeforeDel.Error)
++ assert.Check(t, strings.Contains(resBeforeDel.Combined(), bridgeName),
++ "With container: expected rules for %s in: %s", bridgeName, resBeforeDel.Combined())
++
++ // Delete the container and its network.
++ ctr.Remove(ctx, t, c, cid, containertypes.RemoveOptions{Force: true})
++ network.RemoveNoError(ctx, t, c, nw)
++ removed = true
++
++ // Check the network does not appear in iptables rules.
++ resAfterDel := icmd.RunCmd(iptablesSave)
++ assert.NilError(t, resAfterDel.Error)
++ assert.Check(t, !strings.Contains(resAfterDel.Combined(), bridgeName),
++ "After deletes: did not expect rules for %s in: %s", bridgeName, resAfterDel.Combined())
++
++ // firewall-cmd --reload, and wait for the daemon to restore rules.
++ networking.FirewalldReload(t, d)
++
++ // Check that rules for the deleted container/network have not reappeared.
++ resAfterReload := icmd.RunCmd(iptablesSave)
++ assert.NilError(t, resAfterReload.Error)
++ assert.Check(t, !strings.Contains(resAfterReload.Combined(), bridgeName),
++ "After deletes: did not expect rules for %s in: %s", bridgeName, resAfterReload.Combined())
++}
+diff --git a/integration/networking/bridge_test.go b/integration/networking/bridge_test.go
+index 8f54234406..8cdcf02096 100644
+--- a/integration/networking/bridge_test.go
++++ b/integration/networking/bridge_test.go
+@@ -10,6 +10,7 @@ import (
+ containertypes "github.com/docker/docker/api/types/container"
+ "github.com/docker/docker/integration/internal/container"
+ "github.com/docker/docker/integration/internal/network"
++ "github.com/docker/docker/internal/testutils/networking"
+ "github.com/docker/docker/testutil"
+ "github.com/docker/docker/testutil/daemon"
+ "gotest.tools/v3/assert"
+@@ -160,6 +161,8 @@ func TestBridgeICC(t *testing.T) {
+ Force: true,
+ })
+
++ networking.FirewalldReload(t, d)
++
+ pingHost := tc.pingHost
+ if pingHost == "" {
+ if tc.linkLocal {
+@@ -515,6 +518,7 @@ func TestInternalNwConnectivity(t *testing.T) {
+ container.WithNetworkMode(bridgeName),
+ )
+ defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
++ networking.FirewalldReload(t, d)
+
+ execCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
+ defer cancel()
+diff --git a/internal/testutils/networking/iptables.go b/internal/testutils/networking/iptables.go
+index 2e6ada8363..2041e507c3 100644
+--- a/internal/testutils/networking/iptables.go
++++ b/internal/testutils/networking/iptables.go
+@@ -1,12 +1,60 @@
+ package networking
+
+ import (
++ "fmt"
+ "os/exec"
++ "regexp"
+ "strings"
+ "testing"
++ "time"
++
++ "github.com/docker/docker/testutil/daemon"
++ "golang.org/x/net/context"
++ "gotest.tools/v3/assert"
++ "gotest.tools/v3/icmd"
++ "gotest.tools/v3/poll"
+ )
+
+ func FirewalldRunning() bool {
+ state, err := exec.Command("firewall-cmd", "--state").CombinedOutput()
+ return err == nil && strings.TrimSpace(string(state)) == "running"
+ }
++
++func extractLogTime(s string) (time.Time, error) {
++ // time="2025-07-15T13:46:13.414214418Z" level=info msg=""
++ re := regexp.MustCompile(`time="([^"]+)"`)
++ matches := re.FindStringSubmatch(s)
++ if len(matches) < 2 {
++ return time.Time{}, fmt.Errorf("timestamp not found in log line: %s, matches: %+v", s, matches)
++ }
++
++ return time.Parse(time.RFC3339Nano, matches[1])
++}
++
++// FirewalldReload reloads firewalld and waits for the daemon to re-create its rules.
++// It's a no-op if firewalld is not running, and the test fails if the reload does
++// not complete.
++func FirewalldReload(t *testing.T, d *daemon.Daemon) {
++ t.Helper()
++ if !FirewalldRunning() {
++ return
++ }
++ timeBeforeReload := time.Now()
++ res := icmd.RunCommand("firewall-cmd", "--reload")
++ assert.NilError(t, res.Error)
++
++ ctx := context.Background()
++ poll.WaitOn(t, d.PollCheckLogs(ctx, func(s string) bool {
++ if !strings.Contains(s, "Firewalld reload completed") {
++ return false
++ }
++ lastReload, err := extractLogTime(s)
++ if err != nil {
++ return false
++ }
++ if lastReload.After(timeBeforeReload) {
++ return true
++ }
++ return false
++ }))
++}
+diff --git a/internal/testutils/networking/iptables_test.go b/internal/testutils/networking/iptables_test.go
+new file mode 100644
+index 0000000000..24a7bbb49d
+--- /dev/null
++++ b/internal/testutils/networking/iptables_test.go
+@@ -0,0 +1,47 @@
++package networking
++
++import (
++ "reflect"
++ "testing"
++ "time"
++)
++
++func Test_getTimeFromLogMsg(t *testing.T) {
++ tests := []struct {
++ name string
++ s string
++ want time.Time
++ wantErr bool
++ }{
++ {
++ name: "valid time",
++ s: `time="2025-07-15T13:46:13.414214418Z" level=info msg=""`,
++ want: time.Date(2025, 7, 15, 13, 46, 13, 414214418, time.UTC),
++ wantErr: false,
++ },
++ {
++ name: "invalid format",
++ s: `time="invalid-time-format" level=info msg=""`,
++ want: time.Time{},
++ wantErr: true,
++ },
++ {
++ name: "missing time",
++ s: `level=info msg=""`,
++ want: time.Time{},
++ wantErr: true,
++ },
++ }
++ for _, tt := range tests {
++ t.Run(tt.name, func(t *testing.T) {
++ got, err := extractLogTime(tt.s)
++ if (err != nil) != tt.wantErr {
++ t.Errorf("getTimeFromLogMsg() error = %v, wantErr %v", err, tt.wantErr)
++ return
++ }
++ if !reflect.DeepEqual(got, tt.want) {
++ t.Errorf("getTimeFromLogMsg() got = %v, want %v", got, tt.want)
++ }
++ })
++ }
++}
+diff --git a/libnetwork/drivers/bridge/bridge_linux.go b/libnetwork/drivers/bridge/bridge_linux.go
+index 005c726f2d..2b313c85bf 100644
+--- a/libnetwork/drivers/bridge/bridge_linux.go
++++ b/libnetwork/drivers/bridge/bridge_linux.go
+@@ -483,6 +483,8 @@ func (d *driver) configure(option map[string]interface{}) error {
+ d.config = config
+ d.Unlock()
+
++ iptables.OnReloaded(d.handleFirewalldReload)
++
+ return d.initStore(option)
+ }
+
+@@ -800,12 +802,6 @@ func (d *driver) createNetwork(config *networkConfiguration) (err error) {
+ // Setup IP6Tables.
+ {config.EnableIPv6 && d.config.EnableIP6Tables, network.setupIP6Tables},
+
+- // We want to track firewalld configuration so that
+- // if it is started/reloaded, the rules can be applied correctly
+- {d.config.EnableIPTables, network.setupFirewalld},
+- // same for IPv6
+- {config.EnableIPv6 && d.config.EnableIP6Tables, network.setupFirewalld6},
+-
+ // Setup DefaultGatewayIPv4
+ {config.DefaultGatewayIPv4 != nil, setupGatewayIPv4},
+
+@@ -1297,6 +1293,11 @@ func (d *driver) Leave(nid, eid string) error {
+ }
+
+ func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
++ // Make sure the network isn't deleted, or the in middle of a firewalld reload, while
++ // updating its iptables rules.
++ d.configNetwork.Lock()
++ defer d.configNetwork.Unlock()
++
+ network, err := d.getNetwork(nid)
+ if err != nil {
+ return err
+@@ -1348,6 +1349,11 @@ func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string
+ }
+
+ func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
++ // Make sure this function isn't deleting iptables rules while handleFirewalldReloadNw
++ // is restoring those same rules.
++ d.configNetwork.Lock()
++ defer d.configNetwork.Unlock()
++
+ network, err := d.getNetwork(nid)
+ if err != nil {
+ return err
+@@ -1381,6 +1387,53 @@ func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+ return nil
+ }
+
++func (d *driver) handleFirewalldReload() {
++ if !d.config.EnableIPTables && !d.config.EnableIP6Tables {
++ return
++ }
++
++ d.Lock()
++ nids := make([]string, 0, len(d.networks))
++ for _, nw := range d.networks {
++ nids = append(nids, nw.id)
++ }
++ d.Unlock()
++
++ for _, nid := range nids {
++ d.handleFirewalldReloadNw(nid)
++ }
++}
++
++func (d *driver) handleFirewalldReloadNw(nid string) {
++ // Make sure the network isn't being deleted, and ProgramExternalConnectivity/RevokeExternalConnectivity
++ // aren't modifying iptables rules, while restoring the rules.
++ d.configNetwork.Lock()
++ defer d.configNetwork.Unlock()
++
++ nw, err := d.getNetwork(nid)
++ if err != nil {
++ return
++ }
++ if d.config.EnableIPTables {
++ if err := nw.setupIP4Tables(nw.config, nw.bridge); err != nil {
++ log.G(context.TODO()).WithFields(log.Fields{
++ "network": nw.id,
++ "error": err,
++ }).Warn("Failed to restore IPv4 per-port iptables rules on firewalld reload")
++ }
++ }
++ if d.config.EnableIP6Tables {
++ if err := nw.setupIP6Tables(nw.config, nw.bridge); err != nil {
++ log.G(context.TODO()).WithFields(log.Fields{
++ "network": nw.id,
++ "error": err,
++ }).Warn("Failed to restore IPv6 per-port iptables rules on firewalld reload")
++ }
++ }
++ nw.portMapper.ReMapAll()
++ log.G(context.TODO()).Info("Restored iptables rules on firewalld reload")
++}
++
+ func (d *driver) link(network *bridgeNetwork, endpoint *bridgeEndpoint, enable bool) (retErr error) {
+ cc := endpoint.containerConfig
+ ec := endpoint.extConnConfig
+diff --git a/libnetwork/drivers/bridge/setup_firewalld.go b/libnetwork/drivers/bridge/setup_firewalld.go
+deleted file mode 100644
+index db7843847c..0000000000
+--- a/libnetwork/drivers/bridge/setup_firewalld.go
++++ /dev/null
+@@ -1,41 +0,0 @@
+-//go:build linux
+-
+-package bridge
+-
+-import (
+- "errors"
+-
+- "github.com/docker/docker/libnetwork/iptables"
+-)
+-
+-func (n *bridgeNetwork) setupFirewalld(config *networkConfiguration, i *bridgeInterface) error {
+- d := n.driver
+- d.Lock()
+- driverConfig := d.config
+- d.Unlock()
+-
+- // Sanity check.
+- if !driverConfig.EnableIPTables {
+- return errors.New("no need to register firewalld hooks, iptables is disabled")
+- }
+-
+- iptables.OnReloaded(func() { n.setupIP4Tables(config, i) })
+- iptables.OnReloaded(n.portMapper.ReMapAll)
+- return nil
+-}
+-
+-func (n *bridgeNetwork) setupFirewalld6(config *networkConfiguration, i *bridgeInterface) error {
+- d := n.driver
+- d.Lock()
+- driverConfig := d.config
+- d.Unlock()
+-
+- // Sanity check.
+- if !driverConfig.EnableIP6Tables {
+- return errors.New("no need to register firewalld hooks, ip6tables is disabled")
+- }
+-
+- iptables.OnReloaded(func() { n.setupIP6Tables(config, i) })
+- iptables.OnReloaded(n.portMapperV6.ReMapAll)
+- return nil
+-}
+diff --git a/libnetwork/drivers/bridge/setup_ip_tables_linux.go b/libnetwork/drivers/bridge/setup_ip_tables_linux.go
+index 328c58bced..42039e24ee 100644
+--- a/libnetwork/drivers/bridge/setup_ip_tables_linux.go
++++ b/libnetwork/drivers/bridge/setup_ip_tables_linux.go
+@@ -130,6 +130,9 @@ func (n *bridgeNetwork) setupIP6Tables(config *networkConfiguration, i *bridgeIn
+ return errors.New("Cannot program chains, EnableIP6Tables is disabled")
+ }
+
++ if i.bridgeIPv6 == nil {
++ return nil
++ }
+ maskedAddrv6 := &net.IPNet{
+ IP: i.bridgeIPv6.IP.Mask(i.bridgeIPv6.Mask),
+ Mask: i.bridgeIPv6.Mask,
+diff --git a/libnetwork/iptables/firewalld.go b/libnetwork/iptables/firewalld.go
+index dc67240da6..d3b76426e5 100644
+--- a/libnetwork/iptables/firewalld.go
++++ b/libnetwork/iptables/firewalld.go
+@@ -132,6 +132,7 @@ func reloaded() {
+ for _, pf := range onReloaded {
+ (*pf)()
+ }
++ log.G(context.TODO()).Info("Firewalld reload completed")
+ }
+
+ // OnReloaded add callback
+--
+2.44.3
diff --git a/recipes-containers/docker/files/CVE-2025-54410.patch b/recipes-containers/docker/files/CVE-2025-54410.patch
new file mode 100644
index 00000000..b1aa211c
--- /dev/null
+++ b/recipes-containers/docker/files/CVE-2025-54410.patch
@@ -0,0 +1,50 @@
+From a4b2c5a774213eb6594cdece17680e16961c8fcb Mon Sep 17 00:00:00 2001
+From: Rob Murray <rob.murray@docker.com>
+Date: Fri, 14 Feb 2025 16:50:43 +0000
+Subject: [PATCH 4/4] Restore INC iptables rules on firewalld reload
+
+CVE: CVE-2025-54410
+Upstream-Status: Backport [https://github.com/moby/moby/commit/651b2feb2731]
+
+Signed-off-by: Rob Murray <rob.murray@docker.com>
+(cherry picked from commit 651b2feb27316cf907173c2a76cc6eb85f763663)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ integration/networking/bridge_test.go | 1 +
+ libnetwork/drivers/bridge/bridge_linux.go | 9 +++++++++
+ 2 files changed, 10 insertions(+)
+
+diff --git a/integration/networking/bridge_test.go b/integration/networking/bridge_test.go
+index 8cdcf02096..7c88cd8b43 100644
+--- a/integration/networking/bridge_test.go
++++ b/integration/networking/bridge_test.go
+@@ -294,6 +294,7 @@ func TestBridgeINC(t *testing.T) {
+ defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{
+ Force: true,
+ })
++ networking.FirewalldReload(t, d)
+
+ ctr1Info := container.Inspect(ctx, t, c, id1)
+ targetAddr := ctr1Info.NetworkSettings.Networks[bridge1].IPAddress
+diff --git a/libnetwork/drivers/bridge/bridge_linux.go b/libnetwork/drivers/bridge/bridge_linux.go
+index 2b313c85bf..a8a12aebe3 100644
+--- a/libnetwork/drivers/bridge/bridge_linux.go
++++ b/libnetwork/drivers/bridge/bridge_linux.go
+@@ -1431,6 +1431,15 @@ func (d *driver) handleFirewalldReloadNw(nid string) {
+ }
+ }
+ nw.portMapper.ReMapAll()
++
++ // Restore the inter-network connectivity (INC) rules.
++ if err := nw.isolateNetwork(true); err != nil {
++ log.G(context.TODO()).WithFields(log.Fields{
++ "network": nw.id,
++ "error": err,
++ }).Warn("Failed to restore inter-network iptables rules on firewalld reload")
++ }
++
+ log.G(context.TODO()).Info("Restored iptables rules on firewalld reload")
+ }
+
+--
+2.44.3
--
2.44.3
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [meta-virtualization] [scarthgap] [PATCH] docker-moby: Fix CVE-2025-54410
2025-09-08 7:09 [meta-virtualization] [scarthgap] [PATCH] docker-moby: Fix CVE-2025-54410 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2025-09-19 2:34 ` Bruce Ashfield
0 siblings, 0 replies; 2+ messages in thread
From: Bruce Ashfield @ 2025-09-19 2:34 UTC (permalink / raw)
To: Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
Cc: meta-virtualization, vchavda
In message: [meta-virtualization] [scarthgap] [PATCH] docker-moby: Fix CVE-2025-54410
on 08/09/2025 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco) wrote:
> From: Deepak Rathore <deeratho@cisco.com>
>
> Upstream Repository: https://github.com/moby/moby.git
>
> Bug Details: https://nvd.nist.gov/vuln/detail/CVE-2025-54410
> Type: Security Fix
> CVE: CVE-2025-54410
> Score: 3.3
> Patch:
> [1] https://github.com/moby/moby/commit/a012739c2cf3
> [2] https://github.com/moby/moby/commit/c761353e7c75
> [3] https://github.com/moby/moby/commit/41f080df250a
> [4] https://github.com/moby/moby/commit/651b2feb2731
>
> Note:
> - The main fix is implemented in [4], which is part of version 25.0.
> - Commits [1], [2], and [3] are dependent patches also included in v25.0.
> - The FirewalldReload() function, essential for the fix in [4], was
> introduced in [3].
> - Commit [1] adds the iptables.go file, enabling the implementation of
> FirewalldReload() in [3].
> - The TestInternalNwConnectivity() test was first introduced in [2] and
> later updated in [3] to validate the firewall reload behavior.
This isn't answering the right question. scarthgap is on the
25.x series.
Are these fixes not integrated into that stable release ?
A very quick check shows they are:
% git tag --contains a012739c2cf3
v25.0.13
So we should not be doing these as patches, we should be
bumping the version of docker-moby.
Bruce
>
> Signed-off-by: Deepak Rathore <deeratho@cisco.com>
> ---
> recipes-containers/docker/docker-moby_git.bb | 4 +
> .../files/CVE-2025-54410-dependent_p1.patch | 38 ++
> .../files/CVE-2025-54410-dependent_p2.patch | 143 ++++++
> .../files/CVE-2025-54410-dependent_p3.patch | 451 ++++++++++++++++++
> .../docker/files/CVE-2025-54410.patch | 50 ++
> 5 files changed, 686 insertions(+)
> create mode 100644 recipes-containers/docker/files/CVE-2025-54410-dependent_p1.patch
> create mode 100644 recipes-containers/docker/files/CVE-2025-54410-dependent_p2.patch
> create mode 100644 recipes-containers/docker/files/CVE-2025-54410-dependent_p3.patch
> create mode 100644 recipes-containers/docker/files/CVE-2025-54410.patch
>
> diff --git a/recipes-containers/docker/docker-moby_git.bb b/recipes-containers/docker/docker-moby_git.bb
> index d274b002..e4791319 100644
> --- a/recipes-containers/docker/docker-moby_git.bb
> +++ b/recipes-containers/docker/docker-moby_git.bb
> @@ -58,6 +58,10 @@ SRC_URI = "\
> file://0001-dynbinary-use-go-cross-compiler.patch;patchdir=src/import \
> file://CVE-2024-36620.patch;patchdir=src/import \
> file://CVE-2024-36621.patch;patchdir=src/import \
> + file://CVE-2025-54410-dependent_p1.patch;patchdir=src/import \
> + file://CVE-2025-54410-dependent_p2.patch;patchdir=src/import \
> + file://CVE-2025-54410-dependent_p3.patch;patchdir=src/import \
> + file://CVE-2025-54410.patch;patchdir=src/import \
> "
>
> DOCKER_COMMIT = "${SRCREV_moby}"
> diff --git a/recipes-containers/docker/files/CVE-2025-54410-dependent_p1.patch b/recipes-containers/docker/files/CVE-2025-54410-dependent_p1.patch
> new file mode 100644
> index 00000000..f1a7db3d
> --- /dev/null
> +++ b/recipes-containers/docker/files/CVE-2025-54410-dependent_p1.patch
> @@ -0,0 +1,38 @@
> +From 4ff88c2b9d85e162676fd54b773a4a3957b5c622 Mon Sep 17 00:00:00 2001
> +From: Rob Murray <rob.murray@docker.com>
> +Date: Wed, 26 Mar 2025 14:32:15 +0000
> +Subject: [PATCH 1/4] Add test util "FirewalldRunning"
> +
> +CVE: CVE-2025-54410
> +Upstream-Status: Backport [https://github.com/moby/moby/commit/a012739c2cf3]
> +
> +Signed-off-by: Rob Murray <rob.murray@docker.com>
> +(cherry picked from commit b8cacdf324c8309fb0555381889aa867391ba9fb)
> +Signed-off-by: Andrey Epifanov <aepifanov@mirantis.com>
> +(cherry picked from commit a012739c2cf347847d83ea77e30333528602c8a6)
> +Signed-off-by: Deepak Rathore <deeratho@cisco.com>
> +---
> + internal/testutils/networking/iptables.go | 12 ++++++++++++
> + 1 file changed, 12 insertions(+)
> + create mode 100644 internal/testutils/networking/iptables.go
> +
> +diff --git a/internal/testutils/networking/iptables.go b/internal/testutils/networking/iptables.go
> +new file mode 100644
> +index 0000000000..2e6ada8363
> +--- /dev/null
> ++++ b/internal/testutils/networking/iptables.go
> +@@ -0,0 +1,12 @@
> ++package networking
> ++
> ++import (
> ++ "os/exec"
> ++ "strings"
> ++ "testing"
> ++)
> ++
> ++func FirewalldRunning() bool {
> ++ state, err := exec.Command("firewall-cmd", "--state").CombinedOutput()
> ++ return err == nil && strings.TrimSpace(string(state)) == "running"
> ++}
> +--
> +2.44.3
> diff --git a/recipes-containers/docker/files/CVE-2025-54410-dependent_p2.patch b/recipes-containers/docker/files/CVE-2025-54410-dependent_p2.patch
> new file mode 100644
> index 00000000..e376338f
> --- /dev/null
> +++ b/recipes-containers/docker/files/CVE-2025-54410-dependent_p2.patch
> @@ -0,0 +1,143 @@
> +From 11125e7362281772f6d8cebaee3d8a1c07456e31 Mon Sep 17 00:00:00 2001
> +From: Rob Murray <rob.murray@docker.com>
> +Date: Wed, 7 Feb 2024 18:53:27 +0000
> +Subject: [PATCH 2/4] Make 'internal' bridge networks accessible from host
> +
> +Prior to release 25.0.0, the bridge in an internal network was assigned
> +an IP address - making the internal network accessible from the host,
> +giving containers on the network access to anything listening on the
> +bridge's address (or INADDR_ANY on the host).
> +
> +This change restores that behaviour. It does not restore the default
> +route that was configured in the container, because packets sent outside
> +the internal network's subnet have always been dropped. So, a 'connect()'
> +to an address outside the subnet will still fail fast.
> +
> +CVE: CVE-2025-54410
> +Upstream-Status: Backport [https://github.com/moby/moby/commit/c761353e7c75]
> +
> +Signed-off-by: Rob Murray <rob.murray@docker.com>
> +(cherry picked from commit 419f5a637230570ec44c32f889f981f67ce15793)
> +Signed-off-by: Albin Kerouanton <albinker@gmail.com>
> +(cherry picked from commit c761353e7c75e4ffa90b80f3b5df40473d59620a)
> +Signed-off-by: Deepak Rathore <deeratho@cisco.com>
> +---
> + integration/networking/bridge_test.go | 57 +++++++++++++++++++
> + libnetwork/drivers/bridge/setup_ipv4_linux.go | 10 ++--
> + .../drivers/bridge/setup_verify_linux.go | 4 +-
> + 3 files changed, 63 insertions(+), 8 deletions(-)
> +
> +diff --git a/integration/networking/bridge_test.go b/integration/networking/bridge_test.go
> +index e3d1fe2a36..8f54234406 100644
> +--- a/integration/networking/bridge_test.go
> ++++ b/integration/networking/bridge_test.go
> +@@ -477,3 +477,60 @@ func TestDefaultBridgeAddresses(t *testing.T) {
> + })
> + }
> + }
> ++
> ++// Test that a container on an 'internal' network has IP connectivity with
> ++// the host (on its own subnet, because the n/w bridge has an address on that
> ++// subnet, and it's in the host's namespace).
> ++// Regression test for https://github.com/moby/moby/issues/47329
> ++func TestInternalNwConnectivity(t *testing.T) {
> ++ skip.If(t, testEnv.DaemonInfo.OSType == "windows")
> ++
> ++ ctx := setupTest(t)
> ++
> ++ d := daemon.New(t)
> ++ d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables")
> ++ defer d.Stop(t)
> ++
> ++ c := d.NewClientT(t)
> ++ defer c.Close()
> ++
> ++ const bridgeName = "intnw"
> ++ const gw4 = "172.30.0.1"
> ++ const gw6 = "fda9:4130:4715::1234"
> ++ network.CreateNoError(ctx, t, c, bridgeName,
> ++ network.WithInternal(),
> ++ network.WithIPv6(),
> ++ network.WithIPAM("172.30.0.0/24", gw4),
> ++ network.WithIPAM("fda9:4130:4715::/64", gw6),
> ++ network.WithDriver("bridge"),
> ++ network.WithOption("com.docker.network.bridge.name", bridgeName),
> ++ )
> ++ defer network.RemoveNoError(ctx, t, c, bridgeName)
> ++
> ++ const ctrName = "intctr"
> ++ id := container.Run(ctx, t, c,
> ++ container.WithName(ctrName),
> ++ container.WithImage("busybox:latest"),
> ++ container.WithCmd("top"),
> ++ container.WithNetworkMode(bridgeName),
> ++ )
> ++ defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
> ++
> ++ execCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
> ++ defer cancel()
> ++
> ++ res := container.ExecT(execCtx, t, c, id, []string{"ping", "-c1", "-W3", gw4})
> ++ assert.Check(t, is.Equal(res.ExitCode, 0))
> ++ assert.Check(t, is.Equal(res.Stderr(), ""))
> ++ assert.Check(t, is.Contains(res.Stdout(), "1 packets transmitted, 1 packets received"))
> ++
> ++ res = container.ExecT(execCtx, t, c, id, []string{"ping6", "-c1", "-W3", gw6})
> ++ assert.Check(t, is.Equal(res.ExitCode, 0))
> ++ assert.Check(t, is.Equal(res.Stderr(), ""))
> ++ assert.Check(t, is.Contains(res.Stdout(), "1 packets transmitted, 1 packets received"))
> ++
> ++ // Addresses outside the internal subnet must not be accessible.
> ++ res = container.ExecT(execCtx, t, c, id, []string{"ping", "-c1", "-W3", "8.8.8.8"})
> ++ assert.Check(t, is.Equal(res.ExitCode, 1))
> ++ assert.Check(t, is.Contains(res.Stderr(), "Network is unreachable"))
> ++}
> +diff --git a/libnetwork/drivers/bridge/setup_ipv4_linux.go b/libnetwork/drivers/bridge/setup_ipv4_linux.go
> +index 0940745c23..6b925190ce 100644
> +--- a/libnetwork/drivers/bridge/setup_ipv4_linux.go
> ++++ b/libnetwork/drivers/bridge/setup_ipv4_linux.go
> +@@ -32,10 +32,6 @@ func setupBridgeIPv4(config *networkConfiguration, i *bridgeInterface) error {
> + // are decoupled, we should assign it only when it's really needed.
> + i.bridgeIPv4 = config.AddressIPv4
> +
> +- if config.Internal {
> +- return nil
> +- }
> +-
> + if !config.InhibitIPv4 {
> + addrv4List, err := i.addresses(netlink.FAMILY_V4)
> + if err != nil {
> +@@ -57,8 +53,10 @@ func setupBridgeIPv4(config *networkConfiguration, i *bridgeInterface) error {
> + }
> + }
> +
> +- // Store the default gateway
> +- i.gatewayIPv4 = config.AddressIPv4.IP
> ++ if !config.Internal {
> ++ // Store the default gateway
> ++ i.gatewayIPv4 = config.AddressIPv4.IP
> ++ }
> +
> + return nil
> + }
> +diff --git a/libnetwork/drivers/bridge/setup_verify_linux.go b/libnetwork/drivers/bridge/setup_verify_linux.go
> +index a39d750346..b7ddbcf814 100644
> +--- a/libnetwork/drivers/bridge/setup_verify_linux.go
> ++++ b/libnetwork/drivers/bridge/setup_verify_linux.go
> +@@ -20,12 +20,12 @@ func setupVerifyAndReconcileIPv4(config *networkConfiguration, i *bridgeInterfac
> + addrv4, _ := selectIPv4Address(addrsv4, config.AddressIPv4)
> +
> + // Verify that the bridge has an IPv4 address.
> +- if !config.Internal && addrv4.IPNet == nil {
> ++ if addrv4.IPNet == nil {
> + return &ErrNoIPAddr{}
> + }
> +
> + // Verify that the bridge IPv4 address matches the requested configuration.
> +- if config.AddressIPv4 != nil && addrv4.IPNet != nil && !addrv4.IP.Equal(config.AddressIPv4.IP) {
> ++ if config.AddressIPv4 != nil && !addrv4.IP.Equal(config.AddressIPv4.IP) {
> + return &IPv4AddrNoMatchError{IP: addrv4.IP, CfgIP: config.AddressIPv4.IP}
> + }
> +
> +--
> +2.44.3
> diff --git a/recipes-containers/docker/files/CVE-2025-54410-dependent_p3.patch b/recipes-containers/docker/files/CVE-2025-54410-dependent_p3.patch
> new file mode 100644
> index 00000000..16d4768f
> --- /dev/null
> +++ b/recipes-containers/docker/files/CVE-2025-54410-dependent_p3.patch
> @@ -0,0 +1,451 @@
> +From 211e9acdef48b1229591724ba83fe4b6ad86c7b6 Mon Sep 17 00:00:00 2001
> +From: Rob Murray <rob.murray@docker.com>
> +Date: Fri, 14 Feb 2025 16:23:49 +0000
> +Subject: [PATCH 3/4] Restore iptables for current networks on firewalld reload
> +
> +Using iptables.OnReloaded to restore individual per-network rules
> +on firewalld reload means rules for deleted networks pop back in
> +to existence (because there was no way to delete the callbacks on
> +network-delete).
> +
> +So, on firewalld reload, walk over current networks and ask them
> +to restore their iptables rules.
> +
> +CVE: CVE-2025-54410
> +Upstream-Status: Backport [https://github.com/moby/moby/commit/41f080df250a]
> +
> +Signed-off-by: Rob Murray <rob.murray@docker.com>
> +(cherry picked from commit a527e5a546526117a0f3d7a33f3fcb8f4cd87c72)
> +
> +Test that firewalld reload doesn't re-create deleted iptables rules
> +
> +Signed-off-by: Rob Murray <rob.murray@docker.com>
> +(cherry picked from commit c3fa7c17794feaf4e27238aa3c6de42c606765fa)
> +
> +Signed-off-by: Andrey Epifanov <aepifanov@mirantis.com>
> +(cherry picked from commit 41f080df250aef805d34628d1719c369e9ff1585)
> +Signed-off-by: Deepak Rathore <deeratho@cisco.com>
> +---
> + integration/network/bridge_test.go | 65 +++++++++++++++++++
> + integration/networking/bridge_test.go | 4 ++
> + internal/testutils/networking/iptables.go | 48 ++++++++++++++
> + .../testutils/networking/iptables_test.go | 47 ++++++++++++++
> + libnetwork/drivers/bridge/bridge_linux.go | 65 +++++++++++++++++--
> + libnetwork/drivers/bridge/setup_firewalld.go | 41 ------------
> + .../drivers/bridge/setup_ip_tables_linux.go | 3 +
> + libnetwork/iptables/firewalld.go | 1 +
> + 8 files changed, 227 insertions(+), 47 deletions(-)
> + create mode 100644 internal/testutils/networking/iptables_test.go
> + delete mode 100644 libnetwork/drivers/bridge/setup_firewalld.go
> +
> +diff --git a/integration/network/bridge_test.go b/integration/network/bridge_test.go
> +index b09ff5766c..09b7da9c2b 100644
> +--- a/integration/network/bridge_test.go
> ++++ b/integration/network/bridge_test.go
> +@@ -6,11 +6,17 @@ import (
> + "testing"
> + "time"
> +
> ++ containertypes "github.com/docker/docker/api/types/container"
> + networktypes "github.com/docker/docker/api/types/network"
> + "github.com/docker/docker/api/types/versions"
> + ctr "github.com/docker/docker/integration/internal/container"
> + "github.com/docker/docker/integration/internal/network"
> ++ "github.com/docker/docker/internal/testutils/networking"
> ++ "github.com/docker/docker/libnetwork/drivers/bridge"
> ++ "github.com/docker/docker/testutil/daemon"
> ++ "github.com/docker/go-connections/nat"
> + "gotest.tools/v3/assert"
> ++ "gotest.tools/v3/icmd"
> + "gotest.tools/v3/skip"
> + )
> +
> +@@ -43,3 +49,62 @@ func TestCreateWithMultiNetworks(t *testing.T) {
> + ifacesWithAddress := strings.Count(res.Stdout.String(), "\n")
> + assert.Equal(t, ifacesWithAddress, 3)
> + }
> ++
> ++// TestFirewalldReloadNoZombies checks that when firewalld is reloaded, rules
> ++// belonging to deleted networks/containers do not reappear.
> ++func TestFirewalldReloadNoZombies(t *testing.T) {
> ++ skip.If(t, testEnv.DaemonInfo.OSType == "windows")
> ++ skip.If(t, !networking.FirewalldRunning(), "firewalld is not running")
> ++ skip.If(t, testEnv.IsRootless, "no firewalld in rootless netns")
> ++
> ++ ctx := setupTest(t)
> ++ d := daemon.New(t)
> ++ d.StartWithBusybox(ctx, t)
> ++ defer d.Stop(t)
> ++ c := d.NewClientT(t)
> ++
> ++ const bridgeName = "br-fwdreload"
> ++ removed := false
> ++ nw := network.CreateNoError(ctx, t, c, "testnet",
> ++ network.WithOption(bridge.BridgeName, bridgeName))
> ++ defer func() {
> ++ if !removed {
> ++ network.RemoveNoError(ctx, t, c, nw)
> ++ }
> ++ }()
> ++
> ++ cid := ctr.Run(ctx, t, c,
> ++ ctr.WithExposedPorts("80/tcp", "81/tcp"),
> ++ ctr.WithPortMap(nat.PortMap{"80/tcp": {{HostPort: "8000"}}}))
> ++ defer func() {
> ++ if !removed {
> ++ ctr.Remove(ctx, t, c, cid, containertypes.RemoveOptions{Force: true})
> ++ }
> ++ }()
> ++
> ++ iptablesSave := icmd.Command("iptables-save")
> ++ resBeforeDel := icmd.RunCmd(iptablesSave)
> ++ assert.NilError(t, resBeforeDel.Error)
> ++ assert.Check(t, strings.Contains(resBeforeDel.Combined(), bridgeName),
> ++ "With container: expected rules for %s in: %s", bridgeName, resBeforeDel.Combined())
> ++
> ++ // Delete the container and its network.
> ++ ctr.Remove(ctx, t, c, cid, containertypes.RemoveOptions{Force: true})
> ++ network.RemoveNoError(ctx, t, c, nw)
> ++ removed = true
> ++
> ++ // Check the network does not appear in iptables rules.
> ++ resAfterDel := icmd.RunCmd(iptablesSave)
> ++ assert.NilError(t, resAfterDel.Error)
> ++ assert.Check(t, !strings.Contains(resAfterDel.Combined(), bridgeName),
> ++ "After deletes: did not expect rules for %s in: %s", bridgeName, resAfterDel.Combined())
> ++
> ++ // firewall-cmd --reload, and wait for the daemon to restore rules.
> ++ networking.FirewalldReload(t, d)
> ++
> ++ // Check that rules for the deleted container/network have not reappeared.
> ++ resAfterReload := icmd.RunCmd(iptablesSave)
> ++ assert.NilError(t, resAfterReload.Error)
> ++ assert.Check(t, !strings.Contains(resAfterReload.Combined(), bridgeName),
> ++ "After deletes: did not expect rules for %s in: %s", bridgeName, resAfterReload.Combined())
> ++}
> +diff --git a/integration/networking/bridge_test.go b/integration/networking/bridge_test.go
> +index 8f54234406..8cdcf02096 100644
> +--- a/integration/networking/bridge_test.go
> ++++ b/integration/networking/bridge_test.go
> +@@ -10,6 +10,7 @@ import (
> + containertypes "github.com/docker/docker/api/types/container"
> + "github.com/docker/docker/integration/internal/container"
> + "github.com/docker/docker/integration/internal/network"
> ++ "github.com/docker/docker/internal/testutils/networking"
> + "github.com/docker/docker/testutil"
> + "github.com/docker/docker/testutil/daemon"
> + "gotest.tools/v3/assert"
> +@@ -160,6 +161,8 @@ func TestBridgeICC(t *testing.T) {
> + Force: true,
> + })
> +
> ++ networking.FirewalldReload(t, d)
> ++
> + pingHost := tc.pingHost
> + if pingHost == "" {
> + if tc.linkLocal {
> +@@ -515,6 +518,7 @@ func TestInternalNwConnectivity(t *testing.T) {
> + container.WithNetworkMode(bridgeName),
> + )
> + defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
> ++ networking.FirewalldReload(t, d)
> +
> + execCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
> + defer cancel()
> +diff --git a/internal/testutils/networking/iptables.go b/internal/testutils/networking/iptables.go
> +index 2e6ada8363..2041e507c3 100644
> +--- a/internal/testutils/networking/iptables.go
> ++++ b/internal/testutils/networking/iptables.go
> +@@ -1,12 +1,60 @@
> + package networking
> +
> + import (
> ++ "fmt"
> + "os/exec"
> ++ "regexp"
> + "strings"
> + "testing"
> ++ "time"
> ++
> ++ "github.com/docker/docker/testutil/daemon"
> ++ "golang.org/x/net/context"
> ++ "gotest.tools/v3/assert"
> ++ "gotest.tools/v3/icmd"
> ++ "gotest.tools/v3/poll"
> + )
> +
> + func FirewalldRunning() bool {
> + state, err := exec.Command("firewall-cmd", "--state").CombinedOutput()
> + return err == nil && strings.TrimSpace(string(state)) == "running"
> + }
> ++
> ++func extractLogTime(s string) (time.Time, error) {
> ++ // time="2025-07-15T13:46:13.414214418Z" level=info msg=""
> ++ re := regexp.MustCompile(`time="([^"]+)"`)
> ++ matches := re.FindStringSubmatch(s)
> ++ if len(matches) < 2 {
> ++ return time.Time{}, fmt.Errorf("timestamp not found in log line: %s, matches: %+v", s, matches)
> ++ }
> ++
> ++ return time.Parse(time.RFC3339Nano, matches[1])
> ++}
> ++
> ++// FirewalldReload reloads firewalld and waits for the daemon to re-create its rules.
> ++// It's a no-op if firewalld is not running, and the test fails if the reload does
> ++// not complete.
> ++func FirewalldReload(t *testing.T, d *daemon.Daemon) {
> ++ t.Helper()
> ++ if !FirewalldRunning() {
> ++ return
> ++ }
> ++ timeBeforeReload := time.Now()
> ++ res := icmd.RunCommand("firewall-cmd", "--reload")
> ++ assert.NilError(t, res.Error)
> ++
> ++ ctx := context.Background()
> ++ poll.WaitOn(t, d.PollCheckLogs(ctx, func(s string) bool {
> ++ if !strings.Contains(s, "Firewalld reload completed") {
> ++ return false
> ++ }
> ++ lastReload, err := extractLogTime(s)
> ++ if err != nil {
> ++ return false
> ++ }
> ++ if lastReload.After(timeBeforeReload) {
> ++ return true
> ++ }
> ++ return false
> ++ }))
> ++}
> +diff --git a/internal/testutils/networking/iptables_test.go b/internal/testutils/networking/iptables_test.go
> +new file mode 100644
> +index 0000000000..24a7bbb49d
> +--- /dev/null
> ++++ b/internal/testutils/networking/iptables_test.go
> +@@ -0,0 +1,47 @@
> ++package networking
> ++
> ++import (
> ++ "reflect"
> ++ "testing"
> ++ "time"
> ++)
> ++
> ++func Test_getTimeFromLogMsg(t *testing.T) {
> ++ tests := []struct {
> ++ name string
> ++ s string
> ++ want time.Time
> ++ wantErr bool
> ++ }{
> ++ {
> ++ name: "valid time",
> ++ s: `time="2025-07-15T13:46:13.414214418Z" level=info msg=""`,
> ++ want: time.Date(2025, 7, 15, 13, 46, 13, 414214418, time.UTC),
> ++ wantErr: false,
> ++ },
> ++ {
> ++ name: "invalid format",
> ++ s: `time="invalid-time-format" level=info msg=""`,
> ++ want: time.Time{},
> ++ wantErr: true,
> ++ },
> ++ {
> ++ name: "missing time",
> ++ s: `level=info msg=""`,
> ++ want: time.Time{},
> ++ wantErr: true,
> ++ },
> ++ }
> ++ for _, tt := range tests {
> ++ t.Run(tt.name, func(t *testing.T) {
> ++ got, err := extractLogTime(tt.s)
> ++ if (err != nil) != tt.wantErr {
> ++ t.Errorf("getTimeFromLogMsg() error = %v, wantErr %v", err, tt.wantErr)
> ++ return
> ++ }
> ++ if !reflect.DeepEqual(got, tt.want) {
> ++ t.Errorf("getTimeFromLogMsg() got = %v, want %v", got, tt.want)
> ++ }
> ++ })
> ++ }
> ++}
> +diff --git a/libnetwork/drivers/bridge/bridge_linux.go b/libnetwork/drivers/bridge/bridge_linux.go
> +index 005c726f2d..2b313c85bf 100644
> +--- a/libnetwork/drivers/bridge/bridge_linux.go
> ++++ b/libnetwork/drivers/bridge/bridge_linux.go
> +@@ -483,6 +483,8 @@ func (d *driver) configure(option map[string]interface{}) error {
> + d.config = config
> + d.Unlock()
> +
> ++ iptables.OnReloaded(d.handleFirewalldReload)
> ++
> + return d.initStore(option)
> + }
> +
> +@@ -800,12 +802,6 @@ func (d *driver) createNetwork(config *networkConfiguration) (err error) {
> + // Setup IP6Tables.
> + {config.EnableIPv6 && d.config.EnableIP6Tables, network.setupIP6Tables},
> +
> +- // We want to track firewalld configuration so that
> +- // if it is started/reloaded, the rules can be applied correctly
> +- {d.config.EnableIPTables, network.setupFirewalld},
> +- // same for IPv6
> +- {config.EnableIPv6 && d.config.EnableIP6Tables, network.setupFirewalld6},
> +-
> + // Setup DefaultGatewayIPv4
> + {config.DefaultGatewayIPv4 != nil, setupGatewayIPv4},
> +
> +@@ -1297,6 +1293,11 @@ func (d *driver) Leave(nid, eid string) error {
> + }
> +
> + func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
> ++ // Make sure the network isn't deleted, or the in middle of a firewalld reload, while
> ++ // updating its iptables rules.
> ++ d.configNetwork.Lock()
> ++ defer d.configNetwork.Unlock()
> ++
> + network, err := d.getNetwork(nid)
> + if err != nil {
> + return err
> +@@ -1348,6 +1349,11 @@ func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string
> + }
> +
> + func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
> ++ // Make sure this function isn't deleting iptables rules while handleFirewalldReloadNw
> ++ // is restoring those same rules.
> ++ d.configNetwork.Lock()
> ++ defer d.configNetwork.Unlock()
> ++
> + network, err := d.getNetwork(nid)
> + if err != nil {
> + return err
> +@@ -1381,6 +1387,53 @@ func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
> + return nil
> + }
> +
> ++func (d *driver) handleFirewalldReload() {
> ++ if !d.config.EnableIPTables && !d.config.EnableIP6Tables {
> ++ return
> ++ }
> ++
> ++ d.Lock()
> ++ nids := make([]string, 0, len(d.networks))
> ++ for _, nw := range d.networks {
> ++ nids = append(nids, nw.id)
> ++ }
> ++ d.Unlock()
> ++
> ++ for _, nid := range nids {
> ++ d.handleFirewalldReloadNw(nid)
> ++ }
> ++}
> ++
> ++func (d *driver) handleFirewalldReloadNw(nid string) {
> ++ // Make sure the network isn't being deleted, and ProgramExternalConnectivity/RevokeExternalConnectivity
> ++ // aren't modifying iptables rules, while restoring the rules.
> ++ d.configNetwork.Lock()
> ++ defer d.configNetwork.Unlock()
> ++
> ++ nw, err := d.getNetwork(nid)
> ++ if err != nil {
> ++ return
> ++ }
> ++ if d.config.EnableIPTables {
> ++ if err := nw.setupIP4Tables(nw.config, nw.bridge); err != nil {
> ++ log.G(context.TODO()).WithFields(log.Fields{
> ++ "network": nw.id,
> ++ "error": err,
> ++ }).Warn("Failed to restore IPv4 per-port iptables rules on firewalld reload")
> ++ }
> ++ }
> ++ if d.config.EnableIP6Tables {
> ++ if err := nw.setupIP6Tables(nw.config, nw.bridge); err != nil {
> ++ log.G(context.TODO()).WithFields(log.Fields{
> ++ "network": nw.id,
> ++ "error": err,
> ++ }).Warn("Failed to restore IPv6 per-port iptables rules on firewalld reload")
> ++ }
> ++ }
> ++ nw.portMapper.ReMapAll()
> ++ log.G(context.TODO()).Info("Restored iptables rules on firewalld reload")
> ++}
> ++
> + func (d *driver) link(network *bridgeNetwork, endpoint *bridgeEndpoint, enable bool) (retErr error) {
> + cc := endpoint.containerConfig
> + ec := endpoint.extConnConfig
> +diff --git a/libnetwork/drivers/bridge/setup_firewalld.go b/libnetwork/drivers/bridge/setup_firewalld.go
> +deleted file mode 100644
> +index db7843847c..0000000000
> +--- a/libnetwork/drivers/bridge/setup_firewalld.go
> ++++ /dev/null
> +@@ -1,41 +0,0 @@
> +-//go:build linux
> +-
> +-package bridge
> +-
> +-import (
> +- "errors"
> +-
> +- "github.com/docker/docker/libnetwork/iptables"
> +-)
> +-
> +-func (n *bridgeNetwork) setupFirewalld(config *networkConfiguration, i *bridgeInterface) error {
> +- d := n.driver
> +- d.Lock()
> +- driverConfig := d.config
> +- d.Unlock()
> +-
> +- // Sanity check.
> +- if !driverConfig.EnableIPTables {
> +- return errors.New("no need to register firewalld hooks, iptables is disabled")
> +- }
> +-
> +- iptables.OnReloaded(func() { n.setupIP4Tables(config, i) })
> +- iptables.OnReloaded(n.portMapper.ReMapAll)
> +- return nil
> +-}
> +-
> +-func (n *bridgeNetwork) setupFirewalld6(config *networkConfiguration, i *bridgeInterface) error {
> +- d := n.driver
> +- d.Lock()
> +- driverConfig := d.config
> +- d.Unlock()
> +-
> +- // Sanity check.
> +- if !driverConfig.EnableIP6Tables {
> +- return errors.New("no need to register firewalld hooks, ip6tables is disabled")
> +- }
> +-
> +- iptables.OnReloaded(func() { n.setupIP6Tables(config, i) })
> +- iptables.OnReloaded(n.portMapperV6.ReMapAll)
> +- return nil
> +-}
> +diff --git a/libnetwork/drivers/bridge/setup_ip_tables_linux.go b/libnetwork/drivers/bridge/setup_ip_tables_linux.go
> +index 328c58bced..42039e24ee 100644
> +--- a/libnetwork/drivers/bridge/setup_ip_tables_linux.go
> ++++ b/libnetwork/drivers/bridge/setup_ip_tables_linux.go
> +@@ -130,6 +130,9 @@ func (n *bridgeNetwork) setupIP6Tables(config *networkConfiguration, i *bridgeIn
> + return errors.New("Cannot program chains, EnableIP6Tables is disabled")
> + }
> +
> ++ if i.bridgeIPv6 == nil {
> ++ return nil
> ++ }
> + maskedAddrv6 := &net.IPNet{
> + IP: i.bridgeIPv6.IP.Mask(i.bridgeIPv6.Mask),
> + Mask: i.bridgeIPv6.Mask,
> +diff --git a/libnetwork/iptables/firewalld.go b/libnetwork/iptables/firewalld.go
> +index dc67240da6..d3b76426e5 100644
> +--- a/libnetwork/iptables/firewalld.go
> ++++ b/libnetwork/iptables/firewalld.go
> +@@ -132,6 +132,7 @@ func reloaded() {
> + for _, pf := range onReloaded {
> + (*pf)()
> + }
> ++ log.G(context.TODO()).Info("Firewalld reload completed")
> + }
> +
> + // OnReloaded add callback
> +--
> +2.44.3
> diff --git a/recipes-containers/docker/files/CVE-2025-54410.patch b/recipes-containers/docker/files/CVE-2025-54410.patch
> new file mode 100644
> index 00000000..b1aa211c
> --- /dev/null
> +++ b/recipes-containers/docker/files/CVE-2025-54410.patch
> @@ -0,0 +1,50 @@
> +From a4b2c5a774213eb6594cdece17680e16961c8fcb Mon Sep 17 00:00:00 2001
> +From: Rob Murray <rob.murray@docker.com>
> +Date: Fri, 14 Feb 2025 16:50:43 +0000
> +Subject: [PATCH 4/4] Restore INC iptables rules on firewalld reload
> +
> +CVE: CVE-2025-54410
> +Upstream-Status: Backport [https://github.com/moby/moby/commit/651b2feb2731]
> +
> +Signed-off-by: Rob Murray <rob.murray@docker.com>
> +(cherry picked from commit 651b2feb27316cf907173c2a76cc6eb85f763663)
> +Signed-off-by: Deepak Rathore <deeratho@cisco.com>
> +---
> + integration/networking/bridge_test.go | 1 +
> + libnetwork/drivers/bridge/bridge_linux.go | 9 +++++++++
> + 2 files changed, 10 insertions(+)
> +
> +diff --git a/integration/networking/bridge_test.go b/integration/networking/bridge_test.go
> +index 8cdcf02096..7c88cd8b43 100644
> +--- a/integration/networking/bridge_test.go
> ++++ b/integration/networking/bridge_test.go
> +@@ -294,6 +294,7 @@ func TestBridgeINC(t *testing.T) {
> + defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{
> + Force: true,
> + })
> ++ networking.FirewalldReload(t, d)
> +
> + ctr1Info := container.Inspect(ctx, t, c, id1)
> + targetAddr := ctr1Info.NetworkSettings.Networks[bridge1].IPAddress
> +diff --git a/libnetwork/drivers/bridge/bridge_linux.go b/libnetwork/drivers/bridge/bridge_linux.go
> +index 2b313c85bf..a8a12aebe3 100644
> +--- a/libnetwork/drivers/bridge/bridge_linux.go
> ++++ b/libnetwork/drivers/bridge/bridge_linux.go
> +@@ -1431,6 +1431,15 @@ func (d *driver) handleFirewalldReloadNw(nid string) {
> + }
> + }
> + nw.portMapper.ReMapAll()
> ++
> ++ // Restore the inter-network connectivity (INC) rules.
> ++ if err := nw.isolateNetwork(true); err != nil {
> ++ log.G(context.TODO()).WithFields(log.Fields{
> ++ "network": nw.id,
> ++ "error": err,
> ++ }).Warn("Failed to restore inter-network iptables rules on firewalld reload")
> ++ }
> ++
> + log.G(context.TODO()).Info("Restored iptables rules on firewalld reload")
> + }
> +
> +--
> +2.44.3
> --
> 2.44.3
>
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2025-09-19 2:34 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-08 7:09 [meta-virtualization] [scarthgap] [PATCH] docker-moby: Fix CVE-2025-54410 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2025-09-19 2:34 ` Bruce Ashfield
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.