[PATCH 4.14 56/63] fs/writeback.c: use rcu_barrier() to wait for inflight wb switches going into workqueue when umount

From: Greg Kroah-Hartman
Date: Mon May 20 2019 - 08:23:30 EST

From: Jiufei Xue <jiufei.xue@xxxxxxxxxxxxxxxxx>

commit ec084de929e419e51bcdafaafe567d9e7d0273b7 upstream.

synchronize_rcu() didn't wait for call_rcu() callbacks, so inode wb
switch may not go to the workqueue after synchronize_rcu(). Thus
previous scheduled switches was not finished even flushing the
workqueue, which will cause a NULL pointer dereferenced followed below.

VFS: Busy inodes after unmount of vdd. Self-destruct in 5 seconds. Have a nice day...
BUG: unable to handle kernel NULL pointer dereference at 0000000000000278
? process_one_work+0x410/0x410

Replace the synchronize_rcu() call with a rcu_barrier() to wait for all
pending callbacks to finish. And inc isw_nr_in_flight after call_rcu()
in inode_switch_wbs() to make more sense.

Link: http://lkml.kernel.org/r/20190429024108.54150-1-jiufei.xue@xxxxxxxxxxxxxxxxx
Signed-off-by: Jiufei Xue <jiufei.xue@xxxxxxxxxxxxxxxxx>
Acked-by: Tejun Heo <tj@xxxxxxxxxx>
Suggested-by: Tejun Heo <tj@xxxxxxxxxx>
Cc: <stable@xxxxxxxxxxxxxxx>
Signed-off-by: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>
Signed-off-by: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx>
Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>

fs/fs-writeback.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)

--- a/fs/fs-writeback.c
+++ b/fs/fs-writeback.c
@@ -530,8 +530,6 @@ static void inode_switch_wbs(struct inod

isw->inode = inode;

- atomic_inc(&isw_nr_in_flight);
* In addition to synchronizing among switchers, I_WB_SWITCH tells
* the RCU protected stat update paths to grab the mapping's
@@ -539,6 +537,9 @@ static void inode_switch_wbs(struct inod
* Let's continue after I_WB_SWITCH is guaranteed to be visible.
call_rcu(&isw->rcu_head, inode_switch_wbs_rcu_fn);
+ atomic_inc(&isw_nr_in_flight);
goto out_unlock;

@@ -908,7 +909,11 @@ restart:
void cgroup_writeback_umount(void)
if (atomic_read(&isw_nr_in_flight)) {
- synchronize_rcu();
+ /*
+ * Use rcu_barrier() to wait for all pending callbacks to
+ * ensure that all in-flight wb switches are in the workqueue.
+ */
+ rcu_barrier();