Messages in this thread | | | Subject | Re: [PATCH] f2fs: fix potential .flags overflow on 32bit architecture | From | Chao Yu <> | Date | Mon, 23 Mar 2020 09:15:39 +0800 |
| |
On 2020/3/22 23:30, Ondřej Jirman wrote: > On Sun, Mar 22, 2020 at 09:18:56PM +0800, Chao Yu wrote: >> Hi, >> >> On 2020-3-22 20:14, Ondřej Jirman wrote: >>> Hello, >>> >>> On Sun, Mar 22, 2020 at 06:13:27PM +0800, Chao Yu wrote: >>>> From: Chao Yu <yuchao0@huawei.com> >>>> >>>> f2fs_inode_info.flags is unsigned long variable, it has 32 bits >>>> in 32bit architecture, since we introduced FI_MMAP_FILE flag >>>> when we support data compression, we may access memory cross >>>> the border of .flags field, corrupting .i_sem field, result in >>>> below deadlock. >>>> >>>> To fix this issue, let's introduce .extra_flags to grab extra >>>> space to store those new flags. >>>> >>>> Call Trace: >>>> __schedule+0x8d0/0x13fc >>>> ? mark_held_locks+0xac/0x100 >>>> schedule+0xcc/0x260 >>>> rwsem_down_write_slowpath+0x3ab/0x65d >>>> down_write+0xc7/0xe0 >>>> f2fs_drop_nlink+0x3d/0x600 [f2fs] >>>> f2fs_delete_inline_entry+0x300/0x440 [f2fs] >>>> f2fs_delete_entry+0x3a1/0x7f0 [f2fs] >>>> f2fs_unlink+0x500/0x790 [f2fs] >>>> vfs_unlink+0x211/0x490 >>>> do_unlinkat+0x483/0x520 >>>> sys_unlink+0x4a/0x70 >>>> do_fast_syscall_32+0x12b/0x683 >>>> entry_SYSENTER_32+0xaa/0x102 >>>> >>>> Fixes: 4c8ff7095bef ("f2fs: support data compression") >>>> Signed-off-by: Chao Yu <yuchao0@huawei.com> >>>> --- >>>> fs/f2fs/f2fs.h | 26 ++++++++++++++++++++------ >>>> fs/f2fs/inode.c | 1 + >>>> 2 files changed, 21 insertions(+), 6 deletions(-) >>>> >>>> diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h >>>> index fcafa68212eb..fcd22df2e9ca 100644 >>>> --- a/fs/f2fs/f2fs.h >>>> +++ b/fs/f2fs/f2fs.h >>>> @@ -695,6 +695,7 @@ struct f2fs_inode_info { >>>> >>>> /* Use below internally in f2fs*/ >>>> unsigned long flags; /* use to pass per-file flags */ >>>> + unsigned long extra_flags; /* extra flags */ >>>> struct rw_semaphore i_sem; /* protect fi info */ >>>> atomic_t dirty_pages; /* # of dirty pages */ >>>> f2fs_hash_t chash; /* hash value of given file name */ >>>> @@ -2569,7 +2570,7 @@ enum { >>>> }; >>>> >>>> static inline void __mark_inode_dirty_flag(struct inode *inode, >>>> - int flag, bool set) >>>> + unsigned long long flag, bool set) >>>> { >>>> switch (flag) { >>>> case FI_INLINE_XATTR: >>>> @@ -2588,20 +2589,33 @@ static inline void __mark_inode_dirty_flag(struct inode *inode, >>>> >>>> static inline void set_inode_flag(struct inode *inode, int flag) >>>> { >>>> - if (!test_bit(flag, &F2FS_I(inode)->flags)) >>>> - set_bit(flag, &F2FS_I(inode)->flags); >>>> + if ((1 << flag) <= sizeof(unsigned long)) { >>> >>> ^ this is wrong. Maybe you meant flag <= BITS_PER_LONG >> >> Oh, my bad, I meant that, thanks for pointing out this. :) >> >>> >>> And ditto for the same checks below. Maybe you can make flags an array of >>> BIT_WORD(max_flag_value) + 1 and skip the branches altogether? > > I've found maybe a clearer macro for this: > > https://elixir.bootlin.com/linux/latest/source/include/linux/bitops.h#L15 > > BITS_TO_LONGS(nr)
More clean, thanks, will send v3 soon.
Thanks,
> > But it takes the number of bits in the bitmap, which would be > "max_flag_value + 1". > > regards, > o. > >> That will be better, let me revise this patch. >> >> Thanks, >> >>> >>> thank you and regards, >>> o. >>> >>>> + if (!test_bit(flag, &F2FS_I(inode)->flags)) >>>> + set_bit(flag, &F2FS_I(inode)->flags); >>>> + } else { >>>> + if (!test_bit(flag - 32, &F2FS_I(inode)->extra_flags)) >>>> + set_bit(flag - 32, &F2FS_I(inode)->extra_flags); >>>> + } >>>> __mark_inode_dirty_flag(inode, flag, true); >>>> } >>>> >>>> static inline int is_inode_flag_set(struct inode *inode, int flag) >>>> { >>>> - return test_bit(flag, &F2FS_I(inode)->flags); >>>> + if ((1 << flag) <= sizeof(unsigned long)) >>>> + return test_bit(flag, &F2FS_I(inode)->flags); >>>> + else >>>> + return test_bit(flag - 32, &F2FS_I(inode)->extra_flags); >>>> } >>>> >>>> static inline void clear_inode_flag(struct inode *inode, int flag) >>>> { >>>> - if (test_bit(flag, &F2FS_I(inode)->flags)) >>>> - clear_bit(flag, &F2FS_I(inode)->flags); >>>> + if ((1 << flag) <= sizeof(unsigned long)) { >>>> + if (test_bit(flag, &F2FS_I(inode)->flags)) >>>> + clear_bit(flag, &F2FS_I(inode)->flags); >>>> + } else { >>>> + if (test_bit(flag - 32, &F2FS_I(inode)->extra_flags)) >>>> + clear_bit(flag - 32, &F2FS_I(inode)->extra_flags); >>>> + } >>>> __mark_inode_dirty_flag(inode, flag, false); >>>> } >>>> >>>> diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c >>>> index 44e08bf2e2b4..ca924d7e0e30 100644 >>>> --- a/fs/f2fs/inode.c >>>> +++ b/fs/f2fs/inode.c >>>> @@ -363,6 +363,7 @@ static int do_read_inode(struct inode *inode) >>>> if (S_ISREG(inode->i_mode)) >>>> fi->i_flags &= ~F2FS_PROJINHERIT_FL; >>>> fi->flags = 0; >>>> + fi->extra_flags = 0; >>>> fi->i_advise = ri->i_advise; >>>> fi->i_pino = le32_to_cpu(ri->i_pino); >>>> fi->i_dir_level = ri->i_dir_level; >>>> -- >>>> 2.22.0 >>>> > . >
| |