Skip to content

Commit

Permalink
[IPV6]: Fix race in ipv6_flowlabel_opt() when inserting two labels
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Pavel Emelyanov authored and David S. Miller committed Oct 18, 2007
1 parent bd0bf57 commit 78c2e50
Showing 1 changed file with 25 additions and 9 deletions.
34 changes: 25 additions & 9 deletions net/ipv6/ip6_flowlabel.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,29 +154,45 @@ static void ip6_fl_gc(unsigned long dummy)
write_unlock(&ip6_fl_lock);
}

static int fl_intern(struct ip6_flowlabel *fl, __be32 label)
static struct ip6_flowlabel *fl_intern(struct ip6_flowlabel *fl, __be32 label)
{
struct ip6_flowlabel *lfl;

fl->label = label & IPV6_FLOWLABEL_MASK;

write_lock_bh(&ip6_fl_lock);
if (label == 0) {
for (;;) {
fl->label = htonl(net_random())&IPV6_FLOWLABEL_MASK;
if (fl->label) {
struct ip6_flowlabel *lfl;
lfl = __fl_lookup(fl->label);
if (lfl == NULL)
break;
}
}
} else {
/*
* we dropper the ip6_fl_lock, so this entry could reappear
* and we need to recheck with it.
*
* OTOH no need to search the active socket first, like it is
* done in ipv6_flowlabel_opt - sock is locked, so new entry
* with the same label can only appear on another sock
*/
lfl = __fl_lookup(fl->label);
if (lfl != NULL) {
atomic_inc(&lfl->users);
write_unlock_bh(&ip6_fl_lock);
return lfl;
}
}

fl->lastuse = jiffies;
fl->next = fl_ht[FL_HASH(fl->label)];
fl_ht[FL_HASH(fl->label)] = fl;
atomic_inc(&fl_size);
write_unlock_bh(&ip6_fl_lock);
return 0;
return NULL;
}


Expand Down Expand Up @@ -429,7 +445,8 @@ int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen)
struct in6_flowlabel_req freq;
struct ipv6_fl_socklist *sfl1=NULL;
struct ipv6_fl_socklist *sfl, **sflp;
struct ip6_flowlabel *fl;
struct ip6_flowlabel *fl, *fl1 = NULL;


if (optlen < sizeof(freq))
return -EINVAL;
Expand Down Expand Up @@ -485,8 +502,6 @@ int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen)
sfl1 = kmalloc(sizeof(*sfl1), GFP_KERNEL);

if (freq.flr_label) {
struct ip6_flowlabel *fl1 = NULL;

err = -EEXIST;
read_lock_bh(&ip6_sk_fl_lock);
for (sfl = np->ipv6_fl_list; sfl; sfl = sfl->next) {
Expand All @@ -505,6 +520,7 @@ int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen)
if (fl1 == NULL)
fl1 = fl_lookup(freq.flr_label);
if (fl1) {
recheck:
err = -EEXIST;
if (freq.flr_flags&IPV6_FL_F_EXCL)
goto release;
Expand Down Expand Up @@ -543,9 +559,9 @@ int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen)
if (sfl1 == NULL || (err = mem_check(sk)) != 0)
goto done;

err = fl_intern(fl, freq.flr_label);
if (err)
goto done;
fl1 = fl_intern(fl, freq.flr_label);
if (fl1 != NULL)
goto recheck;

if (!freq.flr_label) {
if (copy_to_user(&((struct in6_flowlabel_req __user *) optval)->flr_label,
Expand Down

0 comments on commit 78c2e50

Please sign in to comment.