diff options
author | Dmitry Mishin <dim@openvz.org> | 2006-08-31 18:28:39 -0400 |
---|---|---|
committer | David S. Miller <davem@sunset.davemloft.net> | 2006-09-22 18:18:47 -0400 |
commit | fda9ef5d679b07c9d9097aaf6ef7f069d794a8f9 (patch) | |
tree | 6a265dc2038bc5568c5a499e6c8d4733650ed3f7 | |
parent | dc435e6dac1439340eaeceef84022c4e4749796d (diff) |
[NET]: Fix sk->sk_filter field access
Function sk_filter() is called from tcp_v{4,6}_rcv() functions with arg
needlock = 0, while socket is not locked at that moment. In order to avoid
this and similar issues in the future, use rcu for sk->sk_filter field read
protection.
Signed-off-by: Dmitry Mishin <dim@openvz.org>
Signed-off-by: Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
Signed-off-by: Kirill Korotaev <dev@openvz.org>
-rw-r--r-- | include/linux/filter.h | 13 | ||||
-rw-r--r-- | include/net/sock.h | 34 | ||||
-rw-r--r-- | net/core/filter.c | 8 | ||||
-rw-r--r-- | net/core/sock.c | 22 | ||||
-rw-r--r-- | net/dccp/ipv6.c | 2 | ||||
-rw-r--r-- | net/decnet/dn_nsp_in.c | 2 | ||||
-rw-r--r-- | net/ipv4/tcp_ipv4.c | 2 | ||||
-rw-r--r-- | net/ipv6/tcp_ipv6.c | 4 | ||||
-rw-r--r-- | net/packet/af_packet.c | 43 | ||||
-rw-r--r-- | net/sctp/input.c | 2 |
10 files changed, 61 insertions, 71 deletions
diff --git a/include/linux/filter.h b/include/linux/filter.h index c6cb8f095088..91b2e3b9251e 100644 --- a/include/linux/filter.h +++ b/include/linux/filter.h | |||
@@ -25,10 +25,10 @@ | |||
25 | 25 | ||
26 | struct sock_filter /* Filter block */ | 26 | struct sock_filter /* Filter block */ |
27 | { | 27 | { |
28 | __u16 code; /* Actual filter code */ | 28 | __u16 code; /* Actual filter code */ |
29 | __u8 jt; /* Jump true */ | 29 | __u8 jt; /* Jump true */ |
30 | __u8 jf; /* Jump false */ | 30 | __u8 jf; /* Jump false */ |
31 | __u32 k; /* Generic multiuse field */ | 31 | __u32 k; /* Generic multiuse field */ |
32 | }; | 32 | }; |
33 | 33 | ||
34 | struct sock_fprog /* Required for SO_ATTACH_FILTER. */ | 34 | struct sock_fprog /* Required for SO_ATTACH_FILTER. */ |
@@ -41,8 +41,9 @@ struct sock_fprog /* Required for SO_ATTACH_FILTER. */ | |||
41 | struct sk_filter | 41 | struct sk_filter |
42 | { | 42 | { |
43 | atomic_t refcnt; | 43 | atomic_t refcnt; |
44 | unsigned int len; /* Number of filter blocks */ | 44 | unsigned int len; /* Number of filter blocks */ |
45 | struct sock_filter insns[0]; | 45 | struct rcu_head rcu; |
46 | struct sock_filter insns[0]; | ||
46 | }; | 47 | }; |
47 | 48 | ||
48 | static inline unsigned int sk_filter_len(struct sk_filter *fp) | 49 | static inline unsigned int sk_filter_len(struct sk_filter *fp) |
diff --git a/include/net/sock.h b/include/net/sock.h index 337ebec84c70..edd4d73ce7f5 100644 --- a/include/net/sock.h +++ b/include/net/sock.h | |||
@@ -862,30 +862,24 @@ extern void sock_init_data(struct socket *sock, struct sock *sk); | |||
862 | * | 862 | * |
863 | */ | 863 | */ |
864 | 864 | ||
865 | static inline int sk_filter(struct sock *sk, struct sk_buff *skb, int needlock) | 865 | static inline int sk_filter(struct sock *sk, struct sk_buff *skb) |
866 | { | 866 | { |
867 | int err; | 867 | int err; |
868 | struct sk_filter *filter; | ||
868 | 869 | ||
869 | err = security_sock_rcv_skb(sk, skb); | 870 | err = security_sock_rcv_skb(sk, skb); |
870 | if (err) | 871 | if (err) |
871 | return err; | 872 | return err; |
872 | 873 | ||
873 | if (sk->sk_filter) { | 874 | rcu_read_lock_bh(); |
874 | struct sk_filter *filter; | 875 | filter = sk->sk_filter; |
875 | 876 | if (filter) { | |
876 | if (needlock) | 877 | unsigned int pkt_len = sk_run_filter(skb, filter->insns, |
877 | bh_lock_sock(sk); | 878 | filter->len); |
878 | 879 | err = pkt_len ? pskb_trim(skb, pkt_len) : -EPERM; | |
879 | filter = sk->sk_filter; | ||
880 | if (filter) { | ||
881 | unsigned int pkt_len = sk_run_filter(skb, filter->insns, | ||
882 | filter->len); | ||
883 | err = pkt_len ? pskb_trim(skb, pkt_len) : -EPERM; | ||
884 | } | ||
885 | |||
886 | if (needlock) | ||
887 | bh_unlock_sock(sk); | ||
888 | } | 880 | } |
881 | rcu_read_unlock_bh(); | ||
882 | |||
889 | return err; | 883 | return err; |
890 | } | 884 | } |
891 | 885 | ||
@@ -897,6 +891,12 @@ static inline int sk_filter(struct sock *sk, struct sk_buff *skb, int needlock) | |||
897 | * Remove a filter from a socket and release its resources. | 891 | * Remove a filter from a socket and release its resources. |
898 | */ | 892 | */ |
899 | 893 | ||
894 | static inline void sk_filter_rcu_free(struct rcu_head *rcu) | ||
895 | { | ||
896 | struct sk_filter *fp = container_of(rcu, struct sk_filter, rcu); | ||
897 | kfree(fp); | ||
898 | } | ||
899 | |||
900 | static inline void sk_filter_release(struct sock *sk, struct sk_filter *fp) | 900 | static inline void sk_filter_release(struct sock *sk, struct sk_filter *fp) |
901 | { | 901 | { |
902 | unsigned int size = sk_filter_len(fp); | 902 | unsigned int size = sk_filter_len(fp); |
@@ -904,7 +904,7 @@ static inline void sk_filter_release(struct sock *sk, struct sk_filter *fp) | |||
904 | atomic_sub(size, &sk->sk_omem_alloc); | 904 | atomic_sub(size, &sk->sk_omem_alloc); |
905 | 905 | ||
906 | if (atomic_dec_and_test(&fp->refcnt)) | 906 | if (atomic_dec_and_test(&fp->refcnt)) |
907 | kfree(fp); | 907 | call_rcu_bh(&fp->rcu, sk_filter_rcu_free); |
908 | } | 908 | } |
909 | 909 | ||
910 | static inline void sk_filter_charge(struct sock *sk, struct sk_filter *fp) | 910 | static inline void sk_filter_charge(struct sock *sk, struct sk_filter *fp) |
diff --git a/net/core/filter.c b/net/core/filter.c index 5b4486a60cf6..6732782a5a40 100644 --- a/net/core/filter.c +++ b/net/core/filter.c | |||
@@ -422,10 +422,10 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk) | |||
422 | if (!err) { | 422 | if (!err) { |
423 | struct sk_filter *old_fp; | 423 | struct sk_filter *old_fp; |
424 | 424 | ||
425 | spin_lock_bh(&sk->sk_lock.slock); | 425 | rcu_read_lock_bh(); |
426 | old_fp = sk->sk_filter; | 426 | old_fp = rcu_dereference(sk->sk_filter); |
427 | sk->sk_filter = fp; | 427 | rcu_assign_pointer(sk->sk_filter, fp); |
428 | spin_unlock_bh(&sk->sk_lock.slock); | 428 | rcu_read_unlock_bh(); |
429 | fp = old_fp; | 429 | fp = old_fp; |
430 | } | 430 | } |
431 | 431 | ||
diff --git a/net/core/sock.c b/net/core/sock.c index cfaf09039b02..b77e155cbe6c 100644 --- a/net/core/sock.c +++ b/net/core/sock.c | |||
@@ -247,11 +247,7 @@ int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb) | |||
247 | goto out; | 247 | goto out; |
248 | } | 248 | } |
249 | 249 | ||
250 | /* It would be deadlock, if sock_queue_rcv_skb is used | 250 | err = sk_filter(sk, skb); |
251 | with socket lock! We assume that users of this | ||
252 | function are lock free. | ||
253 | */ | ||
254 | err = sk_filter(sk, skb, 1); | ||
255 | if (err) | 251 | if (err) |
256 | goto out; | 252 | goto out; |
257 | 253 | ||
@@ -278,7 +274,7 @@ int sk_receive_skb(struct sock *sk, struct sk_buff *skb) | |||
278 | { | 274 | { |
279 | int rc = NET_RX_SUCCESS; | 275 | int rc = NET_RX_SUCCESS; |
280 | 276 | ||
281 | if (sk_filter(sk, skb, 0)) | 277 | if (sk_filter(sk, skb)) |
282 | goto discard_and_relse; | 278 | goto discard_and_relse; |
283 | 279 | ||
284 | skb->dev = NULL; | 280 | skb->dev = NULL; |
@@ -606,15 +602,15 @@ set_rcvbuf: | |||
606 | break; | 602 | break; |
607 | 603 | ||
608 | case SO_DETACH_FILTER: | 604 | case SO_DETACH_FILTER: |
609 | spin_lock_bh(&sk->sk_lock.slock); | 605 | rcu_read_lock_bh(); |
610 | filter = sk->sk_filter; | 606 | filter = rcu_dereference(sk->sk_filter); |
611 | if (filter) { | 607 | if (filter) { |
612 | sk->sk_filter = NULL; | 608 | rcu_assign_pointer(sk->sk_filter, NULL); |
613 | spin_unlock_bh(&sk->sk_lock.slock); | ||
614 | sk_filter_release(sk, filter); | 609 | sk_filter_release(sk, filter); |
610 | rcu_read_unlock_bh(); | ||
615 | break; | 611 | break; |
616 | } | 612 | } |
617 | spin_unlock_bh(&sk->sk_lock.slock); | 613 | rcu_read_unlock_bh(); |
618 | ret = -ENONET; | 614 | ret = -ENONET; |
619 | break; | 615 | break; |
620 | 616 | ||
@@ -884,10 +880,10 @@ void sk_free(struct sock *sk) | |||
884 | if (sk->sk_destruct) | 880 | if (sk->sk_destruct) |
885 | sk->sk_destruct(sk); | 881 | sk->sk_destruct(sk); |
886 | 882 | ||
887 | filter = sk->sk_filter; | 883 | filter = rcu_dereference(sk->sk_filter); |
888 | if (filter) { | 884 | if (filter) { |
889 | sk_filter_release(sk, filter); | 885 | sk_filter_release(sk, filter); |
890 | sk->sk_filter = NULL; | 886 | rcu_assign_pointer(sk->sk_filter, NULL); |
891 | } | 887 | } |
892 | 888 | ||
893 | sock_disable_timestamp(sk); | 889 | sock_disable_timestamp(sk); |
diff --git a/net/dccp/ipv6.c b/net/dccp/ipv6.c index f9c5e12d7038..7a47399cf31f 100644 --- a/net/dccp/ipv6.c +++ b/net/dccp/ipv6.c | |||
@@ -970,7 +970,7 @@ static int dccp_v6_do_rcv(struct sock *sk, struct sk_buff *skb) | |||
970 | if (skb->protocol == htons(ETH_P_IP)) | 970 | if (skb->protocol == htons(ETH_P_IP)) |
971 | return dccp_v4_do_rcv(sk, skb); | 971 | return dccp_v4_do_rcv(sk, skb); |
972 | 972 | ||
973 | if (sk_filter(sk, skb, 0)) | 973 | if (sk_filter(sk, skb)) |
974 | goto discard; | 974 | goto discard; |
975 | 975 | ||
976 | /* | 976 | /* |
diff --git a/net/decnet/dn_nsp_in.c b/net/decnet/dn_nsp_in.c index 86f7f3b28e70..72ecc6e62ec4 100644 --- a/net/decnet/dn_nsp_in.c +++ b/net/decnet/dn_nsp_in.c | |||
@@ -586,7 +586,7 @@ static __inline__ int dn_queue_skb(struct sock *sk, struct sk_buff *skb, int sig | |||
586 | goto out; | 586 | goto out; |
587 | } | 587 | } |
588 | 588 | ||
589 | err = sk_filter(sk, skb, 0); | 589 | err = sk_filter(sk, skb); |
590 | if (err) | 590 | if (err) |
591 | goto out; | 591 | goto out; |
592 | 592 | ||
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 23b46e36b147..39b179856082 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c | |||
@@ -1104,7 +1104,7 @@ process: | |||
1104 | goto discard_and_relse; | 1104 | goto discard_and_relse; |
1105 | nf_reset(skb); | 1105 | nf_reset(skb); |
1106 | 1106 | ||
1107 | if (sk_filter(sk, skb, 0)) | 1107 | if (sk_filter(sk, skb)) |
1108 | goto discard_and_relse; | 1108 | goto discard_and_relse; |
1109 | 1109 | ||
1110 | skb->dev = NULL; | 1110 | skb->dev = NULL; |
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index 2b18918f3011..2546fc9f0a78 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c | |||
@@ -1075,7 +1075,7 @@ static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb) | |||
1075 | if (skb->protocol == htons(ETH_P_IP)) | 1075 | if (skb->protocol == htons(ETH_P_IP)) |
1076 | return tcp_v4_do_rcv(sk, skb); | 1076 | return tcp_v4_do_rcv(sk, skb); |
1077 | 1077 | ||
1078 | if (sk_filter(sk, skb, 0)) | 1078 | if (sk_filter(sk, skb)) |
1079 | goto discard; | 1079 | goto discard; |
1080 | 1080 | ||
1081 | /* | 1081 | /* |
@@ -1232,7 +1232,7 @@ process: | |||
1232 | if (!xfrm6_policy_check(sk, XFRM_POLICY_IN, skb)) | 1232 | if (!xfrm6_policy_check(sk, XFRM_POLICY_IN, skb)) |
1233 | goto discard_and_relse; | 1233 | goto discard_and_relse; |
1234 | 1234 | ||
1235 | if (sk_filter(sk, skb, 0)) | 1235 | if (sk_filter(sk, skb)) |
1236 | goto discard_and_relse; | 1236 | goto discard_and_relse; |
1237 | 1237 | ||
1238 | skb->dev = NULL; | 1238 | skb->dev = NULL; |
diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 300215bdbf46..f4ccb90e6739 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c | |||
@@ -427,21 +427,24 @@ out_unlock: | |||
427 | } | 427 | } |
428 | #endif | 428 | #endif |
429 | 429 | ||
430 | static inline unsigned run_filter(struct sk_buff *skb, struct sock *sk, unsigned res) | 430 | static inline int run_filter(struct sk_buff *skb, struct sock *sk, |
431 | unsigned *snaplen) | ||
431 | { | 432 | { |
432 | struct sk_filter *filter; | 433 | struct sk_filter *filter; |
434 | int err = 0; | ||
433 | 435 | ||
434 | bh_lock_sock(sk); | 436 | rcu_read_lock_bh(); |
435 | filter = sk->sk_filter; | 437 | filter = rcu_dereference(sk->sk_filter); |
436 | /* | 438 | if (filter != NULL) { |
437 | * Our caller already checked that filter != NULL but we need to | 439 | err = sk_run_filter(skb, filter->insns, filter->len); |
438 | * verify that under bh_lock_sock() to be safe | 440 | if (!err) |
439 | */ | 441 | err = -EPERM; |
440 | if (likely(filter != NULL)) | 442 | else if (*snaplen > err) |
441 | res = sk_run_filter(skb, filter->insns, filter->len); | 443 | *snaplen = err; |
442 | bh_unlock_sock(sk); | 444 | } |
445 | rcu_read_unlock_bh(); | ||
443 | 446 | ||
444 | return res; | 447 | return err; |
445 | } | 448 | } |
446 | 449 | ||
447 | /* | 450 | /* |
@@ -491,13 +494,8 @@ static int packet_rcv(struct sk_buff *skb, struct net_device *dev, struct packet | |||
491 | 494 | ||
492 | snaplen = skb->len; | 495 | snaplen = skb->len; |
493 | 496 | ||
494 | if (sk->sk_filter) { | 497 | if (run_filter(skb, sk, &snaplen) < 0) |
495 | unsigned res = run_filter(skb, sk, snaplen); | 498 | goto drop_n_restore; |
496 | if (res == 0) | ||
497 | goto drop_n_restore; | ||
498 | if (snaplen > res) | ||
499 | snaplen = res; | ||
500 | } | ||
501 | 499 | ||
502 | if (atomic_read(&sk->sk_rmem_alloc) + skb->truesize >= | 500 | if (atomic_read(&sk->sk_rmem_alloc) + skb->truesize >= |
503 | (unsigned)sk->sk_rcvbuf) | 501 | (unsigned)sk->sk_rcvbuf) |
@@ -593,13 +591,8 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev, struct packe | |||
593 | 591 | ||
594 | snaplen = skb->len; | 592 | snaplen = skb->len; |
595 | 593 | ||
596 | if (sk->sk_filter) { | 594 | if (run_filter(skb, sk, &snaplen) < 0) |
597 | unsigned res = run_filter(skb, sk, snaplen); | 595 | goto drop_n_restore; |
598 | if (res == 0) | ||
599 | goto drop_n_restore; | ||
600 | if (snaplen > res) | ||
601 | snaplen = res; | ||
602 | } | ||
603 | 596 | ||
604 | if (sk->sk_type == SOCK_DGRAM) { | 597 | if (sk->sk_type == SOCK_DGRAM) { |
605 | macoff = netoff = TPACKET_ALIGN(TPACKET_HDRLEN) + 16; | 598 | macoff = netoff = TPACKET_ALIGN(TPACKET_HDRLEN) + 16; |
diff --git a/net/sctp/input.c b/net/sctp/input.c index 8a34d95602ce..03f65de75d88 100644 --- a/net/sctp/input.c +++ b/net/sctp/input.c | |||
@@ -228,7 +228,7 @@ int sctp_rcv(struct sk_buff *skb) | |||
228 | goto discard_release; | 228 | goto discard_release; |
229 | nf_reset(skb); | 229 | nf_reset(skb); |
230 | 230 | ||
231 | if (sk_filter(sk, skb, 1)) | 231 | if (sk_filter(sk, skb)) |
232 | goto discard_release; | 232 | goto discard_release; |
233 | 233 | ||
234 | /* Create an SCTP packet structure. */ | 234 | /* Create an SCTP packet structure. */ |