diff options
author | Pavel Emelyanov <xemul@openvz.org> | 2007-10-18 08:18:56 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2007-10-18 08:18:56 -0400 |
commit | 78c2e50253569e62caa4a61fc1cc5a0158edec43 (patch) | |
tree | 07567a10517b1cf1ce297c1ecf4ce7c617856331 /net/ipv6 | |
parent | bd0bf57700cb0eaa92f3d2ee040a69743cdd99d0 (diff) |
[IPV6]: Fix race in ipv6_flowlabel_opt() when inserting two labels
In the IPV6_FL_A_GET case the hash is checked for flowlabels
with the given label. If it is not found, the lock, protecting
the hash, is dropped to be re-get for writing. After this a
newly allocated entry is inserted, but no checks are performed
to catch a classical SMP race, when the conflicting label may
be inserted on another cpu.
Use the (currently unused) return value from fl_intern() to
return the conflicting entry (if found) and re-check, whether
we can reuse it (IPV6_FL_F_EXCL) or return -EEXISTS.
Also add the comment, about why not re-lookup the current
sock for conflicting flowlabel entry.
Signed-off-by: Pavel Emelyanov <xemul@openvz.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/ipv6')
-rw-r--r-- | net/ipv6/ip6_flowlabel.c | 34 |
1 files changed, 25 insertions, 9 deletions
diff --git a/net/ipv6/ip6_flowlabel.c b/net/ipv6/ip6_flowlabel.c index f40a08669db0..e55ae1a1f560 100644 --- a/net/ipv6/ip6_flowlabel.c +++ b/net/ipv6/ip6_flowlabel.c | |||
@@ -154,8 +154,10 @@ static void ip6_fl_gc(unsigned long dummy) | |||
154 | write_unlock(&ip6_fl_lock); | 154 | write_unlock(&ip6_fl_lock); |
155 | } | 155 | } |
156 | 156 | ||
157 | static int fl_intern(struct ip6_flowlabel *fl, __be32 label) | 157 | static struct ip6_flowlabel *fl_intern(struct ip6_flowlabel *fl, __be32 label) |
158 | { | 158 | { |
159 | struct ip6_flowlabel *lfl; | ||
160 | |||
159 | fl->label = label & IPV6_FLOWLABEL_MASK; | 161 | fl->label = label & IPV6_FLOWLABEL_MASK; |
160 | 162 | ||
161 | write_lock_bh(&ip6_fl_lock); | 163 | write_lock_bh(&ip6_fl_lock); |
@@ -163,12 +165,26 @@ static int fl_intern(struct ip6_flowlabel *fl, __be32 label) | |||
163 | for (;;) { | 165 | for (;;) { |
164 | fl->label = htonl(net_random())&IPV6_FLOWLABEL_MASK; | 166 | fl->label = htonl(net_random())&IPV6_FLOWLABEL_MASK; |
165 | if (fl->label) { | 167 | if (fl->label) { |
166 | struct ip6_flowlabel *lfl; | ||
167 | lfl = __fl_lookup(fl->label); | 168 | lfl = __fl_lookup(fl->label); |
168 | if (lfl == NULL) | 169 | if (lfl == NULL) |
169 | break; | 170 | break; |
170 | } | 171 | } |
171 | } | 172 | } |
173 | } else { | ||
174 | /* | ||
175 | * we dropper the ip6_fl_lock, so this entry could reappear | ||
176 | * and we need to recheck with it. | ||
177 | * | ||
178 | * OTOH no need to search the active socket first, like it is | ||
179 | * done in ipv6_flowlabel_opt - sock is locked, so new entry | ||
180 | * with the same label can only appear on another sock | ||
181 | */ | ||
182 | lfl = __fl_lookup(fl->label); | ||
183 | if (lfl != NULL) { | ||
184 | atomic_inc(&lfl->users); | ||
185 | write_unlock_bh(&ip6_fl_lock); | ||
186 | return lfl; | ||
187 | } | ||
172 | } | 188 | } |
173 | 189 | ||
174 | fl->lastuse = jiffies; | 190 | fl->lastuse = jiffies; |
@@ -176,7 +192,7 @@ static int fl_intern(struct ip6_flowlabel *fl, __be32 label) | |||
176 | fl_ht[FL_HASH(fl->label)] = fl; | 192 | fl_ht[FL_HASH(fl->label)] = fl; |
177 | atomic_inc(&fl_size); | 193 | atomic_inc(&fl_size); |
178 | write_unlock_bh(&ip6_fl_lock); | 194 | write_unlock_bh(&ip6_fl_lock); |
179 | return 0; | 195 | return NULL; |
180 | } | 196 | } |
181 | 197 | ||
182 | 198 | ||
@@ -429,7 +445,8 @@ int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen) | |||
429 | struct in6_flowlabel_req freq; | 445 | struct in6_flowlabel_req freq; |
430 | struct ipv6_fl_socklist *sfl1=NULL; | 446 | struct ipv6_fl_socklist *sfl1=NULL; |
431 | struct ipv6_fl_socklist *sfl, **sflp; | 447 | struct ipv6_fl_socklist *sfl, **sflp; |
432 | struct ip6_flowlabel *fl; | 448 | struct ip6_flowlabel *fl, *fl1 = NULL; |
449 | |||
433 | 450 | ||
434 | if (optlen < sizeof(freq)) | 451 | if (optlen < sizeof(freq)) |
435 | return -EINVAL; | 452 | return -EINVAL; |
@@ -485,8 +502,6 @@ int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen) | |||
485 | sfl1 = kmalloc(sizeof(*sfl1), GFP_KERNEL); | 502 | sfl1 = kmalloc(sizeof(*sfl1), GFP_KERNEL); |
486 | 503 | ||
487 | if (freq.flr_label) { | 504 | if (freq.flr_label) { |
488 | struct ip6_flowlabel *fl1 = NULL; | ||
489 | |||
490 | err = -EEXIST; | 505 | err = -EEXIST; |
491 | read_lock_bh(&ip6_sk_fl_lock); | 506 | read_lock_bh(&ip6_sk_fl_lock); |
492 | for (sfl = np->ipv6_fl_list; sfl; sfl = sfl->next) { | 507 | for (sfl = np->ipv6_fl_list; sfl; sfl = sfl->next) { |
@@ -505,6 +520,7 @@ int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen) | |||
505 | if (fl1 == NULL) | 520 | if (fl1 == NULL) |
506 | fl1 = fl_lookup(freq.flr_label); | 521 | fl1 = fl_lookup(freq.flr_label); |
507 | if (fl1) { | 522 | if (fl1) { |
523 | recheck: | ||
508 | err = -EEXIST; | 524 | err = -EEXIST; |
509 | if (freq.flr_flags&IPV6_FL_F_EXCL) | 525 | if (freq.flr_flags&IPV6_FL_F_EXCL) |
510 | goto release; | 526 | goto release; |
@@ -543,9 +559,9 @@ release: | |||
543 | if (sfl1 == NULL || (err = mem_check(sk)) != 0) | 559 | if (sfl1 == NULL || (err = mem_check(sk)) != 0) |
544 | goto done; | 560 | goto done; |
545 | 561 | ||
546 | err = fl_intern(fl, freq.flr_label); | 562 | fl1 = fl_intern(fl, freq.flr_label); |
547 | if (err) | 563 | if (fl1 != NULL) |
548 | goto done; | 564 | goto recheck; |
549 | 565 | ||
550 | if (!freq.flr_label) { | 566 | if (!freq.flr_label) { |
551 | if (copy_to_user(&((struct in6_flowlabel_req __user *) optval)->flr_label, | 567 | if (copy_to_user(&((struct in6_flowlabel_req __user *) optval)->flr_label, |