ipv6: do not clear pinet6 field
We have seen multiple NULL dereferences in __inet6_lookup_established()
After analysis, I found that inet6_sk() could be NULL while the
check for sk_family == AF_INET6 was true.
Bug was added in linux-2.6.29 when RCU lookups were introduced in UDP
and TCP stacks.
Once an IPv6 socket, using SLAB_DESTROY_BY_RCU is inserted in a hash
table, we no longer can clear pinet6 field.
This patch extends logic used in commit fcbdf09d96
("net: fix nulls list corruptions in sk_prot_alloc")
TCP/UDP/UDPLite IPv6 protocols provide their own .clear_sk() method
to make sure we do not clear pinet6 field.
At socket clone phase, we do not really care, as cloning the parent (non
NULL) pinet6 is not adding a fatal race.
Signed-off-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
committed by
David S. Miller
parent
233c7df082
commit
f77d602124
@@ -866,6 +866,18 @@ struct inet_hashinfo;
|
|||||||
struct raw_hashinfo;
|
struct raw_hashinfo;
|
||||||
struct module;
|
struct module;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* caches using SLAB_DESTROY_BY_RCU should let .next pointer from nulls nodes
|
||||||
|
* un-modified. Special care is taken when initializing object to zero.
|
||||||
|
*/
|
||||||
|
static inline void sk_prot_clear_nulls(struct sock *sk, int size)
|
||||||
|
{
|
||||||
|
if (offsetof(struct sock, sk_node.next) != 0)
|
||||||
|
memset(sk, 0, offsetof(struct sock, sk_node.next));
|
||||||
|
memset(&sk->sk_node.pprev, 0,
|
||||||
|
size - offsetof(struct sock, sk_node.pprev));
|
||||||
|
}
|
||||||
|
|
||||||
/* Networking protocol blocks we attach to sockets.
|
/* Networking protocol blocks we attach to sockets.
|
||||||
* socket layer -> transport layer interface
|
* socket layer -> transport layer interface
|
||||||
* transport -> network interface is defined by struct inet_proto
|
* transport -> network interface is defined by struct inet_proto
|
||||||
|
@@ -1217,18 +1217,6 @@ static void sock_copy(struct sock *nsk, const struct sock *osk)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* caches using SLAB_DESTROY_BY_RCU should let .next pointer from nulls nodes
|
|
||||||
* un-modified. Special care is taken when initializing object to zero.
|
|
||||||
*/
|
|
||||||
static inline void sk_prot_clear_nulls(struct sock *sk, int size)
|
|
||||||
{
|
|
||||||
if (offsetof(struct sock, sk_node.next) != 0)
|
|
||||||
memset(sk, 0, offsetof(struct sock, sk_node.next));
|
|
||||||
memset(&sk->sk_node.pprev, 0,
|
|
||||||
size - offsetof(struct sock, sk_node.pprev));
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_prot_clear_portaddr_nulls(struct sock *sk, int size)
|
void sk_prot_clear_portaddr_nulls(struct sock *sk, int size)
|
||||||
{
|
{
|
||||||
unsigned long nulls1, nulls2;
|
unsigned long nulls1, nulls2;
|
||||||
|
@@ -1890,6 +1890,17 @@ void tcp6_proc_exit(struct net *net)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static void tcp_v6_clear_sk(struct sock *sk, int size)
|
||||||
|
{
|
||||||
|
struct inet_sock *inet = inet_sk(sk);
|
||||||
|
|
||||||
|
/* we do not want to clear pinet6 field, because of RCU lookups */
|
||||||
|
sk_prot_clear_nulls(sk, offsetof(struct inet_sock, pinet6));
|
||||||
|
|
||||||
|
size -= offsetof(struct inet_sock, pinet6) + sizeof(inet->pinet6);
|
||||||
|
memset(&inet->pinet6 + 1, 0, size);
|
||||||
|
}
|
||||||
|
|
||||||
struct proto tcpv6_prot = {
|
struct proto tcpv6_prot = {
|
||||||
.name = "TCPv6",
|
.name = "TCPv6",
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
@@ -1933,6 +1944,7 @@ struct proto tcpv6_prot = {
|
|||||||
#ifdef CONFIG_MEMCG_KMEM
|
#ifdef CONFIG_MEMCG_KMEM
|
||||||
.proto_cgroup = tcp_proto_cgroup,
|
.proto_cgroup = tcp_proto_cgroup,
|
||||||
#endif
|
#endif
|
||||||
|
.clear_sk = tcp_v6_clear_sk,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct inet6_protocol tcpv6_protocol = {
|
static const struct inet6_protocol tcpv6_protocol = {
|
||||||
|
@@ -1432,6 +1432,17 @@ void udp6_proc_exit(struct net *net) {
|
|||||||
}
|
}
|
||||||
#endif /* CONFIG_PROC_FS */
|
#endif /* CONFIG_PROC_FS */
|
||||||
|
|
||||||
|
void udp_v6_clear_sk(struct sock *sk, int size)
|
||||||
|
{
|
||||||
|
struct inet_sock *inet = inet_sk(sk);
|
||||||
|
|
||||||
|
/* we do not want to clear pinet6 field, because of RCU lookups */
|
||||||
|
sk_prot_clear_portaddr_nulls(sk, offsetof(struct inet_sock, pinet6));
|
||||||
|
|
||||||
|
size -= offsetof(struct inet_sock, pinet6) + sizeof(inet->pinet6);
|
||||||
|
memset(&inet->pinet6 + 1, 0, size);
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
struct proto udpv6_prot = {
|
struct proto udpv6_prot = {
|
||||||
@@ -1462,7 +1473,7 @@ struct proto udpv6_prot = {
|
|||||||
.compat_setsockopt = compat_udpv6_setsockopt,
|
.compat_setsockopt = compat_udpv6_setsockopt,
|
||||||
.compat_getsockopt = compat_udpv6_getsockopt,
|
.compat_getsockopt = compat_udpv6_getsockopt,
|
||||||
#endif
|
#endif
|
||||||
.clear_sk = sk_prot_clear_portaddr_nulls,
|
.clear_sk = udp_v6_clear_sk,
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct inet_protosw udpv6_protosw = {
|
static struct inet_protosw udpv6_protosw = {
|
||||||
|
@@ -31,6 +31,8 @@ extern int udpv6_recvmsg(struct kiocb *iocb, struct sock *sk,
|
|||||||
extern int udpv6_queue_rcv_skb(struct sock * sk, struct sk_buff *skb);
|
extern int udpv6_queue_rcv_skb(struct sock * sk, struct sk_buff *skb);
|
||||||
extern void udpv6_destroy_sock(struct sock *sk);
|
extern void udpv6_destroy_sock(struct sock *sk);
|
||||||
|
|
||||||
|
extern void udp_v6_clear_sk(struct sock *sk, int size);
|
||||||
|
|
||||||
#ifdef CONFIG_PROC_FS
|
#ifdef CONFIG_PROC_FS
|
||||||
extern int udp6_seq_show(struct seq_file *seq, void *v);
|
extern int udp6_seq_show(struct seq_file *seq, void *v);
|
||||||
#endif
|
#endif
|
||||||
|
@@ -56,7 +56,7 @@ struct proto udplitev6_prot = {
|
|||||||
.compat_setsockopt = compat_udpv6_setsockopt,
|
.compat_setsockopt = compat_udpv6_setsockopt,
|
||||||
.compat_getsockopt = compat_udpv6_getsockopt,
|
.compat_getsockopt = compat_udpv6_getsockopt,
|
||||||
#endif
|
#endif
|
||||||
.clear_sk = sk_prot_clear_portaddr_nulls,
|
.clear_sk = udp_v6_clear_sk,
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct inet_protosw udplite6_protosw = {
|
static struct inet_protosw udplite6_protosw = {
|
||||||
|
Reference in New Issue
Block a user