namespace related fix for v4.11-rc2
Linus,
Please pull the for-linus branch from the git tree:
git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace.git for-linus
HEAD: 040757f738e13caaa9c5078bca79aa97e11dde88 ucount: Remove the atomicity from ucount->count
This fixes a race between put_ucounts and get_ucounts that can cause a use after free. The fix works by simplifying the code and so there is not even a temptation to be clever and play spinlock vs atomic reference games.
Eric
From 040757f738e13caaa9c5078bca79aa97e11dde88 Mon Sep 17 00:00:00 2001
From: "Eric W. Biederman" <ebiederm-aS9lmoZGLiVWk0Htik3J/[email protected]>
Date: Sun, 5 Mar 2017 15:03:22 -0600
Subject: [PATCH] ucount: Remove the atomicity from ucount->count
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.
--- include/linux/user_namespace.h | 2 +- kernel/ucount.c | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-)
include/linux/user_namespace.h +1 -1
@@ -72,7 +72,7 @@ struct ucounts {struct hlist_node node;struct user_namespace *ns;kuid_t uid; -atomic_t count; +int count;atomic_t ucount[UCOUNT_COUNTS ]; };
kernel/ucount.c +11 -7
@@ -144,7 +144,7 @@ static struct ucounts *get_ucounts(struct user_namespace *ns, kuid_t uid) new->ns = ns; new->uid = uid; -atomic_set (&new->count,0 ); + new->count =0 ;spin_lock_irq (&ucounts_lock); ucounts =find_ucounts (ns, uid, hashent);
@@ -155,8 +155,10 @@ static struct ucounts *get_ucounts(struct user_namespace *ns, kuid_t uid) ucounts = new; } } -if (!atomic_add_unless (&ucounts->count,1 ,INT_MAX )) +if (ucounts->count ==INT_MAX ) ucounts =NULL ; +else + ucounts->count +=1 ;spin_unlock_irq (&ucounts_lock);return ucounts; }
@@ -165,13 +167,15 @@ static void put_ucounts(struct ucounts *ucounts) {unsigned long flags; -if (atomic_dec_and_test (&ucounts->count)) { -spin_lock_irqsave (&ucounts_lock, flags); +spin_lock_irqsave (&ucounts_lock, flags); + ucounts->count -=1 ; +if (!ucounts->count)hlist_del_init (&ucounts->node); -spin_unlock_irqrestore (&ucounts_lock, flags); +else + ucounts =NULL ; +spin_unlock_irqrestore (&ucounts_lock, flags); -kfree (ucounts); - } +kfree (ucounts); }static inline bool atomic_inc_below (atomic_t *v,int u)
Signature · 1 line
2.10.1