From ed2e2bd1cb6491cc76e6681db122844400762a2e Mon Sep 17 00:00:00 2001 From: lvxiangcong Date: Mon, 22 Apr 2024 10:59:42 +0800 Subject: [PATCH] fix cve-2024-32473 --- integration/network/ipvlan/ipvlan_test.go | 25 +++++++++++++++ integration/network/macvlan/macvlan_test.go | 29 +++++++++++++++++ integration/networking/bridge_test.go | 35 +++++++++++++++++++++ libnetwork/osl/interface_linux.go | 21 ++++++++----- 4 files changed, 103 insertions(+), 7 deletions(-) diff --git a/integration/network/ipvlan/ipvlan_test.go b/integration/network/ipvlan/ipvlan_test.go index 130b60d..adb42cd 100644 --- a/integration/network/ipvlan/ipvlan_test.go +++ b/integration/network/ipvlan/ipvlan_test.go @@ -87,6 +87,9 @@ func TestDockerNetworkIpvlan(t *testing.T) { }, { name: "Addressing", test: testIpvlanAddressing, + }, { + name: "NoIPv6", + test: testIpvlanNoIPv6, }, } { @@ -438,3 +441,25 @@ func ipvlanKernelSupport(t *testing.T) bool { return ipvlanSupported } + +// Check that an ipvlan interface with '--ipv6=false' doesn't get kernel-assigned +// IPv6 addresses, but the loopback interface does still have an IPv6 address ('::1'). +func testIpvlanNoIPv6(t *testing.T, ctx context.Context, client dclient.APIClient) { + const netName = "ipvlannet" + net.CreateNoError(ctx, t, client, netName, net.WithIPvlan("", "l3")) + assert.Check(t, n.IsNetworkAvailable(ctx, client, netName)) + + id := container.Run(ctx, t, client, container.WithNetworkMode(netName)) + + loRes := container.ExecT(ctx, t, client, id, []string{"ip", "a", "show", "dev", "lo"}) + assert.Check(t, is.Contains(loRes.Combined(), " inet ")) + assert.Check(t, is.Contains(loRes.Combined(), " inet6 ")) + + eth0Res := container.ExecT(ctx, t, client, id, []string{"ip", "a", "show", "dev", "eth0"}) + assert.Check(t, is.Contains(eth0Res.Combined(), " inet ")) + assert.Check(t, !strings.Contains(eth0Res.Combined(), " inet6 "), + "result.Combined(): %s", eth0Res.Combined()) + + sysctlRes := container.ExecT(ctx, t, client, id, []string{"sysctl", "-n", "net.ipv6.conf.eth0.disable_ipv6"}) + assert.Check(t, is.Equal(strings.TrimSpace(sysctlRes.Combined()), "1")) +} diff --git a/integration/network/macvlan/macvlan_test.go b/integration/network/macvlan/macvlan_test.go index c41373c..c907ffb 100644 --- a/integration/network/macvlan/macvlan_test.go +++ b/integration/network/macvlan/macvlan_test.go @@ -71,6 +71,9 @@ func TestDockerNetworkMacvlan(t *testing.T) { }, { name: "Addressing", test: testMacvlanAddressing, + }, { + name: "NoIPv6", + test: testMacvlanNoIPv6, }, } { tc := tc @@ -275,3 +278,29 @@ func testMacvlanAddressing(ctx context.Context, client client.APIClient) func(*t assert.Check(t, strings.Contains(result.Combined(), "default via 2001:db8:abca::254 dev eth0")) } } + +// Check that a macvlan interface with '--ipv6=false' doesn't get kernel-assigned +// IPv6 addresses, but the loopback interface does still have an IPv6 address ('::1'). +func testMacvlanNoIPv6(t *testing.T, ctx context.Context, client client.APIClient) { + const netName = "macvlannet" + + net.CreateNoError(ctx, t, client, netName, + net.WithMacvlan(""), + net.WithOption("macvlan_mode", "bridge"), + ) + assert.Check(t, n.IsNetworkAvailable(ctx, client, netName)) + + id := container.Run(ctx, t, client, container.WithNetworkMode(netName)) + + loRes := container.ExecT(ctx, t, client, id, []string{"ip", "a", "show", "dev", "lo"}) + assert.Check(t, is.Contains(loRes.Combined(), " inet ")) + assert.Check(t, is.Contains(loRes.Combined(), " inet6 ")) + + eth0Res := container.ExecT(ctx, t, client, id, []string{"ip", "a", "show", "dev", "eth0"}) + assert.Check(t, is.Contains(eth0Res.Combined(), " inet ")) + assert.Check(t, !strings.Contains(eth0Res.Combined(), " inet6 "), + "result.Combined(): %s", eth0Res.Combined()) + + sysctlRes := container.ExecT(ctx, t, client, id, []string{"sysctl", "-n", "net.ipv6.conf.eth0.disable_ipv6"}) + assert.Check(t, is.Equal(strings.TrimSpace(sysctlRes.Combined()), "1")) +} diff --git a/integration/networking/bridge_test.go b/integration/networking/bridge_test.go index e3d1fe2..7dfcd28 100644 --- a/integration/networking/bridge_test.go +++ b/integration/networking/bridge_test.go @@ -3,6 +3,7 @@ package networking import ( "context" "fmt" + "strings" "testing" "time" @@ -477,3 +478,37 @@ func TestDefaultBridgeAddresses(t *testing.T) { }) } } + +// Check that an interface to an '--ipv6=false' network has no IPv6 +// address - either IPAM assigned, or kernel-assigned LL, but the loopback +// interface does still have an IPv6 address ('::1'). +func TestNonIPv6Network(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType == "windows") + + ctx := setupTest(t) + d := daemon.New(t) + d.StartWithBusybox(ctx, t) + defer d.Stop(t) + + c := d.NewClientT(t) + defer c.Close() + + const netName = "testnet" + network.CreateNoError(ctx, t, c, netName) + defer network.RemoveNoError(ctx, t, c, netName) + + id := container.Run(ctx, t, c, container.WithNetworkMode(netName)) + defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true}) + + loRes := container.ExecT(ctx, t, c, id, []string{"ip", "a", "show", "dev", "lo"}) + assert.Check(t, is.Contains(loRes.Combined(), " inet ")) + assert.Check(t, is.Contains(loRes.Combined(), " inet6 ")) + + eth0Res := container.ExecT(ctx, t, c, id, []string{"ip", "a", "show", "dev", "eth0"}) + assert.Check(t, is.Contains(eth0Res.Combined(), " inet ")) + assert.Check(t, !strings.Contains(eth0Res.Combined(), " inet6 "), + "result.Combined(): %s", eth0Res.Combined()) + + sysctlRes := container.ExecT(ctx, t, c, id, []string{"sysctl", "-n", "net.ipv6.conf.eth0.disable_ipv6"}) + assert.Check(t, is.Equal(strings.TrimSpace(sysctlRes.Combined()), "1")) +} diff --git a/libnetwork/osl/interface_linux.go b/libnetwork/osl/interface_linux.go index 27e079d..e559ab9 100644 --- a/libnetwork/osl/interface_linux.go +++ b/libnetwork/osl/interface_linux.go @@ -367,17 +367,24 @@ func setInterfaceIP(nlh *netlink.Handle, iface netlink.Link, i *Interface) error } func setInterfaceIPv6(nlh *netlink.Handle, iface netlink.Link, i *Interface) error { - if i.AddressIPv6() == nil { + addr := i.AddressIPv6() + // IPv6 must be enabled on the interface if and only if the network is + // IPv6-enabled. For an interface on an IPv4-only network, if IPv6 isn't + // disabled, the interface will be put into IPv6 multicast groups making + // it unexpectedly susceptible to NDP cache poisoning, route injection, etc. + // (At present, there will always be a pre-configured IPv6 address if the + // network is IPv6-enabled.) + if err := setIPv6(i.ns.path, i.DstName(), addr != nil); err != nil { + return fmt.Errorf("failed to configure ipv6: %v", err) + } + if addr == nil { return nil } - if err := checkRouteConflict(nlh, i.AddressIPv6(), netlink.FAMILY_V6); err != nil { + if err := checkRouteConflict(nlh, addr, netlink.FAMILY_V6); err != nil { return err } - if err := setIPv6(i.ns.path, i.DstName(), true); err != nil { - return fmt.Errorf("failed to enable ipv6: %v", err) - } - ipAddr := &netlink.Addr{IPNet: i.AddressIPv6(), Label: "", Flags: syscall.IFA_F_NODAD} - return nlh.AddrAdd(iface, ipAddr) + nlAddr := &netlink.Addr{IPNet: addr, Label: "", Flags: syscall.IFA_F_NODAD} + return nlh.AddrAdd(iface, nlAddr) } func setInterfaceLinkLocalIPs(nlh *netlink.Handle, iface netlink.Link, i *Interface) error { -- 2.25.1