diff options
author | Eric Dumazet <edumazet@google.com> | 2013-05-09 06:28:16 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2013-05-11 19:26:38 -0400 |
commit | f77d602124d865c38705df7fa25c03de9c284ad2 (patch) | |
tree | 173c11c4c8bfe13a891dcee6bf546c2bc4c9ed76 /net | |
parent | 233c7df0821c4190e2d3f4be0f2ca0ab40a5ed8c (diff) |
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 fcbdf09d9652c891
("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>
Diffstat (limited to 'net')
-rw-r--r-- | net/core/sock.c | 12 | ||||
-rw-r--r-- | net/ipv6/tcp_ipv6.c | 12 | ||||
-rw-r--r-- | net/ipv6/udp.c | 13 | ||||
-rw-r--r-- | net/ipv6/udp_impl.h | 2 | ||||
-rw-r--r-- | net/ipv6/udplite.c | 2 |
5 files changed, 27 insertions, 14 deletions
diff --git a/net/core/sock.c b/net/core/sock.c index d4f4cea726e7..6ba327da79e1 100644 --- a/net/core/sock.c +++ b/net/core/sock.c | |||
@@ -1217,18 +1217,6 @@ static void sock_copy(struct sock *nsk, const struct sock *osk) | |||
1217 | #endif | 1217 | #endif |
1218 | } | 1218 | } |
1219 | 1219 | ||
1220 | /* | ||
1221 | * caches using SLAB_DESTROY_BY_RCU should let .next pointer from nulls nodes | ||
1222 | * un-modified. Special care is taken when initializing object to zero. | ||
1223 | */ | ||
1224 | static inline void sk_prot_clear_nulls(struct sock *sk, int size) | ||
1225 | { | ||
1226 | if (offsetof(struct sock, sk_node.next) != 0) | ||
1227 | memset(sk, 0, offsetof(struct sock, sk_node.next)); | ||
1228 | memset(&sk->sk_node.pprev, 0, | ||
1229 | size - offsetof(struct sock, sk_node.pprev)); | ||
1230 | } | ||
1231 | |||
1232 | void sk_prot_clear_portaddr_nulls(struct sock *sk, int size) | 1220 | void sk_prot_clear_portaddr_nulls(struct sock *sk, int size) |
1233 | { | 1221 | { |
1234 | unsigned long nulls1, nulls2; | 1222 | unsigned long nulls1, nulls2; |
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index 71167069b394..0a17ed9eaf39 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c | |||
@@ -1890,6 +1890,17 @@ void tcp6_proc_exit(struct net *net) | |||
1890 | } | 1890 | } |
1891 | #endif | 1891 | #endif |
1892 | 1892 | ||
1893 | static void tcp_v6_clear_sk(struct sock *sk, int size) | ||
1894 | { | ||
1895 | struct inet_sock *inet = inet_sk(sk); | ||
1896 | |||
1897 | /* we do not want to clear pinet6 field, because of RCU lookups */ | ||
1898 | sk_prot_clear_nulls(sk, offsetof(struct inet_sock, pinet6)); | ||
1899 | |||
1900 | size -= offsetof(struct inet_sock, pinet6) + sizeof(inet->pinet6); | ||
1901 | memset(&inet->pinet6 + 1, 0, size); | ||
1902 | } | ||
1903 | |||
1893 | struct proto tcpv6_prot = { | 1904 | struct proto tcpv6_prot = { |
1894 | .name = "TCPv6", | 1905 | .name = "TCPv6", |
1895 | .owner = THIS_MODULE, | 1906 | .owner = THIS_MODULE, |
@@ -1933,6 +1944,7 @@ struct proto tcpv6_prot = { | |||
1933 | #ifdef CONFIG_MEMCG_KMEM | 1944 | #ifdef CONFIG_MEMCG_KMEM |
1934 | .proto_cgroup = tcp_proto_cgroup, | 1945 | .proto_cgroup = tcp_proto_cgroup, |
1935 | #endif | 1946 | #endif |
1947 | .clear_sk = tcp_v6_clear_sk, | ||
1936 | }; | 1948 | }; |
1937 | 1949 | ||
1938 | static const struct inet6_protocol tcpv6_protocol = { | 1950 | static const struct inet6_protocol tcpv6_protocol = { |
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index d4defdd44937..42923b14dfa6 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c | |||
@@ -1432,6 +1432,17 @@ void udp6_proc_exit(struct net *net) { | |||
1432 | } | 1432 | } |
1433 | #endif /* CONFIG_PROC_FS */ | 1433 | #endif /* CONFIG_PROC_FS */ |
1434 | 1434 | ||
1435 | void udp_v6_clear_sk(struct sock *sk, int size) | ||
1436 | { | ||
1437 | struct inet_sock *inet = inet_sk(sk); | ||
1438 | |||
1439 | /* we do not want to clear pinet6 field, because of RCU lookups */ | ||
1440 | sk_prot_clear_portaddr_nulls(sk, offsetof(struct inet_sock, pinet6)); | ||
1441 | |||
1442 | size -= offsetof(struct inet_sock, pinet6) + sizeof(inet->pinet6); | ||
1443 | memset(&inet->pinet6 + 1, 0, size); | ||
1444 | } | ||
1445 | |||
1435 | /* ------------------------------------------------------------------------ */ | 1446 | /* ------------------------------------------------------------------------ */ |
1436 | 1447 | ||
1437 | struct proto udpv6_prot = { | 1448 | struct proto udpv6_prot = { |
@@ -1462,7 +1473,7 @@ struct proto udpv6_prot = { | |||
1462 | .compat_setsockopt = compat_udpv6_setsockopt, | 1473 | .compat_setsockopt = compat_udpv6_setsockopt, |
1463 | .compat_getsockopt = compat_udpv6_getsockopt, | 1474 | .compat_getsockopt = compat_udpv6_getsockopt, |
1464 | #endif | 1475 | #endif |
1465 | .clear_sk = sk_prot_clear_portaddr_nulls, | 1476 | .clear_sk = udp_v6_clear_sk, |
1466 | }; | 1477 | }; |
1467 | 1478 | ||
1468 | static struct inet_protosw udpv6_protosw = { | 1479 | static struct inet_protosw udpv6_protosw = { |
diff --git a/net/ipv6/udp_impl.h b/net/ipv6/udp_impl.h index d7571046bfc4..4691ed50a928 100644 --- a/net/ipv6/udp_impl.h +++ b/net/ipv6/udp_impl.h | |||
@@ -31,6 +31,8 @@ extern int udpv6_recvmsg(struct kiocb *iocb, struct sock *sk, | |||
31 | extern int udpv6_queue_rcv_skb(struct sock * sk, struct sk_buff *skb); | 31 | extern int udpv6_queue_rcv_skb(struct sock * sk, struct sk_buff *skb); |
32 | extern void udpv6_destroy_sock(struct sock *sk); | 32 | extern void udpv6_destroy_sock(struct sock *sk); |
33 | 33 | ||
34 | extern void udp_v6_clear_sk(struct sock *sk, int size); | ||
35 | |||
34 | #ifdef CONFIG_PROC_FS | 36 | #ifdef CONFIG_PROC_FS |
35 | extern int udp6_seq_show(struct seq_file *seq, void *v); | 37 | extern int udp6_seq_show(struct seq_file *seq, void *v); |
36 | #endif | 38 | #endif |
diff --git a/net/ipv6/udplite.c b/net/ipv6/udplite.c index 1d08e21d9f69..dfcc4be46898 100644 --- a/net/ipv6/udplite.c +++ b/net/ipv6/udplite.c | |||
@@ -56,7 +56,7 @@ struct proto udplitev6_prot = { | |||
56 | .compat_setsockopt = compat_udpv6_setsockopt, | 56 | .compat_setsockopt = compat_udpv6_setsockopt, |
57 | .compat_getsockopt = compat_udpv6_getsockopt, | 57 | .compat_getsockopt = compat_udpv6_getsockopt, |
58 | #endif | 58 | #endif |
59 | .clear_sk = sk_prot_clear_portaddr_nulls, | 59 | .clear_sk = udp_v6_clear_sk, |
60 | }; | 60 | }; |
61 | 61 | ||
62 | static struct inet_protosw udplite6_protosw = { | 62 | static struct inet_protosw udplite6_protosw = { |