| From c0bc4f696d60188eea4d16368a068d7156e82880 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Fri, 18 Jun 2021 16:11:03 +0800 |
| Subject: ubifs: Set/Clear I_LINKABLE under i_lock for whiteout inode |
| |
| From: Zhihao Cheng <chengzhihao1@huawei.com> |
| |
| [ Upstream commit a801fcfeef96702fa3f9b22ad56c5eb1989d9221 ] |
| |
| xfstests-generic/476 reports a warning message as below: |
| |
| WARNING: CPU: 2 PID: 30347 at fs/inode.c:361 inc_nlink+0x52/0x70 |
| Call Trace: |
| do_rename+0x502/0xd40 [ubifs] |
| ubifs_rename+0x8b/0x180 [ubifs] |
| vfs_rename+0x476/0x1080 |
| do_renameat2+0x67c/0x7b0 |
| __x64_sys_renameat2+0x6e/0x90 |
| do_syscall_64+0x66/0xe0 |
| entry_SYSCALL_64_after_hwframe+0x44/0xae |
| |
| Following race case can cause this: |
| rename_whiteout(Thread 1) wb_workfn(Thread 2) |
| ubifs_rename |
| do_rename |
| __writeback_single_inode |
| spin_lock(&inode->i_lock) |
| whiteout->i_state |= I_LINKABLE |
| inode->i_state &= ~dirty; |
| ---- How race happens on i_state: |
| (tmp = whiteout->i_state | I_LINKABLE) |
| (tmp = inode->i_state & ~dirty) |
| (whiteout->i_state = tmp) |
| (inode->i_state = tmp) |
| ---- |
| spin_unlock(&inode->i_lock) |
| inc_nlink(whiteout) |
| WARN_ON(!(inode->i_state & I_LINKABLE)) !!! |
| |
| Fix to add i_lock to avoid i_state update race condition. |
| |
| Fixes: 9e0a1fff8db56ea ("ubifs: Implement RENAME_WHITEOUT") |
| Signed-off-by: Zhihao Cheng <chengzhihao1@huawei.com> |
| Signed-off-by: Richard Weinberger <richard@nod.at> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| fs/ubifs/dir.c | 7 +++++++ |
| 1 file changed, 7 insertions(+) |
| |
| diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c |
| index d9d8d7794eff..af1ea5fcd38d 100644 |
| --- a/fs/ubifs/dir.c |
| +++ b/fs/ubifs/dir.c |
| @@ -1337,7 +1337,10 @@ static int do_rename(struct inode *old_dir, struct dentry *old_dentry, |
| goto out_release; |
| } |
| |
| + spin_lock(&whiteout->i_lock); |
| whiteout->i_state |= I_LINKABLE; |
| + spin_unlock(&whiteout->i_lock); |
| + |
| whiteout_ui = ubifs_inode(whiteout); |
| whiteout_ui->data = dev; |
| whiteout_ui->data_len = ubifs_encode_dev(dev, MKDEV(0, 0)); |
| @@ -1430,7 +1433,11 @@ static int do_rename(struct inode *old_dir, struct dentry *old_dentry, |
| |
| inc_nlink(whiteout); |
| mark_inode_dirty(whiteout); |
| + |
| + spin_lock(&whiteout->i_lock); |
| whiteout->i_state &= ~I_LINKABLE; |
| + spin_unlock(&whiteout->i_lock); |
| + |
| iput(whiteout); |
| } |
| |
| -- |
| 2.30.2 |
| |