aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric W. Biederman <ebiederm@xmission.com>2017-03-05 16:03:22 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-03-18 07:14:29 -0400
commitee6f7ee1e4cdb0098fee4593ddf11ca6028abef2 (patch)
tree3c469849bf880367cc69d071cbd90160e7909285
parent8bb208d02accfe50cee2a9b5db3ea9da49a208b3 (diff)
ucount: Remove the atomicity from ucount->count
commit 040757f738e13caaa9c5078bca79aa97e11dde88 upstream. Always increment/decrement ucount->count under the ucounts_lock. The increments are there already and moving the decrements there means the locking logic of the code is simpler. This simplification in the locking logic fixes a race between put_ucounts and get_ucounts that could result in a use-after-free because the count could go zero then be found by get_ucounts and then be freed by put_ucounts. A bug presumably this one was found by a combination of syzkaller and KASAN. JongWhan Kim reported the syzkaller failure and Dmitry Vyukov spotted the race in the code. Fixes: f6b2db1a3e8d ("userns: Make the count of user namespaces per user") Reported-by: JongHwan Kim <zzoru007@gmail.com> Reported-by: Dmitry Vyukov <dvyukov@google.com> Reviewed-by: Andrei Vagin <avagin@gmail.com> Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--include/linux/user_namespace.h2
-rw-r--r--kernel/ucount.c18
2 files changed, 12 insertions, 8 deletions
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index eb209d4523f5..dc797739f164 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -65,7 +65,7 @@ struct ucounts {
65 struct hlist_node node; 65 struct hlist_node node;
66 struct user_namespace *ns; 66 struct user_namespace *ns;
67 kuid_t uid; 67 kuid_t uid;
68 atomic_t count; 68 int count;
69 atomic_t ucount[UCOUNT_COUNTS]; 69 atomic_t ucount[UCOUNT_COUNTS];
70}; 70};
71 71
diff --git a/kernel/ucount.c b/kernel/ucount.c
index 4bbd38ec3788..f4ac18509ecf 100644
--- a/kernel/ucount.c
+++ b/kernel/ucount.c
@@ -139,7 +139,7 @@ static struct ucounts *get_ucounts(struct user_namespace *ns, kuid_t uid)
139 139
140 new->ns = ns; 140 new->ns = ns;
141 new->uid = uid; 141 new->uid = uid;
142 atomic_set(&new->count, 0); 142 new->count = 0;
143 143
144 spin_lock_irq(&ucounts_lock); 144 spin_lock_irq(&ucounts_lock);
145 ucounts = find_ucounts(ns, uid, hashent); 145 ucounts = find_ucounts(ns, uid, hashent);
@@ -150,8 +150,10 @@ static struct ucounts *get_ucounts(struct user_namespace *ns, kuid_t uid)
150 ucounts = new; 150 ucounts = new;
151 } 151 }
152 } 152 }
153 if (!atomic_add_unless(&ucounts->count, 1, INT_MAX)) 153 if (ucounts->count == INT_MAX)
154 ucounts = NULL; 154 ucounts = NULL;
155 else
156 ucounts->count += 1;
155 spin_unlock_irq(&ucounts_lock); 157 spin_unlock_irq(&ucounts_lock);
156 return ucounts; 158 return ucounts;
157} 159}
@@ -160,13 +162,15 @@ static void put_ucounts(struct ucounts *ucounts)
160{ 162{
161 unsigned long flags; 163 unsigned long flags;
162 164
163 if (atomic_dec_and_test(&ucounts->count)) { 165 spin_lock_irqsave(&ucounts_lock, flags);
164 spin_lock_irqsave(&ucounts_lock, flags); 166 ucounts->count -= 1;
167 if (!ucounts->count)
165 hlist_del_init(&ucounts->node); 168 hlist_del_init(&ucounts->node);
166 spin_unlock_irqrestore(&ucounts_lock, flags); 169 else
170 ucounts = NULL;
171 spin_unlock_irqrestore(&ucounts_lock, flags);
167 172
168 kfree(ucounts); 173 kfree(ucounts);
169 }
170} 174}
171 175
172static inline bool atomic_inc_below(atomic_t *v, int u) 176static inline bool atomic_inc_below(atomic_t *v, int u)