VYPR
Moderate severityNVD Advisory· Published Jun 3, 2020· Updated Aug 4, 2024

Weave Net clusters susceptible to MitM attacks via IPv6 rogue router advertisements

CVE-2020-11091

Description

Weave Net before 2.6.3 allows container root to perform IPv6 router advertisement attacks, enabling man-in-the-middle on the host.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Weave Net before 2.6.3 allows container root to perform IPv6 router advertisement attacks, enabling man-in-the-middle on the host.

Root

Cause In Weave Net prior to version 2.6.3, the virtual Ethernet (veth) devices used to connect containers to the Weave bridge did not disable IPv6 Router Advertisement (RA) acceptance. By default, the host sysctl net/ipv6/conf/*/accept_ra is set to 1, and IPv6 forwarding is disabled. This combination allows the host to process and accept RAs from any neighbor [1][3].

Exploitation

An attacker with root privileges in a container can craft and send rogue RAs to the host. Since the host accepts these RAs, the attacker can redirect IPv6 traffic intended for other hosts to their own container. Even if the cluster operates on an IPv4 network, many HTTP libraries will attempt IPv6 connections first if DNS returns AAAA records, providing an opportunity for the attacker to intercept traffic [1][3].

Impact

Successful exploitation enables a man-in-the-middle (MitM) attack, allowing the attacker to intercept, modify, or redirect traffic. If the host has additional vulnerabilities, such as CVE-2019-3462 (RCE in apt), the attacker could escalate to compromise the host entirely [1][3].

Mitigation

Weave Net version 2.6.3 patches the vulnerability by explicitly setting accept_ra to 0 on both ends of every veth pair it creates [2]. As a workaround, users should avoid running containers with CAP_NET_RAW capability, which is needed to send raw packets like RAs [3].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/weaveworks/weaveGo
< 2.6.32.6.3

Affected products

2

Patches

1
15f21f189906

Merge pull request #3801 from weaveworks/disable-accept-ra

https://github.com/weaveworks/weaveBryan BorehamMay 22, 2020via ghsa
7 files changed · +47 35
  • net/bridge.go+15 11 modified
    @@ -63,10 +63,10 @@ const (
     )
     
     type Bridge interface {
    -	init(config *BridgeConfig) error // create and initialise bridge device(s)
    -	attach(veth *netlink.Veth) error // attach veth to bridge
    -	IsFastdp() bool                  // does this bridge use fastdp?
    -	String() string                  // human-readable type string
    +	init(procPath string, config *BridgeConfig) error // create and initialise bridge device(s)
    +	attach(veth *netlink.Veth) error                  // attach veth to bridge
    +	IsFastdp() bool                                   // does this bridge use fastdp?
    +	String() string                                   // human-readable type string
     }
     
     // Used to indicate a fallback to the Bridge type
    @@ -250,7 +250,7 @@ func EnsureBridge(procPath string, config *BridgeConfig, log *logrus.Logger, ips
     	}
     
     	for {
    -		if err := bridgeType.init(config); err != nil {
    +		if err := bridgeType.init(procPath, config); err != nil {
     			if errors.Cause(err) == errBridgeNotSupported {
     				log.Warnf("Skipping bridge creation of %q due to: %s", bridgeType, err)
     				bridgeType = bridgeImpl{}
    @@ -279,6 +279,10 @@ func EnsureBridge(procPath string, config *BridgeConfig, log *logrus.Logger, ips
     			return bridgeType, errors.Wrap(err, "setting proxy_arp")
     		}
     	}
    +	// No ipv6 router advertisments please
    +	if err := sysctl(procPath, "net/ipv6/conf/"+config.WeaveBridgeName+"/accept_ra", "0"); err != nil {
    +		return bridgeType, errors.Wrap(err, "setting accept_ra to 0")
    +	}
     
     	if err := linkSetUpByName(config.WeaveBridgeName); err != nil {
     		return bridgeType, err
    @@ -345,11 +349,11 @@ func (b bridgeImpl) initPrep(config *BridgeConfig) error {
     	return nil
     }
     
    -func (b bridgeImpl) init(config *BridgeConfig) error {
    +func (b bridgeImpl) init(procPath string, config *BridgeConfig) error {
     	if err := b.initPrep(config); err != nil {
     		return err
     	}
    -	if _, err := CreateAndAttachVeth(BridgeIfName, PcapIfName, config.WeaveBridgeName, config.MTU, true, false, func(veth netlink.Link) error {
    +	if _, err := CreateAndAttachVeth(procPath, BridgeIfName, PcapIfName, config.WeaveBridgeName, config.MTU, true, false, func(veth netlink.Link) error {
     		return netlink.LinkSetUp(veth)
     	}); err != nil {
     		return errors.Wrap(err, "creating pcap veth pair")
    @@ -361,7 +365,7 @@ func (b bridgeImpl) init(config *BridgeConfig) error {
     	return nil
     }
     
    -func (f fastdpImpl) init(config *BridgeConfig) error {
    +func (f fastdpImpl) init(procPath string, config *BridgeConfig) error {
     	odpSupported, err := odp.CreateDatapath(f.datapathName)
     	if !odpSupported {
     		msg := ""
    @@ -392,14 +396,14 @@ func (f fastdpImpl) init(config *BridgeConfig) error {
     	return nil
     }
     
    -func (bf bridgedFastdpImpl) init(config *BridgeConfig) error {
    -	if err := bf.fastdpImpl.init(config); err != nil {
    +func (bf bridgedFastdpImpl) init(procPath string, config *BridgeConfig) error {
    +	if err := bf.fastdpImpl.init(procPath, config); err != nil {
     		return err
     	}
     	if err := bf.bridgeImpl.initPrep(config); err != nil {
     		return err
     	}
    -	if _, err := CreateAndAttachVeth(BridgeIfName, DatapathIfName, config.WeaveBridgeName, config.MTU, true, false, func(veth netlink.Link) error {
    +	if _, err := CreateAndAttachVeth(procPath, BridgeIfName, DatapathIfName, config.WeaveBridgeName, config.MTU, true, false, func(veth netlink.Link) error {
     		if err := netlink.LinkSetUp(veth); err != nil {
     			return errors.Wrapf(err, "setting link up on %q", veth.Attrs().Name)
     		}
    
  • net/netns.go+3 3 modified
    @@ -55,9 +55,9 @@ func WithNetNSByPath(path string, work func() error) error {
     }
     
     func NSPathByPid(pid int) string {
    -	return NSPathByPidWithRoot("/", pid)
    +	return NSPathByPidWithProc("/proc", pid)
     }
     
    -func NSPathByPidWithRoot(root string, pid int) string {
    -	return filepath.Join(root, fmt.Sprintf("/proc/%d/ns/net", pid))
    +func NSPathByPidWithProc(procPath string, pid int) string {
    +	return filepath.Join(procPath, fmt.Sprint(pid), "/ns/net")
     }
    
  • net/veth.go+17 8 modified
    @@ -13,7 +13,7 @@ import (
     )
     
     // create and attach a veth to the Weave bridge
    -func CreateAndAttachVeth(name, peerName, bridgeName string, mtu int, keepTXOn bool, errIfLinkExist bool, init func(peer netlink.Link) error) (*netlink.Veth, error) {
    +func CreateAndAttachVeth(procPath, name, peerName, bridgeName string, mtu int, keepTXOn bool, errIfLinkExist bool, init func(peer netlink.Link) error) (*netlink.Veth, error) {
     	bridge, err := netlink.LinkByName(bridgeName)
     	if err != nil {
     		return nil, fmt.Errorf(`bridge "%s" not present; did you launch weave?`, bridgeName)
    @@ -49,6 +49,13 @@ func CreateAndAttachVeth(name, peerName, bridgeName string, mtu int, keepTXOn bo
     	if err := bridgeType.attach(veth); err != nil {
     		return cleanup("attaching veth %q to %q: %s", name, bridgeName, err)
     	}
    +	// No ipv6 router advertisments please
    +	if err := sysctl(procPath, "net/ipv6/conf/"+name+"/accept_ra", "0"); err != nil {
    +		return cleanup("setting accept_ra to 0: %s", err)
    +	}
    +	if err := sysctl(procPath, "net/ipv6/conf/"+peerName+"/accept_ra", "0"); err != nil {
    +		return cleanup("setting accept_ra to 0: %s", err)
    +	}
     	if !bridgeType.IsFastdp() && !keepTXOn {
     		if err := EthtoolTXOff(veth.PeerName); err != nil {
     			return cleanup(`unable to set tx off on %q: %s`, peerName, err)
    @@ -112,6 +119,9 @@ func interfaceExistsInNamespace(netNSPath string, ifName string) bool {
     }
     
     func AttachContainer(netNSPath, id, ifName, bridgeName string, mtu int, withMulticastRoute bool, cidrs []*net.IPNet, keepTXOn bool, hairpinMode bool) error {
    +	// AttachContainer expects to be called in host pid namespace
    +	const procPath = "/proc"
    +
     	ns, err := netns.GetFromPath(netNSPath)
     	if err != nil {
     		return err
    @@ -124,12 +134,12 @@ func AttachContainer(netNSPath, id, ifName, bridgeName string, mtu int, withMult
     			id = id[:maxIDLen] // trim passed ID if too long
     		}
     		name, peerName := vethPrefix+"pl"+id, vethPrefix+"pg"+id
    -		veth, err := CreateAndAttachVeth(name, peerName, bridgeName, mtu, keepTXOn, true, func(veth netlink.Link) error {
    +		veth, err := CreateAndAttachVeth(procPath, name, peerName, bridgeName, mtu, keepTXOn, true, func(veth netlink.Link) error {
     			if err := netlink.LinkSetNsFd(veth, int(ns)); err != nil {
     				return fmt.Errorf("failed to move veth to container netns: %s", err)
     			}
     			if err := WithNetNS(ns, func() error {
    -				return setupIface(peerName, ifName)
    +				return setupIface(procPath, peerName, ifName)
     			}); err != nil {
     				return fmt.Errorf("error setting up interface: %s", err)
     			}
    @@ -206,7 +216,7 @@ func setupIfaceAddrs(veth netlink.Link, withMulticastRoute bool, cidrs []*net.IP
     }
     
     // setupIface expects to be called in the container's netns
    -func setupIface(ifaceName, newIfName string) error {
    +func setupIface(procPath, ifaceName, newIfName string) error {
     	ipt, err := iptables.New()
     	if err != nil {
     		return err
    @@ -219,23 +229,22 @@ func setupIface(ifaceName, newIfName string) error {
     	if err := netlink.LinkSetName(link, newIfName); err != nil {
     		return err
     	}
    -	// This is only called by AttachContainer which is only called in host pid namespace
    -	if err := configureARPCache("/proc", newIfName); err != nil {
    +	if err := configureARPCache(procPath, newIfName); err != nil {
     		return err
     	}
     	return ipt.Append("filter", "INPUT", "-i", newIfName, "-d", "224.0.0.0/4", "-j", "DROP")
     }
     
     // configureARP is a helper for the Docker plugin which doesn't set the addresses itself
    -func ConfigureARP(prefix, rootPath string) error {
    +func ConfigureARP(prefix, procPath string) error {
     	links, err := netlink.LinkList()
     	if err != nil {
     		return err
     	}
     	for _, link := range links {
     		ifName := link.Attrs().Name
     		if strings.HasPrefix(ifName, prefix) {
    -			configureARPCache(rootPath+"/proc", ifName)
    +			configureARPCache(procPath, ifName)
     			if addrs, err := netlink.AddrList(link, netlink.FAMILY_V4); err == nil {
     				for _, addr := range addrs {
     					arping.GratuitousArpOverIfaceByName(addr.IPNet.IP, ifName)
    
  • plugin/net/driver.go+4 2 modified
    @@ -39,9 +39,10 @@ type driver struct {
     	// used only by plugin-v2
     	forceMulticast bool
     	networks       map[string]network
    +	procPath       string
     }
     
    -func New(client *docker.Client, weave *weaveapi.Client, name, scope string, dns, isPluginV2, forceMulticast bool) (skel.Driver, error) {
    +func New(client *docker.Client, weave *weaveapi.Client, name, scope string, dns, isPluginV2, forceMulticast bool, procPath string) (skel.Driver, error) {
     	driver := &driver{
     		name:       name,
     		scope:      scope,
    @@ -51,6 +52,7 @@ func New(client *docker.Client, weave *weaveapi.Client, name, scope string, dns,
     		// make sure that it's used only by plugin-v2
     		forceMulticast: isPluginV2 && forceMulticast,
     		networks:       make(map[string]network),
    +		procPath:       procPath,
     	}
     
     	// Do not start watcher in the case of plugin v2, which prevents us from
    @@ -134,7 +136,7 @@ func (driver *driver) CreateEndpoint(create *api.CreateEndpointRequest) (*api.Cr
     
     	// create veths. note we assume endpoint IDs are unique in the first 9 chars
     	name, peerName := vethPair(create.EndpointID)
    -	if _, err := weavenet.CreateAndAttachVeth(name, peerName, weavenet.WeaveBridgeName, 0, false, true, nil); err != nil {
    +	if _, err := weavenet.CreateAndAttachVeth(driver.procPath, name, peerName, weavenet.WeaveBridgeName, 0, false, true, nil); err != nil {
     		return nil, driver.error("JoinEndpoint", "%s", err)
     	}
     
    
  • plugin/net/watcher.go+2 7 modified
    @@ -43,14 +43,9 @@ func (w *watcher) ContainerStarted(id string) {
     					w.driver.warn("ContainerStarted", "unable to register %s with weaveDNS: %s", id, err)
     				}
     			}
    -			rootDir := "/"
    -			if w.driver.isPluginV2 {
    -				// We bind mount host's /proc to /host/proc for plugin-v2
    -				rootDir = "/host"
    -			}
    -			netNSPath := weavenet.NSPathByPidWithRoot(rootDir, info.State.Pid)
    +			netNSPath := weavenet.NSPathByPidWithProc(w.driver.procPath, info.State.Pid)
     			if err := weavenet.WithNetNSByPath(netNSPath, func() error {
    -				return weavenet.ConfigureARP(weavenet.VethName, rootDir)
    +				return weavenet.ConfigureARP(weavenet.VethName, w.driver.procPath)
     			}); err != nil {
     				w.driver.warn("ContainerStarted", "unable to configure interfaces: %s", err)
     			}
    
  • plugin/plugin.go+5 4 modified
    @@ -32,6 +32,7 @@ type Config struct {
     	EnableV2Multicast bool
     	DNS               bool
     	DefaultSubnet     string
    +	ProcPath          string // path to reach host /proc filesystem
     }
     
     type Plugin struct {
    @@ -62,15 +63,15 @@ func (plugin *Plugin) run(dockerClient *docker.Client, weave *weaveapi.Client, r
     	endChan := make(chan error, 1)
     
     	if plugin.Socket != "" {
    -		globalListener, err := listenAndServe(dockerClient, weave, plugin.Socket, endChan, "global", false, plugin.DNS, plugin.EnableV2, plugin.EnableV2Multicast)
    +		globalListener, err := listenAndServe(dockerClient, weave, plugin.Socket, endChan, "global", false, plugin.DNS, plugin.EnableV2, plugin.EnableV2Multicast, plugin.ProcPath)
     		if err != nil {
     			return err
     		}
     		defer os.Remove(plugin.Socket)
     		defer globalListener.Close()
     	}
     	if plugin.MeshSocket != "" {
    -		meshListener, err := listenAndServe(dockerClient, weave, plugin.MeshSocket, endChan, "local", true, plugin.DNS, plugin.EnableV2, plugin.EnableV2Multicast)
    +		meshListener, err := listenAndServe(dockerClient, weave, plugin.MeshSocket, endChan, "local", true, plugin.DNS, plugin.EnableV2, plugin.EnableV2Multicast, plugin.ProcPath)
     		if err != nil {
     			return err
     		}
    @@ -87,15 +88,15 @@ func (plugin *Plugin) run(dockerClient *docker.Client, weave *weaveapi.Client, r
     	return <-endChan
     }
     
    -func listenAndServe(dockerClient *docker.Client, weave *weaveapi.Client, address string, endChan chan<- error, scope string, withIpam, dns bool, isPluginV2, forceMulticast bool) (net.Listener, error) {
    +func listenAndServe(dockerClient *docker.Client, weave *weaveapi.Client, address string, endChan chan<- error, scope string, withIpam, dns bool, isPluginV2, forceMulticast bool, procPath string) (net.Listener, error) {
     	var name string
     	if isPluginV2 {
     		name = pluginV2Name
     	} else {
     		name = pluginNameFromAddress(address)
     	}
     
    -	d, err := netplugin.New(dockerClient, weave, name, scope, dns, isPluginV2, forceMulticast)
    +	d, err := netplugin.New(dockerClient, weave, name, scope, dns, isPluginV2, forceMulticast, procPath)
     	if err != nil {
     		return nil, err
     	}
    
  • prog/weaver/main.go+1 0 modified
    @@ -453,6 +453,7 @@ func main() {
     
     	pluginConfig.DNS = !noDNS
     	pluginConfig.DefaultSubnet = defaultSubnet.String()
    +	pluginConfig.ProcPath = procPath
     	plugin := plugin.NewPlugin(pluginConfig)
     
     	// The weave script always waits for a status call to succeed,
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.