ipip_fill_forward_path() and ip6_tnl_fill_forward_path() assign the outer route's device to ctx->dev without checking that it differs from the tunnel device itself. When a tunnel's outer route resolves back to that same tunnel, the walk in dev_fill_forward_path() makes no progress and trips its loop check WARN_ON_ONCE(last_dev == ctx->dev). Release the route and return -EOPNOTSUPP when the resolved dst device equals ctx->dev, so the path walk either advances to a different device or fails cleanly. Closes: https://lore.kernel.org/all/CA+0ovCgaRvbd0Udj70b2xxG8Cx3CaCpNhnf1V4RWQuDveZYZhA@mail.gmail.com/ Signed-off-by: Farhad Alemi --- --- a/net/ipv4/ipip.c +++ b/net/ipv4/ipip.c @@ -375,6 +375,12 @@ static int ipip_fill_forward_path(struct net_device_path_ctx *ctx, if (IS_ERR(rt)) return PTR_ERR(rt); + /* The path walk must advance: a route back into the tunnel is a loop. */ + if (rt->dst.dev == ctx->dev) { + ip_rt_put(rt); + return -EOPNOTSUPP; + } + path->type = DEV_PATH_TUN; path->tun.src_v4.s_addr = tiph->saddr; path->tun.dst_v4.s_addr = tiph->daddr; --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -1863,7 +1863,12 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx, fl6.flowi6_proto = 0; dst = ip6_route_output(dev_net(ctx->dev), NULL, &fl6); - if (!dst->error) { + err = dst->error; + /* The path walk must advance: a route back into the tunnel is a loop. */ + if (!err && dst->dev == ctx->dev) + err = -EOPNOTSUPP; + + if (!err) { path->type = DEV_PATH_TUN; path->tun.src_v6 = fl6.saddr; path->tun.dst_v6 = fl6.daddr; @@ -1873,7 +1878,6 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx, ctx->dev = dst->dev; } - err = dst->error; if (err) dst_release(dst);