VYPR
Unrated severityNVD Advisory· Published May 27, 2026· Updated May 27, 2026

CVE-2026-45917

CVE-2026-45917

Description

In the Linux kernel, the following vulnerability has been resolved:

ipvs: do not keep dest_dst if dev is going down

There is race between the netdev notifier ip_vs_dst_event() and the code that caches dst with dev that is going down. As the FIB can be notified for the closed device after our handler finishes, it is possible valid route to be returned and cached resuling in a leaked dev reference until the dest is not removed.

To prevent new dest_dst to be attached to dest just after the handler dropped the old one, add a netif_running() check to make sure the notifier handler is not currently running for device that is closing.

AI Insight

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

In the Linux kernel IPVS, a race condition between netdev notifier and dst caching allows a leaked device reference after interface shutdown.

Vulnerability

In the Linux kernel's IPVS subsystem, a race condition exists between the netdev notifier (ip_vs_dst_event()) and the code that caches dest_dst with a device that is going down. When the FIB is notified for a closed device after the notifier handler finishes, a valid route may be returned and cached, resulting in a leaked device reference until the destination is removed. This affects Linux kernel versions prior to the commit that adds a netif_running() check.

Exploitation

An attacker with the ability to trigger a network device to go down (e.g., through admin action or system events) while the IPVS code is caching a route can exploit this race. The precise timing window is narrow, but successful exploitation causes the kernel to retain a reference to a device that is already down.

Impact

A successful exploit results in a leaked device reference, leading to a memory leak in the kernel. This can eventually lead to resource exhaustion and denial of service. No privilege escalation or data integrity impact is described.

Mitigation

The fix is implemented in commit [1] and is part of the stable Linux kernel tree. Users should apply kernel updates that include this patch. No workaround is currently available.

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

Affected products

2

Patches

8
64af43033503

