diff options
author | Joern Engel <joern@logfs.org> | 2013-05-13 16:30:06 -0400 |
---|---|---|
committer | Nicholas Bellinger <nab@linux-iscsi.org> | 2013-05-15 01:47:35 -0700 |
commit | ccf5ae83a6cf3d9cfe9a7038bfe7cd38ab03d5e1 (patch) | |
tree | cb3966328bce7584d4c24434490dc21a67ecb48b /include | |
parent | a1321ddd27e65c6ada5b9a12cae4ee2612d76893 (diff) |
target: close target_put_sess_cmd() vs. core_tmr_abort_task() race
It is possible for one thread to to take se_sess->sess_cmd_lock in
core_tmr_abort_task() before taking a reference count on
se_cmd->cmd_kref, while another thread in target_put_sess_cmd() drops
se_cmd->cmd_kref before taking se_sess->sess_cmd_lock.
This introduces kref_put_spinlock_irqsave() and uses it in
target_put_sess_cmd() to close the race window.
Signed-off-by: Joern Engel <joern@logfs.org>
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: <stable@vger.kernel.org>
Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
Diffstat (limited to 'include')
-rw-r--r-- | include/linux/kref.h | 33 |
1 files changed, 33 insertions, 0 deletions
diff --git a/include/linux/kref.h b/include/linux/kref.h index 4972e6e9ca93..7419c02085d7 100644 --- a/include/linux/kref.h +++ b/include/linux/kref.h @@ -19,6 +19,7 @@ #include <linux/atomic.h> #include <linux/kernel.h> #include <linux/mutex.h> +#include <linux/spinlock.h> struct kref { atomic_t refcount; @@ -95,6 +96,38 @@ static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref) return kref_sub(kref, 1, release); } +/** + * kref_put_spinlock_irqsave - decrement refcount for object. + * @kref: object. + * @release: pointer to the function that will clean up the object when the + * last reference to the object is released. + * This pointer is required, and it is not acceptable to pass kfree + * in as this function. + * @lock: lock to take in release case + * + * Behaves identical to kref_put with one exception. If the reference count + * drops to zero, the lock will be taken atomically wrt dropping the reference + * count. The release function has to call spin_unlock() without _irqrestore. + */ +static inline int kref_put_spinlock_irqsave(struct kref *kref, + void (*release)(struct kref *kref), + spinlock_t *lock) +{ + unsigned long flags; + + WARN_ON(release == NULL); + if (atomic_add_unless(&kref->refcount, -1, 1)) + return 0; + spin_lock_irqsave(lock, flags); + if (atomic_dec_and_test(&kref->refcount)) { + release(kref); + local_irq_restore(flags); + return 1; + } + spin_unlock_irqrestore(lock, flags); + return 0; +} + static inline int kref_put_mutex(struct kref *kref, void (*release)(struct kref *kref), struct mutex *lock) |