mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
ipv6: ndisc: fix NULL deref in accept_untracked_na()
accept_untracked_na() re-fetches the inet6_dev with __in6_dev_get(dev)
and dereferences idev->cnf.accept_untracked_na without a NULL check,
even though its only caller ndisc_recv_na() already fetched and
NULL-checked idev for the same device.
Both reads of dev->ip6_ptr run in the same RCU read-side critical
section, but a concurrent addrconf_ifdown() can clear dev->ip6_ptr
between them: lowering the MTU below IPV6_MIN_MTU calls addrconf_ifdown()
without the synchronize_net() that orders the unregister path, so the
re-fetch returns NULL and oopses:
BUG: KASAN: null-ptr-deref in ndisc_recv_na (net/ipv6/ndisc.c:974)
Read of size 4 at addr 0000000000000364
Call Trace:
<IRQ>
ndisc_recv_na (net/ipv6/ndisc.c:974)
icmpv6_rcv (net/ipv6/icmp.c:1193)
ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:479)
ip6_input_finish (net/ipv6/ip6_input.c:534)
ip6_input (net/ipv6/ip6_input.c:545)
ip6_mc_input (net/ipv6/ip6_input.c:635)
ipv6_rcv (net/ipv6/ip6_input.c:351)
</IRQ>
It is reachable by an unprivileged user via a network namespace.
Pass the caller's already validated idev instead of re-fetching it; the
idev stays alive for the whole RCU critical section, so it is safe even
after dev->ip6_ptr has been cleared.
Fixes: aaa5f515b1 ("net: ipv6: new accept_untracked_na option to accept na only if in-network")
Reported-by: Xiang Mei <xmei5@asu.edu>
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
Reviewed-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Link: https://patch.msgid.link/20260617065512.2529757-2-bestswngs@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
27ccb68e7c
commit
d186e94236
|
|
@ -967,10 +967,8 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)
|
|||
return reason;
|
||||
}
|
||||
|
||||
static int accept_untracked_na(struct net_device *dev, struct in6_addr *saddr)
|
||||
static int accept_untracked_na(struct inet6_dev *idev, struct in6_addr *saddr)
|
||||
{
|
||||
struct inet6_dev *idev = __in6_dev_get(dev);
|
||||
|
||||
switch (READ_ONCE(idev->cnf.accept_untracked_na)) {
|
||||
case 0: /* Don't accept untracked na (absent in neighbor cache) */
|
||||
return 0;
|
||||
|
|
@ -980,7 +978,7 @@ static int accept_untracked_na(struct net_device *dev, struct in6_addr *saddr)
|
|||
* same subnet as an address configured on the interface that
|
||||
* received the na
|
||||
*/
|
||||
return !!ipv6_chk_prefix(saddr, dev);
|
||||
return !!ipv6_chk_prefix(saddr, idev->dev);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1078,7 +1076,7 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
|
|||
*/
|
||||
new_state = msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE;
|
||||
if (!neigh && lladdr && idev && READ_ONCE(idev->cnf.forwarding)) {
|
||||
if (accept_untracked_na(dev, saddr)) {
|
||||
if (accept_untracked_na(idev, saddr)) {
|
||||
neigh = neigh_create(&nd_tbl, &msg->target, dev);
|
||||
new_state = NUD_STALE;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user