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

CVE-2026-45905

CVE-2026-45905

Description

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

xfrm: fix ip_rt_bug race in icmp_route_lookup reverse path

icmp_route_lookup() performs multiple route lookups to find a suitable route for sending ICMP error messages, with special handling for XFRM (IPsec) policies.

The lookup sequence is: 1. First, lookup output route for ICMP reply (dst = original src) 2. Pass through xfrm_lookup() for policy check 3. If blocked (-EPERM) or dst is not local, enter "reverse path" 4. In reverse path, call xfrm_decode_session_reverse() to get fl4_dec which reverses the original packet's flow (saddr<->daddr swapped) 5. If fl4_dec.saddr is local (we are the original destination), use __ip_route_output_key() for output route lookup 6. If fl4_dec.saddr is NOT local (we are a forwarding node), use ip_route_input() to simulate the reverse packet's input path 7. Finally, pass rt2 through xfrm_lookup() with XFRM_LOOKUP_ICMP flag

The bug occurs in step 6: ip_route_input() is called with fl4_dec.daddr (original packet's source) as destination. If this address becomes local between the initial check and ip_route_input() call (e.g., due to concurrent "ip addr add"), ip_route_input() returns a LOCAL route with dst.output set to ip_rt_bug.

This route is then used for ICMP output, causing dst_output() to call ip_rt_bug(), triggering a WARN_ON:

------------[ cut here ]------------ WARNING: net/ipv4/route.c:1275 at ip_rt_bug+0x21/0x30, CPU#1 Call Trace:

ip_push_pending_frames+0x202/0x240 icmp_push_reply+0x30d/0x430 __icmp_send+0x1149/0x24f0 ip_options_compile+0xa2/0xd0 ip_rcv_finish_core+0x829/0x1950 ip_rcv+0x2d7/0x420 __netif_receive_skb_one_core+0x185/0x1f0 netif_receive_skb+0x90/0x450 tun_get_user+0x3413/0x3fb0 tun_chr_write_iter+0xe4/0x220 ...

Fix this by checking rt2->rt_type after ip_route_input(). If it's RTN_LOCAL, the route cannot be used for output, so treat it as an error.

The reproducer requires kernel modification to widen the race window, making it unsuitable as a selftest. It is available at:

https://gist.github.com/mrpre/eae853b72ac6a750f5d45d64ddac1e81

AI Insight

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

A race condition in Linux kernel's ICMP reverse path via xfrm can cause a WARN_ON panic when concurrent IP address changes trick ip_route_input into returning a local route.

Vulnerability

In the Linux kernel, a race condition exists in icmp_route_lookup() when handling XFRM (IPsec) policies for ICMP error messages. The function performs multiple route lookups; during the reverse path (step 6), if the original packet's source (fl4_dec.daddr) becomes local between the reachable check and the ip_route_input() call (e.g., by a concurrent ip addr add), ip_route_input() returns a LOCAL route with dst.output set to ip_rt_bug. This leads to a WARN_ON panic when dst_output() calls ip_rt_bug. The vulnerability affects all Linux kernel versions where this code path exists. The commit that fixes the issue is 423ce12d10b426709489d6b84fdaa6d2f31c5652 [1].

Exploitation

An attacker must be able to trigger ICMP error generation (e.g., through malformed packets) and concurrently add a local IP address on the victim node to widen the race window. The race condition requires precise timing. The official description notes that the reproducer requires kernel modification to widen the window, making it unsuitable for simple exploitation [1].

Impact

On successful exploitation, a local attacker can trigger a kernel WARN_ON and possible system crash (denial of service). The impact is limited to availability; no evidence of privilege escalation or data disclosure is provided in the references.

Mitigation

The fix is available in the Linux kernel stable tree as commit 423ce12d10b426709489d6b84fdaa6d2f31c5652. Users should apply the patch or upgrade to a kernel version containing it. No workaround is documented. The vulnerability is not listed on CISA's Known Exploited Vulnerabilities (KEV) catalog as of the publication date.

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

1

Patches

12
9a95ec9144ee

xfrm: fix ip_rt_bug race in icmp_route_lookup reverse path

2 files changed · +30 2
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 9e1a574384aa64..56b240e7f083c1 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -555,6 +555,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 9e1a574384aa64..56b240e7f083c1 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -555,6 +555,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
b04061f89ffc

xfrm: fix ip_rt_bug race in icmp_route_lookup reverse path

2 files changed · +30 2
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 2bda14908273c3..ee24728fc60bfb 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -555,6 +555,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 2bda14908273c3..ee24728fc60bfb 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -555,6 +555,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
423ce12d10b4

xfrm: fix ip_rt_bug race in icmp_route_lookup reverse path

2 files changed · +30 2
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 4acbbc703e7980..a2cff16668d725 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -554,6 +554,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 4acbbc703e7980..a2cff16668d725 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -554,6 +554,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
1c9ef28f643c

xfrm: fix ip_rt_bug race in icmp_route_lookup reverse path

2 files changed · +30 2
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 8e10e9e7676c58..9323ee0a6ac483 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -554,6 +554,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 8e10e9e7676c58..9323ee0a6ac483 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -554,6 +554,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
81b84de32bb2

xfrm: fix ip_rt_bug race in icmp_route_lookup reverse path

2 files changed · +30 2
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 4acbbc703e7980..a2cff16668d725 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -554,6 +554,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 4acbbc703e7980..a2cff16668d725 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -554,6 +554,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
2c1f59005da9

xfrm: fix ip_rt_bug race in icmp_route_lookup reverse path

2 files changed · +30 2
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index efa589a1e7a389..9653ef1281a463 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -555,6 +555,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index efa589a1e7a389..9653ef1281a463 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -555,6 +555,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
81b84de32bb2

xfrm: fix ip_rt_bug race in icmp_route_lookup reverse path

2 files changed · +30 2
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 4acbbc703e7980..a2cff16668d725 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -554,6 +554,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 4acbbc703e7980..a2cff16668d725 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -554,6 +554,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
9a95ec9144ee

xfrm: fix ip_rt_bug race in icmp_route_lookup reverse path

2 files changed · +30 2
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 9e1a574384aa64..56b240e7f083c1 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -555,6 +555,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 9e1a574384aa64..56b240e7f083c1 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -555,6 +555,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
423ce12d10b4

xfrm: fix ip_rt_bug race in icmp_route_lookup reverse path

2 files changed · +30 2
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 4acbbc703e7980..a2cff16668d725 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -554,6 +554,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 4acbbc703e7980..a2cff16668d725 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -554,6 +554,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
1c9ef28f643c

xfrm: fix ip_rt_bug race in icmp_route_lookup reverse path

2 files changed · +30 2
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 8e10e9e7676c58..9323ee0a6ac483 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -554,6 +554,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 8e10e9e7676c58..9323ee0a6ac483 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -554,6 +554,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
2c1f59005da9

xfrm: fix ip_rt_bug race in icmp_route_lookup reverse path

2 files changed · +30 2
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index efa589a1e7a389..9653ef1281a463 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -555,6 +555,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index efa589a1e7a389..9653ef1281a463 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -555,6 +555,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
b04061f89ffc

xfrm: fix ip_rt_bug race in icmp_route_lookup reverse path

2 files changed · +30 2
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 2bda14908273c3..ee24728fc60bfb 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -555,6 +555,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    
  • net/ipv4/icmp.c+15 1 modified
    diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
    index 2bda14908273c3..ee24728fc60bfb 100644
    --- a/net/ipv4/icmp.c
    +++ b/net/ipv4/icmp.c
    @@ -555,6 +555,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
     		/* steal dst entry from skb_in, don't drop refcnt */
     		skb_dstref_steal(skb_in);
     		skb_dstref_restore(skb_in, orefdst);
    +
    +		/*
    +		 * At this point, fl4_dec.daddr should NOT be local (we
    +		 * checked fl4_dec.saddr above). However, a race condition
    +		 * may occur if the address is added to the interface
    +		 * concurrently. In that case, ip_route_input() returns a
    +		 * LOCAL route with dst.output=ip_rt_bug, which must not
    +		 * be used for output.
    +		 */
    +		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
    +			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
    +					     &fl4_dec.daddr, &fl4_dec.saddr);
    +			dst_release(&rt2->dst);
    +			err = -EINVAL;
    +		}
     	}
     
     	if (err)
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Time-of-check time-of-use (TOCTOU) race condition in icmp_route_lookup(): the check that fl4_dec.saddr is not local happens before ip_route_input() is called, but a concurrent address addition can make fl4_dec.daddr local by the time the lookup runs, causing ip_route_input() to return a LOCAL route with dst.output set to ip_rt_bug, which cannot be used for output."

