Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

tcp/udp: Call inet6_destroy_sock() in IPv6 sk->sk_destruct().

Originally, inet6_sk(sk)->XXX were changed under lock_sock(), so we were
able to clean them up by calling inet6_destroy_sock() during the IPv6 ->
IPv4 conversion by IPV6_ADDRFORM. However, commit 03485f2adcde ("udpv6:
Add lockless sendmsg() support") added a lockless memory allocation path,
which could cause a memory leak:

setsockopt(IPV6_ADDRFORM) sendmsg()
+-----------------------+ +-------+
- do_ipv6_setsockopt(sk, ...) - udpv6_sendmsg(sk, ...)
- sockopt_lock_sock(sk) ^._ called via udpv6_prot
- lock_sock(sk) before WRITE_ONCE()
- WRITE_ONCE(sk->sk_prot, &tcp_prot)
- inet6_destroy_sock() - if (!corkreq)
- sockopt_release_sock(sk) - ip6_make_skb(sk, ...)
- release_sock(sk) ^._ lockless fast path for
the non-corking case

- __ip6_append_data(sk, ...)
- ipv6_local_rxpmtu(sk, ...)
- xchg(&np->rxpmtu, skb)
^._ rxpmtu is never freed.

- goto out_no_dst;

- lock_sock(sk)

For now, rxpmtu is only the case, but not to miss the future change
and a similar bug fixed in commit e27326009a3d ("net: ping6: Fix
memleak in ipv6_renew_options()."), let's set a new function to IPv6
sk->sk_destruct() and call inet6_cleanup_sock() there. Since the
conversion does not change sk->sk_destruct(), we can guarantee that
we can clean up IPv6 resources finally.

We can now remove all inet6_destroy_sock() calls from IPv6 protocol
specific ->destroy() functions, but such changes are invasive to
backport. So they can be posted as a follow-up later for net-next.

Fixes: 03485f2adcde ("udpv6: Add lockless sendmsg() support")
Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Kuniyuki Iwashima and committed by
Jakub Kicinski
d38afeec 21985f43

+46 -15
+1
include/net/ipv6.h
··· 1183 1183 void ipv6_local_rxpmtu(struct sock *sk, struct flowi6 *fl6, u32 mtu); 1184 1184 1185 1185 void inet6_cleanup_sock(struct sock *sk); 1186 + void inet6_sock_destruct(struct sock *sk); 1186 1187 int inet6_release(struct socket *sock); 1187 1188 int inet6_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len); 1188 1189 int inet6_getname(struct socket *sock, struct sockaddr *uaddr,
+1 -1
include/net/udp.h
··· 247 247 } 248 248 249 249 /* net/ipv4/udp.c */ 250 - void udp_destruct_sock(struct sock *sk); 250 + void udp_destruct_common(struct sock *sk); 251 251 void skb_consume_udp(struct sock *sk, struct sk_buff *skb, int len); 252 252 int __udp_enqueue_schedule_skb(struct sock *sk, struct sk_buff *skb); 253 253 void udp_skb_destructor(struct sock *sk, struct sk_buff *skb);
-8
include/net/udplite.h
··· 25 25 return copy_from_iter_full(to, len, &msg->msg_iter) ? 0 : -EFAULT; 26 26 } 27 27 28 - /* Designate sk as UDP-Lite socket */ 29 - static inline int udplite_sk_init(struct sock *sk) 30 - { 31 - udp_init_sock(sk); 32 - udp_sk(sk)->pcflag = UDPLITE_BIT; 33 - return 0; 34 - } 35 - 36 28 /* 37 29 * Checksumming routines 38 30 */
+6 -3
net/ipv4/udp.c
··· 1598 1598 } 1599 1599 EXPORT_SYMBOL_GPL(__udp_enqueue_schedule_skb); 1600 1600 1601 - void udp_destruct_sock(struct sock *sk) 1601 + void udp_destruct_common(struct sock *sk) 1602 1602 { 1603 1603 /* reclaim completely the forward allocated memory */ 1604 1604 struct udp_sock *up = udp_sk(sk); ··· 1611 1611 kfree_skb(skb); 1612 1612 } 1613 1613 udp_rmem_release(sk, total, 0, true); 1614 + } 1615 + EXPORT_SYMBOL_GPL(udp_destruct_common); 1614 1616 1617 + static void udp_destruct_sock(struct sock *sk) 1618 + { 1619 + udp_destruct_common(sk); 1615 1620 inet_sock_destruct(sk); 1616 1621 } 1617 - EXPORT_SYMBOL_GPL(udp_destruct_sock); 1618 1622 1619 1623 int udp_init_sock(struct sock *sk) 1620 1624 { ··· 1626 1622 sk->sk_destruct = udp_destruct_sock; 1627 1623 return 0; 1628 1624 } 1629 - EXPORT_SYMBOL_GPL(udp_init_sock); 1630 1625 1631 1626 void skb_consume_udp(struct sock *sk, struct sk_buff *skb, int len) 1632 1627 {
+8
net/ipv4/udplite.c
··· 17 17 struct udp_table udplite_table __read_mostly; 18 18 EXPORT_SYMBOL(udplite_table); 19 19 20 + /* Designate sk as UDP-Lite socket */ 21 + static int udplite_sk_init(struct sock *sk) 22 + { 23 + udp_init_sock(sk); 24 + udp_sk(sk)->pcflag = UDPLITE_BIT; 25 + return 0; 26 + } 27 + 20 28 static int udplite_rcv(struct sk_buff *skb) 21 29 { 22 30 return __udp4_lib_rcv(skb, &udplite_table, IPPROTO_UDPLITE);
+7 -1
net/ipv6/af_inet6.c
··· 109 109 return (struct ipv6_pinfo *)(((u8 *)sk) + offset); 110 110 } 111 111 112 + void inet6_sock_destruct(struct sock *sk) 113 + { 114 + inet6_cleanup_sock(sk); 115 + inet_sock_destruct(sk); 116 + } 117 + 112 118 static int inet6_create(struct net *net, struct socket *sock, int protocol, 113 119 int kern) 114 120 { ··· 207 201 inet->hdrincl = 1; 208 202 } 209 203 210 - sk->sk_destruct = inet_sock_destruct; 204 + sk->sk_destruct = inet6_sock_destruct; 211 205 sk->sk_family = PF_INET6; 212 206 sk->sk_protocol = protocol; 213 207
+14 -1
net/ipv6/udp.c
··· 56 56 #include <trace/events/skb.h> 57 57 #include "udp_impl.h" 58 58 59 + static void udpv6_destruct_sock(struct sock *sk) 60 + { 61 + udp_destruct_common(sk); 62 + inet6_sock_destruct(sk); 63 + } 64 + 65 + int udpv6_init_sock(struct sock *sk) 66 + { 67 + skb_queue_head_init(&udp_sk(sk)->reader_queue); 68 + sk->sk_destruct = udpv6_destruct_sock; 69 + return 0; 70 + } 71 + 59 72 static u32 udp6_ehashfn(const struct net *net, 60 73 const struct in6_addr *laddr, 61 74 const u16 lport, ··· 1746 1733 .connect = ip6_datagram_connect, 1747 1734 .disconnect = udp_disconnect, 1748 1735 .ioctl = udp_ioctl, 1749 - .init = udp_init_sock, 1736 + .init = udpv6_init_sock, 1750 1737 .destroy = udpv6_destroy_sock, 1751 1738 .setsockopt = udpv6_setsockopt, 1752 1739 .getsockopt = udpv6_getsockopt,
+1
net/ipv6/udp_impl.h
··· 12 12 int __udp6_lib_err(struct sk_buff *, struct inet6_skb_parm *, u8, u8, int, 13 13 __be32, struct udp_table *); 14 14 15 + int udpv6_init_sock(struct sock *sk); 15 16 int udp_v6_get_port(struct sock *sk, unsigned short snum); 16 17 void udp_v6_rehash(struct sock *sk); 17 18
+8 -1
net/ipv6/udplite.c
··· 12 12 #include <linux/proc_fs.h> 13 13 #include "udp_impl.h" 14 14 15 + static int udplitev6_sk_init(struct sock *sk) 16 + { 17 + udpv6_init_sock(sk); 18 + udp_sk(sk)->pcflag = UDPLITE_BIT; 19 + return 0; 20 + } 21 + 15 22 static int udplitev6_rcv(struct sk_buff *skb) 16 23 { 17 24 return __udp6_lib_rcv(skb, &udplite_table, IPPROTO_UDPLITE); ··· 45 38 .connect = ip6_datagram_connect, 46 39 .disconnect = udp_disconnect, 47 40 .ioctl = udp_ioctl, 48 - .init = udplite_sk_init, 41 + .init = udplitev6_sk_init, 49 42 .destroy = udpv6_destroy_sock, 50 43 .setsockopt = udpv6_setsockopt, 51 44 .getsockopt = udpv6_getsockopt,