From: Daeho Jeong get_available_block_count() subtracts current_reserved_blocks, alias_reserved_blocks and root_reserved_blocks from user_block_count without checking the result. Only unusable_block_count is handled so that the result does not go below 0. f2fs_ioc_reserve_dev_alias() checks the free space with the root reserved blocks allowed when root calls it, so alias_reserved_blocks can overlap with the root reserved blocks until the reservation is turned into valid blocks. For example, with user_block_count = 1000, root_reserved_blocks = 100, valid blocks = 30 reserving an alias device of 950 blocks passes the check, since 30 + 950 <= 1000, and sets alias_reserved_blocks to 950. Then, for a user who cannot use the root reserved blocks, the available block count is 1000 - 950 - 100, which wraps around to a huge value, so the check in inc_valid_block_count() does not stop such a user until the reservation is done. current_reserved_blocks can make it wrap in the same way, for root as well, when reserved_blocks is written through sysfs in the meantime, since it is set without considering alias_reserved_blocks. Sum up all the reservations and return 0 if they do not fit in user_block_count, as was done for unusable_block_count only. Fixes: eae3faf210bd ("f2fs: support dynamic reserve/release for device aliasing") Cc: stable@vger.kernel.org Signed-off-by: Daeho Jeong --- This was discussed in the review of "[PATCH v2] f2fs: reserve space to be shrunk during resize_fs" [1]. v3 of that patch is sent separately on top of this one. [1] https://lore.kernel.org/linux-f2fs-devel/20261022165526.1539629-1-daeho43@gmail.com/ fs/f2fs/f2fs.h | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 50a5f845176d..59bc3c2fb70a 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -2710,23 +2710,20 @@ static inline bool __allow_reserved_root(struct f2fs_sb_info *sbi, static inline unsigned int get_available_block_count(struct f2fs_sb_info *sbi, struct inode *inode, bool cap) { - block_t avail_user_block_count; - - avail_user_block_count = sbi->user_block_count - - sbi->current_reserved_blocks - + u64 reserved = (u64)sbi->current_reserved_blocks + sbi->alias_reserved_blocks; if (test_opt(sbi, RESERVE_ROOT) && !__allow_reserved_root(sbi, inode, cap)) - avail_user_block_count -= F2FS_OPTION(sbi).root_reserved_blocks; + reserved += F2FS_OPTION(sbi).root_reserved_blocks; - if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) { - if (avail_user_block_count > sbi->unusable_block_count) - avail_user_block_count -= sbi->unusable_block_count; - else - avail_user_block_count = 0; - } + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) + reserved += sbi->unusable_block_count; + + /* the reservations can overlap each other, so do not wrap around */ + if (unlikely(reserved >= sbi->user_block_count)) + return 0; - return avail_user_block_count; + return sbi->user_block_count - reserved; } static inline void f2fs_i_blocks_write(struct inode *, block_t, bool, bool); base-commit: b9d7379d10d43386b59082da8c9a0b111e054781 -- 2.56.0.611.g1030f0b940-goog