Attack vector

An attacker can trigger this race by sending IP packets that cause the kernel to generate ICMP error messages while simultaneously adding an IP address to a local interface (e.g., via concurrent "ip addr add"). In the reverse-path branch of icmp_route_lookup() [patch_id=2661497], the function calls ip_route_input() with fl4_dec.daddr (the original packet's source) as the destination. If that address becomes local between the preceding non-local check and the route lookup, ip_route_input() returns a LOCAL route whose dst.output is ip_rt_bug. The kernel then uses this route for ICMP output, and dst_output() calls ip_rt_bug(), which fires a WARN_ON and crashes the call trace shown in the commit message. The race window is narrow; the reproducer requires kernel modification to widen it.

Affected code

The vulnerable code is in the icmp_route_lookup() function in net/ipv4/icmp.c. Specifically, the reverse-path branch (step 6 in the commit message) calls ip_route_input() without verifying that the returned route is suitable for output. The patch modifies the same file at the point after ip_route_input() returns and before the route is used for ICMP transmission.

What the fix does

The patch adds a post-lookup check in icmp_route_lookup() at net/ipv4/icmp.c after ip_route_input() returns [patch_id=2661497]. If the resulting route rt2 has rt_type == RTN_LOCAL, the code now releases the dst reference and sets err = -EINVAL, causing the function to treat this as an error instead of passing the invalid LOCAL route onward. A rate-limited warning is also logged. This closes the race by detecting the condition where a concurrent address addition made the destination local after the initial check, preventing the ip_rt_bug WARN_ON from being triggered.

Preconditions

  • configThe system must be acting as a forwarding node (fl4_dec.saddr is not local) and processing packets that trigger ICMP error generation via the XFRM reverse path.
  • networkAn attacker must be able to send IP packets to the victim system that cause the kernel to enter the icmp_route_lookup() reverse-path branch.
  • inputA concurrent IP address addition (e.g., 'ip addr add') must occur on the victim system during the race window between the non-local check and the ip_route_input() call.

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

References

6

News mentions

0

No linked articles in our index yet.