Re: Misuse of mapping_set_folio_order_range()
Jan Kara <[email protected]> Tue, 28 Jul 2026 21:32:19 +0200
| Newsgroups | org.kernel.vger.linux-ext4 |
|---|---|
| Message-ID | <b3t6rtuszod6kz5b6btleqf2cjkul7i7aysgn3cknr5ppqcab4@5xjyanf6jscj> |
On Mon 27-07-26 21:25:01, Matthew Wilcox wrote: > While reviewing an unrelated change, Sashiko seems to have found a bug > in ext4: > > > --- Patch [9]: [PATCH v6 09/12] filemap: Remove checks in mapping_set_folio_order_range() --- > > commit c0859bbb9afcf450bfdee34a144cba981c50e605 > > Author: Matthew Wilcox (Oracle) <[email protected]> > > > > filemap: Remove checks in mapping_set_folio_order_range() > > > > This commit removes the maximum order checks in mapping_set_folio_order_range() > > to allow its use in hugetlb. > > > > > diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h > > > index 2c3718d592d6d..968b791cfd148 100644 > > > --- a/include/linux/pagemap.h > > > +++ b/include/linux/pagemap.h > > > @@ -405,29 +405,23 @@ static inline size_t mapping_max_folio_size_supported(void) > > > > [ ... ] > > > > > static inline void mapping_set_folio_order_range(struct address_space *mapping, > > > - unsigned int min, > > > - unsigned int max) > > > + unsigned int min, unsigned int max) > > > { > > > - if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE)) > > > + if (!IS_ENABLED(CONFIG_LARGE_FOLIO)) > > > return; > > > - > > > - if (min > MAX_PAGECACHE_ORDER) > > > - min = MAX_PAGECACHE_ORDER; > > > - > > > - if (max > MAX_PAGECACHE_ORDER) > > > - max = MAX_PAGECACHE_ORDER; > > > - > > > if (max < min) > > > max = min; > > > > > > mapping->flags = (mapping->flags & ~AS_FOLIO_ORDER_MASK) | > > > (min << AS_FOLIO_ORDER_MIN) | (max << AS_FOLIO_ORDER_MAX); > > > > This is a pre-existing issue, but can this non-atomic read-modify-write of > > mapping->flags cause lost updates to concurrently set atomic bit flags? > > > > While the documentation above this function states it should not be called > > while the inode is active, ext4_change_inode_journal_flag() dynamically > > invokes it on an active inode during the EXT4_IOC_SETFLAGS ioctl: > > > > fs/ext4/inode.c:ext4_change_inode_journal_flag() { > > ... > > ext4_set_inode_mapping_order(inode); > > ... > > } > > > > If a privileged user toggles the ext4 journal flag, could this non-atomic > > update race with mm_take_all_locks() (which can be triggered by userfaultfd > > or KVM) modifying AS_MM_ALL_LOCKS via atomic bitops? > > > > Since they do not share any locks (ext4_change_inode_journal_flag() holds > > inode_lock and invalidate_lock, while mm_take_all_locks() holds mmap_lock > > and i_mmap_rwsem), losing the AS_MM_ALL_LOCKS bit could cause > > vm_unlock_mapping() to skip unlocking mapping->i_mmap_rwsem, resulting in a > > permanent deadlock. > > Making mapping_set_folio_order_range() atomic would be a pain -- it'd > involve a cmpxchg() loop which would be utterly useless for most > callers. Can you figure out how to fix this in ext4? What we need to do in ext4_change_inode_journal_flag() is that we want to disable / enable large folios when enabling / disabling data journalling for an inode (because writeback of large folios would strain the journal too much when journalling data). We are actually pretty careful in ext4_change_inode_journal_flag() - we lock the inode_lock, invalidate_lock, writeout everything, prune all the page cache, make sure to synchronize with writeback path and then we switch adress_space_operations and allowed folio orders. As far as I can tell AS_MM_ALL_LOCKS flags bit manipulation is the only thing we can realistically race with. But I don't see how we could avoid that race from ext4 :-|. Maybe we could have mapping_set_folio_order_range_atomic() helper to avoid the pointless overhead for everybody? Honza -- Jan Kara <[email protected]> SUSE Labs, CR