diff options
author | Vlad Yasevich <vladislav.yasevich@hp.com> | 2007-09-16 19:02:12 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2007-09-16 19:02:12 -0400 |
commit | 293035479942400a7fe8e4f72465d4e4e466b91a (patch) | |
tree | af9890403a554b4cf8389a9116080a0d1aa187fb /net | |
parent | ddeee3ce7fbf0e800f2a26a76d6018b42b337cc2 (diff) |
[SCTP]: Add RCU synchronization around sctp_localaddr_list
sctp_localaddr_list is modified dynamically via NETDEV_UP
and NETDEV_DOWN events, but there is not synchronization
between writer (even handler) and readers. As a result,
the readers can access an entry that has been freed and
crash the sytem.
Signed-off-by: Vlad Yasevich <vladislav.yasevich@hp.com>
Acked-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Acked-by: Sridhar Samdurala <sri@us.ibm.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r-- | net/sctp/bind_addr.c | 2 | ||||
-rw-r--r-- | net/sctp/ipv6.c | 34 | ||||
-rw-r--r-- | net/sctp/protocol.c | 54 | ||||
-rw-r--r-- | net/sctp/socket.c | 38 |
4 files changed, 90 insertions, 38 deletions
diff --git a/net/sctp/bind_addr.c b/net/sctp/bind_addr.c index fdb287a9e2e2..7fc369f9035d 100644 --- a/net/sctp/bind_addr.c +++ b/net/sctp/bind_addr.c | |||
@@ -163,8 +163,10 @@ int sctp_add_bind_addr(struct sctp_bind_addr *bp, union sctp_addr *new, | |||
163 | addr->a.v4.sin_port = htons(bp->port); | 163 | addr->a.v4.sin_port = htons(bp->port); |
164 | 164 | ||
165 | addr->use_as_src = use_as_src; | 165 | addr->use_as_src = use_as_src; |
166 | addr->valid = 1; | ||
166 | 167 | ||
167 | INIT_LIST_HEAD(&addr->list); | 168 | INIT_LIST_HEAD(&addr->list); |
169 | INIT_RCU_HEAD(&addr->rcu); | ||
168 | list_add_tail(&addr->list, &bp->address_list); | 170 | list_add_tail(&addr->list, &bp->address_list); |
169 | SCTP_DBG_OBJCNT_INC(addr); | 171 | SCTP_DBG_OBJCNT_INC(addr); |
170 | 172 | ||
diff --git a/net/sctp/ipv6.c b/net/sctp/ipv6.c index f8aa23dda1c1..e12fa0a91da4 100644 --- a/net/sctp/ipv6.c +++ b/net/sctp/ipv6.c | |||
@@ -77,13 +77,18 @@ | |||
77 | 77 | ||
78 | #include <asm/uaccess.h> | 78 | #include <asm/uaccess.h> |
79 | 79 | ||
80 | /* Event handler for inet6 address addition/deletion events. */ | 80 | /* Event handler for inet6 address addition/deletion events. |
81 | * The sctp_local_addr_list needs to be protocted by a spin lock since | ||
82 | * multiple notifiers (say IPv4 and IPv6) may be running at the same | ||
83 | * time and thus corrupt the list. | ||
84 | * The reader side is protected with RCU. | ||
85 | */ | ||
81 | static int sctp_inet6addr_event(struct notifier_block *this, unsigned long ev, | 86 | static int sctp_inet6addr_event(struct notifier_block *this, unsigned long ev, |
82 | void *ptr) | 87 | void *ptr) |
83 | { | 88 | { |
84 | struct inet6_ifaddr *ifa = (struct inet6_ifaddr *)ptr; | 89 | struct inet6_ifaddr *ifa = (struct inet6_ifaddr *)ptr; |
85 | struct sctp_sockaddr_entry *addr; | 90 | struct sctp_sockaddr_entry *addr = NULL; |
86 | struct list_head *pos, *temp; | 91 | struct sctp_sockaddr_entry *temp; |
87 | 92 | ||
88 | switch (ev) { | 93 | switch (ev) { |
89 | case NETDEV_UP: | 94 | case NETDEV_UP: |
@@ -94,19 +99,26 @@ static int sctp_inet6addr_event(struct notifier_block *this, unsigned long ev, | |||
94 | memcpy(&addr->a.v6.sin6_addr, &ifa->addr, | 99 | memcpy(&addr->a.v6.sin6_addr, &ifa->addr, |
95 | sizeof(struct in6_addr)); | 100 | sizeof(struct in6_addr)); |
96 | addr->a.v6.sin6_scope_id = ifa->idev->dev->ifindex; | 101 | addr->a.v6.sin6_scope_id = ifa->idev->dev->ifindex; |
97 | list_add_tail(&addr->list, &sctp_local_addr_list); | 102 | addr->valid = 1; |
103 | spin_lock_bh(&sctp_local_addr_lock); | ||
104 | list_add_tail_rcu(&addr->list, &sctp_local_addr_list); | ||
105 | spin_unlock_bh(&sctp_local_addr_lock); | ||
98 | } | 106 | } |
99 | break; | 107 | break; |
100 | case NETDEV_DOWN: | 108 | case NETDEV_DOWN: |
101 | list_for_each_safe(pos, temp, &sctp_local_addr_list) { | 109 | spin_lock_bh(&sctp_local_addr_lock); |
102 | addr = list_entry(pos, struct sctp_sockaddr_entry, list); | 110 | list_for_each_entry_safe(addr, temp, |
103 | if (ipv6_addr_equal(&addr->a.v6.sin6_addr, &ifa->addr)) { | 111 | &sctp_local_addr_list, list) { |
104 | list_del(pos); | 112 | if (ipv6_addr_equal(&addr->a.v6.sin6_addr, |
105 | kfree(addr); | 113 | &ifa->addr)) { |
114 | addr->valid = 0; | ||
115 | list_del_rcu(&addr->list); | ||
106 | break; | 116 | break; |
107 | } | 117 | } |
108 | } | 118 | } |
109 | 119 | spin_unlock_bh(&sctp_local_addr_lock); | |
120 | if (addr && !addr->valid) | ||
121 | call_rcu(&addr->rcu, sctp_local_addr_free); | ||
110 | break; | 122 | break; |
111 | } | 123 | } |
112 | 124 | ||
@@ -367,7 +379,9 @@ static void sctp_v6_copy_addrlist(struct list_head *addrlist, | |||
367 | addr->a.v6.sin6_port = 0; | 379 | addr->a.v6.sin6_port = 0; |
368 | addr->a.v6.sin6_addr = ifp->addr; | 380 | addr->a.v6.sin6_addr = ifp->addr; |
369 | addr->a.v6.sin6_scope_id = dev->ifindex; | 381 | addr->a.v6.sin6_scope_id = dev->ifindex; |
382 | addr->valid = 1; | ||
370 | INIT_LIST_HEAD(&addr->list); | 383 | INIT_LIST_HEAD(&addr->list); |
384 | INIT_RCU_HEAD(&addr->rcu); | ||
371 | list_add_tail(&addr->list, addrlist); | 385 | list_add_tail(&addr->list, addrlist); |
372 | } | 386 | } |
373 | } | 387 | } |
diff --git a/net/sctp/protocol.c b/net/sctp/protocol.c index e98579b788b8..7ee120e85913 100644 --- a/net/sctp/protocol.c +++ b/net/sctp/protocol.c | |||
@@ -153,6 +153,9 @@ static void sctp_v4_copy_addrlist(struct list_head *addrlist, | |||
153 | addr->a.v4.sin_family = AF_INET; | 153 | addr->a.v4.sin_family = AF_INET; |
154 | addr->a.v4.sin_port = 0; | 154 | addr->a.v4.sin_port = 0; |
155 | addr->a.v4.sin_addr.s_addr = ifa->ifa_local; | 155 | addr->a.v4.sin_addr.s_addr = ifa->ifa_local; |
156 | addr->valid = 1; | ||
157 | INIT_LIST_HEAD(&addr->list); | ||
158 | INIT_RCU_HEAD(&addr->rcu); | ||
156 | list_add_tail(&addr->list, addrlist); | 159 | list_add_tail(&addr->list, addrlist); |
157 | } | 160 | } |
158 | } | 161 | } |
@@ -192,16 +195,24 @@ static void sctp_free_local_addr_list(void) | |||
192 | } | 195 | } |
193 | } | 196 | } |
194 | 197 | ||
198 | void sctp_local_addr_free(struct rcu_head *head) | ||
199 | { | ||
200 | struct sctp_sockaddr_entry *e = container_of(head, | ||
201 | struct sctp_sockaddr_entry, rcu); | ||
202 | kfree(e); | ||
203 | } | ||
204 | |||
195 | /* Copy the local addresses which are valid for 'scope' into 'bp'. */ | 205 | /* Copy the local addresses which are valid for 'scope' into 'bp'. */ |
196 | int sctp_copy_local_addr_list(struct sctp_bind_addr *bp, sctp_scope_t scope, | 206 | int sctp_copy_local_addr_list(struct sctp_bind_addr *bp, sctp_scope_t scope, |
197 | gfp_t gfp, int copy_flags) | 207 | gfp_t gfp, int copy_flags) |
198 | { | 208 | { |
199 | struct sctp_sockaddr_entry *addr; | 209 | struct sctp_sockaddr_entry *addr; |
200 | int error = 0; | 210 | int error = 0; |
201 | struct list_head *pos, *temp; | ||
202 | 211 | ||
203 | list_for_each_safe(pos, temp, &sctp_local_addr_list) { | 212 | rcu_read_lock(); |
204 | addr = list_entry(pos, struct sctp_sockaddr_entry, list); | 213 | list_for_each_entry_rcu(addr, &sctp_local_addr_list, list) { |
214 | if (!addr->valid) | ||
215 | continue; | ||
205 | if (sctp_in_scope(&addr->a, scope)) { | 216 | if (sctp_in_scope(&addr->a, scope)) { |
206 | /* Now that the address is in scope, check to see if | 217 | /* Now that the address is in scope, check to see if |
207 | * the address type is really supported by the local | 218 | * the address type is really supported by the local |
@@ -221,6 +232,7 @@ int sctp_copy_local_addr_list(struct sctp_bind_addr *bp, sctp_scope_t scope, | |||
221 | } | 232 | } |
222 | 233 | ||
223 | end_copy: | 234 | end_copy: |
235 | rcu_read_unlock(); | ||
224 | return error; | 236 | return error; |
225 | } | 237 | } |
226 | 238 | ||
@@ -600,13 +612,18 @@ static void sctp_v4_seq_dump_addr(struct seq_file *seq, union sctp_addr *addr) | |||
600 | seq_printf(seq, "%d.%d.%d.%d ", NIPQUAD(addr->v4.sin_addr)); | 612 | seq_printf(seq, "%d.%d.%d.%d ", NIPQUAD(addr->v4.sin_addr)); |
601 | } | 613 | } |
602 | 614 | ||
603 | /* Event handler for inet address addition/deletion events. */ | 615 | /* Event handler for inet address addition/deletion events. |
616 | * The sctp_local_addr_list needs to be protocted by a spin lock since | ||
617 | * multiple notifiers (say IPv4 and IPv6) may be running at the same | ||
618 | * time and thus corrupt the list. | ||
619 | * The reader side is protected with RCU. | ||
620 | */ | ||
604 | static int sctp_inetaddr_event(struct notifier_block *this, unsigned long ev, | 621 | static int sctp_inetaddr_event(struct notifier_block *this, unsigned long ev, |
605 | void *ptr) | 622 | void *ptr) |
606 | { | 623 | { |
607 | struct in_ifaddr *ifa = (struct in_ifaddr *)ptr; | 624 | struct in_ifaddr *ifa = (struct in_ifaddr *)ptr; |
608 | struct sctp_sockaddr_entry *addr; | 625 | struct sctp_sockaddr_entry *addr = NULL; |
609 | struct list_head *pos, *temp; | 626 | struct sctp_sockaddr_entry *temp; |
610 | 627 | ||
611 | switch (ev) { | 628 | switch (ev) { |
612 | case NETDEV_UP: | 629 | case NETDEV_UP: |
@@ -615,19 +632,25 @@ static int sctp_inetaddr_event(struct notifier_block *this, unsigned long ev, | |||
615 | addr->a.v4.sin_family = AF_INET; | 632 | addr->a.v4.sin_family = AF_INET; |
616 | addr->a.v4.sin_port = 0; | 633 | addr->a.v4.sin_port = 0; |
617 | addr->a.v4.sin_addr.s_addr = ifa->ifa_local; | 634 | addr->a.v4.sin_addr.s_addr = ifa->ifa_local; |
618 | list_add_tail(&addr->list, &sctp_local_addr_list); | 635 | addr->valid = 1; |
636 | spin_lock_bh(&sctp_local_addr_lock); | ||
637 | list_add_tail_rcu(&addr->list, &sctp_local_addr_list); | ||
638 | spin_unlock_bh(&sctp_local_addr_lock); | ||
619 | } | 639 | } |
620 | break; | 640 | break; |
621 | case NETDEV_DOWN: | 641 | case NETDEV_DOWN: |
622 | list_for_each_safe(pos, temp, &sctp_local_addr_list) { | 642 | spin_lock_bh(&sctp_local_addr_lock); |
623 | addr = list_entry(pos, struct sctp_sockaddr_entry, list); | 643 | list_for_each_entry_safe(addr, temp, |
644 | &sctp_local_addr_list, list) { | ||
624 | if (addr->a.v4.sin_addr.s_addr == ifa->ifa_local) { | 645 | if (addr->a.v4.sin_addr.s_addr == ifa->ifa_local) { |
625 | list_del(pos); | 646 | addr->valid = 0; |
626 | kfree(addr); | 647 | list_del_rcu(&addr->list); |
627 | break; | 648 | break; |
628 | } | 649 | } |
629 | } | 650 | } |
630 | 651 | spin_unlock_bh(&sctp_local_addr_lock); | |
652 | if (addr && !addr->valid) | ||
653 | call_rcu(&addr->rcu, sctp_local_addr_free); | ||
631 | break; | 654 | break; |
632 | } | 655 | } |
633 | 656 | ||
@@ -1160,6 +1183,7 @@ SCTP_STATIC __init int sctp_init(void) | |||
1160 | 1183 | ||
1161 | /* Initialize the local address list. */ | 1184 | /* Initialize the local address list. */ |
1162 | INIT_LIST_HEAD(&sctp_local_addr_list); | 1185 | INIT_LIST_HEAD(&sctp_local_addr_list); |
1186 | spin_lock_init(&sctp_local_addr_lock); | ||
1163 | sctp_get_local_addr_list(); | 1187 | sctp_get_local_addr_list(); |
1164 | 1188 | ||
1165 | /* Register notifier for inet address additions/deletions. */ | 1189 | /* Register notifier for inet address additions/deletions. */ |
@@ -1227,6 +1251,9 @@ SCTP_STATIC __exit void sctp_exit(void) | |||
1227 | sctp_v6_del_protocol(); | 1251 | sctp_v6_del_protocol(); |
1228 | inet_del_protocol(&sctp_protocol, IPPROTO_SCTP); | 1252 | inet_del_protocol(&sctp_protocol, IPPROTO_SCTP); |
1229 | 1253 | ||
1254 | /* Unregister notifier for inet address additions/deletions. */ | ||
1255 | unregister_inetaddr_notifier(&sctp_inetaddr_notifier); | ||
1256 | |||
1230 | /* Free the local address list. */ | 1257 | /* Free the local address list. */ |
1231 | sctp_free_local_addr_list(); | 1258 | sctp_free_local_addr_list(); |
1232 | 1259 | ||
@@ -1240,9 +1267,6 @@ SCTP_STATIC __exit void sctp_exit(void) | |||
1240 | inet_unregister_protosw(&sctp_stream_protosw); | 1267 | inet_unregister_protosw(&sctp_stream_protosw); |
1241 | inet_unregister_protosw(&sctp_seqpacket_protosw); | 1268 | inet_unregister_protosw(&sctp_seqpacket_protosw); |
1242 | 1269 | ||
1243 | /* Unregister notifier for inet address additions/deletions. */ | ||
1244 | unregister_inetaddr_notifier(&sctp_inetaddr_notifier); | ||
1245 | |||
1246 | sctp_sysctl_unregister(); | 1270 | sctp_sysctl_unregister(); |
1247 | list_del(&sctp_ipv4_specific.list); | 1271 | list_del(&sctp_ipv4_specific.list); |
1248 | 1272 | ||
diff --git a/net/sctp/socket.c b/net/sctp/socket.c index 33354602ae86..a3acf78d06ba 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c | |||
@@ -4057,9 +4057,9 @@ static int sctp_getsockopt_local_addrs_num_old(struct sock *sk, int len, | |||
4057 | int __user *optlen) | 4057 | int __user *optlen) |
4058 | { | 4058 | { |
4059 | sctp_assoc_t id; | 4059 | sctp_assoc_t id; |
4060 | struct list_head *pos; | ||
4060 | struct sctp_bind_addr *bp; | 4061 | struct sctp_bind_addr *bp; |
4061 | struct sctp_association *asoc; | 4062 | struct sctp_association *asoc; |
4062 | struct list_head *pos, *temp; | ||
4063 | struct sctp_sockaddr_entry *addr; | 4063 | struct sctp_sockaddr_entry *addr; |
4064 | rwlock_t *addr_lock; | 4064 | rwlock_t *addr_lock; |
4065 | int cnt = 0; | 4065 | int cnt = 0; |
@@ -4096,15 +4096,19 @@ static int sctp_getsockopt_local_addrs_num_old(struct sock *sk, int len, | |||
4096 | addr = list_entry(bp->address_list.next, | 4096 | addr = list_entry(bp->address_list.next, |
4097 | struct sctp_sockaddr_entry, list); | 4097 | struct sctp_sockaddr_entry, list); |
4098 | if (sctp_is_any(&addr->a)) { | 4098 | if (sctp_is_any(&addr->a)) { |
4099 | list_for_each_safe(pos, temp, &sctp_local_addr_list) { | 4099 | rcu_read_lock(); |
4100 | addr = list_entry(pos, | 4100 | list_for_each_entry_rcu(addr, |
4101 | struct sctp_sockaddr_entry, | 4101 | &sctp_local_addr_list, list) { |
4102 | list); | 4102 | if (!addr->valid) |
4103 | continue; | ||
4104 | |||
4103 | if ((PF_INET == sk->sk_family) && | 4105 | if ((PF_INET == sk->sk_family) && |
4104 | (AF_INET6 == addr->a.sa.sa_family)) | 4106 | (AF_INET6 == addr->a.sa.sa_family)) |
4105 | continue; | 4107 | continue; |
4108 | |||
4106 | cnt++; | 4109 | cnt++; |
4107 | } | 4110 | } |
4111 | rcu_read_unlock(); | ||
4108 | } else { | 4112 | } else { |
4109 | cnt = 1; | 4113 | cnt = 1; |
4110 | } | 4114 | } |
@@ -4127,14 +4131,16 @@ static int sctp_copy_laddrs_old(struct sock *sk, __u16 port, | |||
4127 | int max_addrs, void *to, | 4131 | int max_addrs, void *to, |
4128 | int *bytes_copied) | 4132 | int *bytes_copied) |
4129 | { | 4133 | { |
4130 | struct list_head *pos, *next; | ||
4131 | struct sctp_sockaddr_entry *addr; | 4134 | struct sctp_sockaddr_entry *addr; |
4132 | union sctp_addr temp; | 4135 | union sctp_addr temp; |
4133 | int cnt = 0; | 4136 | int cnt = 0; |
4134 | int addrlen; | 4137 | int addrlen; |
4135 | 4138 | ||
4136 | list_for_each_safe(pos, next, &sctp_local_addr_list) { | 4139 | rcu_read_lock(); |
4137 | addr = list_entry(pos, struct sctp_sockaddr_entry, list); | 4140 | list_for_each_entry_rcu(addr, &sctp_local_addr_list, list) { |
4141 | if (!addr->valid) | ||
4142 | continue; | ||
4143 | |||
4138 | if ((PF_INET == sk->sk_family) && | 4144 | if ((PF_INET == sk->sk_family) && |
4139 | (AF_INET6 == addr->a.sa.sa_family)) | 4145 | (AF_INET6 == addr->a.sa.sa_family)) |
4140 | continue; | 4146 | continue; |
@@ -4149,6 +4155,7 @@ static int sctp_copy_laddrs_old(struct sock *sk, __u16 port, | |||
4149 | cnt ++; | 4155 | cnt ++; |
4150 | if (cnt >= max_addrs) break; | 4156 | if (cnt >= max_addrs) break; |
4151 | } | 4157 | } |
4158 | rcu_read_unlock(); | ||
4152 | 4159 | ||
4153 | return cnt; | 4160 | return cnt; |
4154 | } | 4161 | } |
@@ -4156,14 +4163,16 @@ static int sctp_copy_laddrs_old(struct sock *sk, __u16 port, | |||
4156 | static int sctp_copy_laddrs(struct sock *sk, __u16 port, void *to, | 4163 | static int sctp_copy_laddrs(struct sock *sk, __u16 port, void *to, |
4157 | size_t space_left, int *bytes_copied) | 4164 | size_t space_left, int *bytes_copied) |
4158 | { | 4165 | { |
4159 | struct list_head *pos, *next; | ||
4160 | struct sctp_sockaddr_entry *addr; | 4166 | struct sctp_sockaddr_entry *addr; |
4161 | union sctp_addr temp; | 4167 | union sctp_addr temp; |
4162 | int cnt = 0; | 4168 | int cnt = 0; |
4163 | int addrlen; | 4169 | int addrlen; |
4164 | 4170 | ||
4165 | list_for_each_safe(pos, next, &sctp_local_addr_list) { | 4171 | rcu_read_lock(); |
4166 | addr = list_entry(pos, struct sctp_sockaddr_entry, list); | 4172 | list_for_each_entry_rcu(addr, &sctp_local_addr_list, list) { |
4173 | if (!addr->valid) | ||
4174 | continue; | ||
4175 | |||
4167 | if ((PF_INET == sk->sk_family) && | 4176 | if ((PF_INET == sk->sk_family) && |
4168 | (AF_INET6 == addr->a.sa.sa_family)) | 4177 | (AF_INET6 == addr->a.sa.sa_family)) |
4169 | continue; | 4178 | continue; |
@@ -4171,8 +4180,10 @@ static int sctp_copy_laddrs(struct sock *sk, __u16 port, void *to, | |||
4171 | sctp_get_pf_specific(sk->sk_family)->addr_v4map(sctp_sk(sk), | 4180 | sctp_get_pf_specific(sk->sk_family)->addr_v4map(sctp_sk(sk), |
4172 | &temp); | 4181 | &temp); |
4173 | addrlen = sctp_get_af_specific(temp.sa.sa_family)->sockaddr_len; | 4182 | addrlen = sctp_get_af_specific(temp.sa.sa_family)->sockaddr_len; |
4174 | if (space_left < addrlen) | 4183 | if (space_left < addrlen) { |
4175 | return -ENOMEM; | 4184 | cnt = -ENOMEM; |
4185 | break; | ||
4186 | } | ||
4176 | memcpy(to, &temp, addrlen); | 4187 | memcpy(to, &temp, addrlen); |
4177 | 4188 | ||
4178 | to += addrlen; | 4189 | to += addrlen; |
@@ -4180,6 +4191,7 @@ static int sctp_copy_laddrs(struct sock *sk, __u16 port, void *to, | |||
4180 | space_left -= addrlen; | 4191 | space_left -= addrlen; |
4181 | *bytes_copied += addrlen; | 4192 | *bytes_copied += addrlen; |
4182 | } | 4193 | } |
4194 | rcu_read_unlock(); | ||
4183 | 4195 | ||
4184 | return cnt; | 4196 | return cnt; |
4185 | } | 4197 | } |