ipvs: do not keep dest_dst if dev is going down

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitJulian AnastasovFeb 14, 2026Fixed in 6.12.75via kernel-cna
1 file changed · +36 11
  • net/netfilter/ipvs/ip_vs_xmit.c+36 11 modified
    diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
    index fa2db17f6298be..8892f261451e9a 100644
    --- a/net/netfilter/ipvs/ip_vs_xmit.c
    +++ b/net/netfilter/ipvs/ip_vs_xmit.c
    @@ -295,6 +295,12 @@ static inline bool decrement_ttl(struct netns_ipvs *ipvs,
     	return true;
     }
     
    +/* rt has device that is down */
    +static bool rt_dev_is_down(const struct net_device *dev)
    +{
    +	return dev && !netif_running(dev);
    +}
    +
     /* Get route to destination or remote server */
     static int
     __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
    @@ -310,9 +316,11 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rtable(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +		} else {
     			dest_dst = ip_vs_dest_dst_alloc();
     			spin_lock_bh(&dest->dst_lock);
     			if (!dest_dst) {
    @@ -328,14 +336,22 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     				ip_vs_dest_dst_free(dest_dst);
     				goto err_unreach;
     			}
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI4, src %pI4, refcnt=%d\n",
     				  &dest->addr.ip, &dest_dst->dst_saddr.ip,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.ip;
     	} else {
     		noref = 0;
     
    @@ -472,9 +488,11 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rt6_info(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +		} else {
     			u32 cookie;
     
     			dest_dst = ip_vs_dest_dst_alloc();
    @@ -495,14 +513,22 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     			}
     			rt = dst_rt6_info(dst);
     			cookie = rt6_get_cookie(rt);
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI6, src %pI6, refcnt=%d\n",
     				  &dest->addr.in6, &dest_dst->dst_saddr.in6,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.in6;
     	} else {
     		noref = 0;
     		dst = __ip_vs_route_output_v6(net, daddr, ret_saddr, do_xfrm,
    -- 
    cgit 1.3-korg
    
    
    
024eb0bd19f5

ipvs: do not keep dest_dst if dev is going down

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitJulian AnastasovFeb 14, 2026Fixed in 6.19.4via kernel-cna
1 file changed · +36 11
  • net/netfilter/ipvs/ip_vs_xmit.c+36 11 modified
    diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
    index 64c697212578ae..124f779424b0ff 100644
    --- a/net/netfilter/ipvs/ip_vs_xmit.c
    +++ b/net/netfilter/ipvs/ip_vs_xmit.c
    @@ -294,6 +294,12 @@ static inline bool decrement_ttl(struct netns_ipvs *ipvs,
     	return true;
     }
     
    +/* rt has device that is down */
    +static bool rt_dev_is_down(const struct net_device *dev)
    +{
    +	return dev && !netif_running(dev);
    +}
    +
     /* Get route to destination or remote server */
     static int
     __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
    @@ -309,9 +315,11 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rtable(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +		} else {
     			dest_dst = ip_vs_dest_dst_alloc();
     			spin_lock_bh(&dest->dst_lock);
     			if (!dest_dst) {
    @@ -327,14 +335,22 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     				ip_vs_dest_dst_free(dest_dst);
     				goto err_unreach;
     			}
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI4, src %pI4, refcnt=%d\n",
     				  &dest->addr.ip, &dest_dst->dst_saddr.ip,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.ip;
     	} else {
     		noref = 0;
     
    @@ -471,9 +487,11 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rt6_info(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +		} else {
     			u32 cookie;
     
     			dest_dst = ip_vs_dest_dst_alloc();
    @@ -494,14 +512,22 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     			}
     			rt = dst_rt6_info(dst);
     			cookie = rt6_get_cookie(rt);
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI6, src %pI6, refcnt=%d\n",
     				  &dest->addr.in6, &dest_dst->dst_saddr.in6,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.in6;
     	} else {
     		noref = 0;
     		dst = __ip_vs_route_output_v6(net, daddr, ret_saddr, do_xfrm,
    -- 
    cgit 1.3-korg
    
    
    
8fde939b0206

ipvs: do not keep dest_dst if dev is going down

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitJulian AnastasovFeb 14, 2026Fixed in 7.0via kernel-cna
1 file changed · +36 11
  • net/netfilter/ipvs/ip_vs_xmit.c+36 11 modified
    diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
    index f861d116cc33fe..4389bfe3050d4c 100644
    --- a/net/netfilter/ipvs/ip_vs_xmit.c
    +++ b/net/netfilter/ipvs/ip_vs_xmit.c
    @@ -294,6 +294,12 @@ static inline bool decrement_ttl(struct netns_ipvs *ipvs,
     	return true;
     }
     
    +/* rt has device that is down */
    +static bool rt_dev_is_down(const struct net_device *dev)
    +{
    +	return dev && !netif_running(dev);
    +}
    +
     /* Get route to destination or remote server */
     static int
     __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
    @@ -309,9 +315,11 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rtable(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +		} else {
     			dest_dst = ip_vs_dest_dst_alloc();
     			spin_lock_bh(&dest->dst_lock);
     			if (!dest_dst) {
    @@ -327,14 +335,22 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     				ip_vs_dest_dst_free(dest_dst);
     				goto err_unreach;
     			}
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI4, src %pI4, refcnt=%d\n",
     				  &dest->addr.ip, &dest_dst->dst_saddr.ip,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.ip;
     	} else {
     		noref = 0;
     
    @@ -471,9 +487,11 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rt6_info(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +		} else {
     			u32 cookie;
     
     			dest_dst = ip_vs_dest_dst_alloc();
    @@ -494,14 +512,22 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     			}
     			rt = dst_rt6_info(dst);
     			cookie = rt6_get_cookie(rt);
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI6, src %pI6, refcnt=%d\n",
     				  &dest->addr.in6, &dest_dst->dst_saddr.in6,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.in6;
     	} else {
     		noref = 0;
     		dst = __ip_vs_route_output_v6(net, daddr, ret_saddr, do_xfrm,
    -- 
    cgit 1.3-korg
    
    
    
bae53b3baf2f

ipvs: do not keep dest_dst if dev is going down

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitJulian AnastasovFeb 14, 2026Fixed in 6.18.14via kernel-cna
1 file changed · +36 11
  • net/netfilter/ipvs/ip_vs_xmit.c+36 11 modified
    diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
    index 618fbe1240b54c..ecbcdc43263d67 100644
    --- a/net/netfilter/ipvs/ip_vs_xmit.c
    +++ b/net/netfilter/ipvs/ip_vs_xmit.c
    @@ -295,6 +295,12 @@ static inline bool decrement_ttl(struct netns_ipvs *ipvs,
     	return true;
     }
     
    +/* rt has device that is down */
    +static bool rt_dev_is_down(const struct net_device *dev)
    +{
    +	return dev && !netif_running(dev);
    +}
    +
     /* Get route to destination or remote server */
     static int
     __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
    @@ -310,9 +316,11 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rtable(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +		} else {
     			dest_dst = ip_vs_dest_dst_alloc();
     			spin_lock_bh(&dest->dst_lock);
     			if (!dest_dst) {
    @@ -328,14 +336,22 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     				ip_vs_dest_dst_free(dest_dst);
     				goto err_unreach;
     			}
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI4, src %pI4, refcnt=%d\n",
     				  &dest->addr.ip, &dest_dst->dst_saddr.ip,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.ip;
     	} else {
     		noref = 0;
     
    @@ -472,9 +488,11 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rt6_info(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +		} else {
     			u32 cookie;
     
     			dest_dst = ip_vs_dest_dst_alloc();
    @@ -495,14 +513,22 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     			}
     			rt = dst_rt6_info(dst);
     			cookie = rt6_get_cookie(rt);
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI6, src %pI6, refcnt=%d\n",
     				  &dest->addr.in6, &dest_dst->dst_saddr.in6,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.in6;
     	} else {
     		noref = 0;
     		dst = __ip_vs_route_output_v6(net, daddr, ret_saddr, do_xfrm,
    -- 
    cgit 1.3-korg
    
    
    
8fde939b0206

ipvs: do not keep dest_dst if dev is going down

1 file changed · +36 11
  • net/netfilter/ipvs/ip_vs_xmit.c+36 11 modified
    diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
    index f861d116cc33fe..4389bfe3050d4c 100644
    --- a/net/netfilter/ipvs/ip_vs_xmit.c
    +++ b/net/netfilter/ipvs/ip_vs_xmit.c
    @@ -294,6 +294,12 @@ static inline bool decrement_ttl(struct netns_ipvs *ipvs,
     	return true;
     }
     
    +/* rt has device that is down */
    +static bool rt_dev_is_down(const struct net_device *dev)
    +{
    +	return dev && !netif_running(dev);
    +}
    +
     /* Get route to destination or remote server */
     static int
     __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
    @@ -309,9 +315,11 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rtable(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +		} else {
     			dest_dst = ip_vs_dest_dst_alloc();
     			spin_lock_bh(&dest->dst_lock);
     			if (!dest_dst) {
    @@ -327,14 +335,22 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     				ip_vs_dest_dst_free(dest_dst);
     				goto err_unreach;
     			}
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI4, src %pI4, refcnt=%d\n",
     				  &dest->addr.ip, &dest_dst->dst_saddr.ip,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.ip;
     	} else {
     		noref = 0;
     
    @@ -471,9 +487,11 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rt6_info(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +		} else {
     			u32 cookie;
     
     			dest_dst = ip_vs_dest_dst_alloc();
    @@ -494,14 +512,22 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     			}
     			rt = dst_rt6_info(dst);
     			cookie = rt6_get_cookie(rt);
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI6, src %pI6, refcnt=%d\n",
     				  &dest->addr.in6, &dest_dst->dst_saddr.in6,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.in6;
     	} else {
     		noref = 0;
     		dst = __ip_vs_route_output_v6(net, daddr, ret_saddr, do_xfrm,
    -- 
    cgit 1.3-korg
    
    
    
024eb0bd19f5

ipvs: do not keep dest_dst if dev is going down

1 file changed · +36 11
  • net/netfilter/ipvs/ip_vs_xmit.c+36 11 modified
    diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
    index 64c697212578ae..124f779424b0ff 100644
    --- a/net/netfilter/ipvs/ip_vs_xmit.c
    +++ b/net/netfilter/ipvs/ip_vs_xmit.c
    @@ -294,6 +294,12 @@ static inline bool decrement_ttl(struct netns_ipvs *ipvs,
     	return true;
     }
     
    +/* rt has device that is down */
    +static bool rt_dev_is_down(const struct net_device *dev)
    +{
    +	return dev && !netif_running(dev);
    +}
    +
     /* Get route to destination or remote server */
     static int
     __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
    @@ -309,9 +315,11 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rtable(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +		} else {
     			dest_dst = ip_vs_dest_dst_alloc();
     			spin_lock_bh(&dest->dst_lock);
     			if (!dest_dst) {
    @@ -327,14 +335,22 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     				ip_vs_dest_dst_free(dest_dst);
     				goto err_unreach;
     			}
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI4, src %pI4, refcnt=%d\n",
     				  &dest->addr.ip, &dest_dst->dst_saddr.ip,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.ip;
     	} else {
     		noref = 0;
     
    @@ -471,9 +487,11 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rt6_info(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +		} else {
     			u32 cookie;
     
     			dest_dst = ip_vs_dest_dst_alloc();
    @@ -494,14 +512,22 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     			}
     			rt = dst_rt6_info(dst);
     			cookie = rt6_get_cookie(rt);
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI6, src %pI6, refcnt=%d\n",
     				  &dest->addr.in6, &dest_dst->dst_saddr.in6,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.in6;
     	} else {
     		noref = 0;
     		dst = __ip_vs_route_output_v6(net, daddr, ret_saddr, do_xfrm,
    -- 
    cgit 1.3-korg
    
    
    
64af43033503

ipvs: do not keep dest_dst if dev is going down

1 file changed · +36 11
  • net/netfilter/ipvs/ip_vs_xmit.c+36 11 modified
    diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
    index fa2db17f6298be..8892f261451e9a 100644
    --- a/net/netfilter/ipvs/ip_vs_xmit.c
    +++ b/net/netfilter/ipvs/ip_vs_xmit.c
    @@ -295,6 +295,12 @@ static inline bool decrement_ttl(struct netns_ipvs *ipvs,
     	return true;
     }
     
    +/* rt has device that is down */
    +static bool rt_dev_is_down(const struct net_device *dev)
    +{
    +	return dev && !netif_running(dev);
    +}
    +
     /* Get route to destination or remote server */
     static int
     __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
    @@ -310,9 +316,11 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rtable(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +		} else {
     			dest_dst = ip_vs_dest_dst_alloc();
     			spin_lock_bh(&dest->dst_lock);
     			if (!dest_dst) {
    @@ -328,14 +336,22 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     				ip_vs_dest_dst_free(dest_dst);
     				goto err_unreach;
     			}
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI4, src %pI4, refcnt=%d\n",
     				  &dest->addr.ip, &dest_dst->dst_saddr.ip,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.ip;
     	} else {
     		noref = 0;
     
    @@ -472,9 +488,11 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rt6_info(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +		} else {
     			u32 cookie;
     
     			dest_dst = ip_vs_dest_dst_alloc();
    @@ -495,14 +513,22 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     			}
     			rt = dst_rt6_info(dst);
     			cookie = rt6_get_cookie(rt);
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI6, src %pI6, refcnt=%d\n",
     				  &dest->addr.in6, &dest_dst->dst_saddr.in6,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.in6;
     	} else {
     		noref = 0;
     		dst = __ip_vs_route_output_v6(net, daddr, ret_saddr, do_xfrm,
    -- 
    cgit 1.3-korg
    
    
    
bae53b3baf2f

ipvs: do not keep dest_dst if dev is going down

1 file changed · +36 11
  • net/netfilter/ipvs/ip_vs_xmit.c+36 11 modified
    diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
    index 618fbe1240b54c..ecbcdc43263d67 100644
    --- a/net/netfilter/ipvs/ip_vs_xmit.c
    +++ b/net/netfilter/ipvs/ip_vs_xmit.c
    @@ -295,6 +295,12 @@ static inline bool decrement_ttl(struct netns_ipvs *ipvs,
     	return true;
     }
     
    +/* rt has device that is down */
    +static bool rt_dev_is_down(const struct net_device *dev)
    +{
    +	return dev && !netif_running(dev);
    +}
    +
     /* Get route to destination or remote server */
     static int
     __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
    @@ -310,9 +316,11 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rtable(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +		} else {
     			dest_dst = ip_vs_dest_dst_alloc();
     			spin_lock_bh(&dest->dst_lock);
     			if (!dest_dst) {
    @@ -328,14 +336,22 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     				ip_vs_dest_dst_free(dest_dst);
     				goto err_unreach;
     			}
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, 0);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI4, src %pI4, refcnt=%d\n",
     				  &dest->addr.ip, &dest_dst->dst_saddr.ip,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.ip;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.ip;
     	} else {
     		noref = 0;
     
    @@ -472,9 +488,11 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     
     	if (dest) {
     		dest_dst = __ip_vs_dst_check(dest);
    -		if (likely(dest_dst))
    +		if (likely(dest_dst)) {
     			rt = dst_rt6_info(dest_dst->dst_cache);
    -		else {
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +		} else {
     			u32 cookie;
     
     			dest_dst = ip_vs_dest_dst_alloc();
    @@ -495,14 +513,22 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
     			}
     			rt = dst_rt6_info(dst);
     			cookie = rt6_get_cookie(rt);
    -			__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			/* It is forbidden to attach dest->dest_dst if
    +			 * device is going down.
    +			 */
    +			if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))
    +				__ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie);
    +			else
    +				noref = 0;
     			spin_unlock_bh(&dest->dst_lock);
     			IP_VS_DBG(10, "new dst %pI6, src %pI6, refcnt=%d\n",
     				  &dest->addr.in6, &dest_dst->dst_saddr.in6,
     				  rcuref_read(&rt->dst.__rcuref));
    +			if (ret_saddr)
    +				*ret_saddr = dest_dst->dst_saddr.in6;
    +			if (!noref)
    +				ip_vs_dest_dst_free(dest_dst);
     		}
    -		if (ret_saddr)
    -			*ret_saddr = dest_dst->dst_saddr.in6;
     	} else {
     		noref = 0;
     		dst = __ip_vs_route_output_v6(net, daddr, ret_saddr, do_xfrm,
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Race condition: a new route can be cached for a device that is going down, after the netdev notifier has already dropped the old cached route, resulting in a leaked device reference."

Attack vector

An attacker can trigger this race condition by causing a network device to go down while IPVS traffic is being forwarded through that device. The netdev notifier `ip_vs_dst_event()` drops the old cached destination route, but a concurrent call to `__ip_vs_get_out_rt` or `__ip_vs_get_out_rt_v6` may obtain a new valid route from the FIB for the device that is closing and cache it via `__ip_vs_dst_set` [patch_id=2661366]. Because the FIB can be notified for the closed device after the notifier handler finishes, the newly cached route holds a leaked device reference until the IPVS destination is removed.

Affected code

The vulnerability resides in `net/netfilter/ipvs/ip_vs_xmit.c` in the functions `__ip_vs_get_out_rt` (IPv4) and `__ip_vs_get_out_rt_v6` (IPv6). These functions cache a route (`dest_dst`) for an IPVS destination without checking whether the associated network device is still running.

What the fix does

The patch adds a new helper `rt_dev_is_down()` that checks `netif_running(dev)` on the route's device. Before calling `__ip_vs_dst_set()` to cache a new destination route, both `__ip_vs_get_out_rt` and `__ip_vs_get_out_rt_v6` now guard the cache attachment with `if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)))` [patch_id=2661366]. If the device is down, `noref` is set to 0 so the allocated `dest_dst` is freed instead of cached, preventing the leak. The `ret_saddr` assignment is also moved inside the appropriate branches to avoid use-after-free.

Preconditions

  • configIPVS must be configured with a destination (real server) that routes through a network device
  • inputThe network device used by the IPVS destination must be brought down (e.g., admin down, cable disconnect) while IPVS traffic is flowing

Generated on May 27, 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.