aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2016-03-18 13:50:03 -0400
committerAlex Shi <alex.shi@linaro.org>2016-06-24 17:27:38 +0800
commit986acce6d8d403cdb5094c21e05cf181fc9c44bb (patch)
treead47cddac01e5819e99c1de65e6ee08b99fa1160
parenta5d1b39649c318bc4ba9f909bc22b44eb5381938 (diff)
writeback, cgroup: fix premature wb_put() in locked_inode_to_wb_and_lock_list()
locked_inode_to_wb_and_lock_list() wb_get()'s the wb associated with the target inode, unlocks inode, locks the wb's list_lock and verifies that the inode is still associated with the wb. To prevent the wb going away between dropping inode lock and acquiring list_lock, the wb is pinned while inode lock is held. The wb reference is put right after acquiring list_lock citing that the wb won't be dereferenced anymore. This isn't true. If the inode is still associated with the wb, the inode has reference and it's safe to return the wb; however, if inode has been switched, the wb still needs to be unlocked which is a dereference and can lead to use-after-free if it it races with wb destruction. Fix it by putting the reference after releasing list_lock. Signed-off-by: Tejun Heo <tj@kernel.org> Fixes: 87e1d789bf55 ("writeback: implement [locked_]inode_to_wb_and_lock_list()") Cc: stable@vger.kernel.org # v4.2+ Tested-by: Tahsin Erdogan <tahsin@google.com> Signed-off-by: Jens Axboe <axboe@fb.com> (cherry picked from commit 614a4e3773148a31f58dc174bbf578ceb63510c2) Signed-off-by: Alex Shi <alex.shi@linaro.org>
-rw-r--r--fs/fs-writeback.c8
1 files changed, 5 insertions, 3 deletions
diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c
index e25fc7e52be8..2816dfe5c6f7 100644
--- a/fs/fs-writeback.c
+++ b/fs/fs-writeback.c
@@ -278,13 +278,15 @@ locked_inode_to_wb_and_lock_list(struct inode *inode)
wb_get(wb);
spin_unlock(&inode->i_lock);
spin_lock(&wb->list_lock);
- wb_put(wb); /* not gonna deref it anymore */
/* i_wb may have changed inbetween, can't use inode_to_wb() */
- if (likely(wb == inode->i_wb))
- return wb; /* @inode already has ref */
+ if (likely(wb == inode->i_wb)) {
+ wb_put(wb); /* @inode already has ref */
+ return wb;
+ }
spin_unlock(&wb->list_lock);
+ wb_put(wb);
cpu_relax();
spin_lock(&inode->i_lock);
}