Gitweb:     
http://git.kernel.org/git/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=78c2e50253569e62caa4a61fc1cc5a0158edec43
Commit:     78c2e50253569e62caa4a61fc1cc5a0158edec43
Parent:     bd0bf57700cb0eaa92f3d2ee040a69743cdd99d0
Author:     Pavel Emelyanov <[EMAIL PROTECTED]>
AuthorDate: Thu Oct 18 05:18:56 2007 -0700
Committer:  David S. Miller <[EMAIL PROTECTED]>
CommitDate: Thu Oct 18 05:18:56 2007 -0700

    [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 <[EMAIL PROTECTED]>
    Signed-off-by: David S. Miller <[EMAIL PROTECTED]>
---
 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 f40a086..e55ae1a 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)
        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);
@@ -163,12 +165,26 @@ static int fl_intern(struct ip6_flowlabel *fl, __be32 
label)
                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;
@@ -176,7 +192,7 @@ static int fl_intern(struct ip6_flowlabel *fl, __be32 label)
        fl_ht[FL_HASH(fl->label)] = fl;
        atomic_inc(&fl_size);
        write_unlock_bh(&ip6_fl_lock);
-       return 0;
+       return NULL;
 }
 
 
@@ -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;
@@ -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) {
@@ -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;
@@ -543,9 +559,9 @@ release:
                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,
-
To unsubscribe from this list: send the line "unsubscribe git-commits-head" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to