All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Christian Brauner" <brauner@kernel.org>
Cc: linux-ext4@vger.kernel.org, tytso@mit.edu
Subject: Re: [PATCH RFC v3 08/14] fs: set I_METADATA_WRITEBACK in mmb_mark_buffer_dirty()
Date: Mon, 09 Nov 2026 17:50:03 +0000	[thread overview]
Message-ID: <sashiko-outbox-205344@kernel.org> (raw)
In-Reply-To: <20261109-work-inode-metadata_writeback-v3-8-91ac6c024018@kernel.org>

Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [High] ext4: crash in fastcommit replay when mmb_mark_buffer_dirty()
  sets writeback
- [Medium] fs/buffer: early return in mmb_mark_buffer_dirty() skips
  I_METADATA_WRITEBACK
- [Medium] fs/buffer: mmb_mark_buffer_dirty() sets b_mmb before adding
  bh to mmb->list
--

commit f5c565458ebd0a39fc1e91b886caf335fa191c4d
Author: Christian Brauner <brauner@kernel.org>

fs: set I_METADATA_WRITEBACK in mmb_mark_buffer_dirty()

This commit attaches the inode to a writeback structure and sets
I_METADATA_WRITEBACK in mmb_mark_buffer_dirty() so that fsync() and
inode eviction write out dirty metadata buffers tracked on the mapping.

> diff --git a/fs/buffer.c b/fs/buffer.c
> index 5d3852b726d6b..f951559e7d32c 100644
> --- a/fs/buffer.c
> +++ b/fs/buffer.c
> @@ -621,12 +621,15 @@ void write_boundary_block(struct block_device *bdev,
>   * @bh: The buffer to mark dirty.
>   * @mmb: The list of buffers to add the buffer to.
>   *
> - * Mark the buffer dirty and add it to the list if it is not already on
> - * a list.
> + * Mark the buffer dirty, add it to the list if it is not already on a list
> + * and set I_METADATA_WRITEBACK on the inode so that fsync() and eviction
> + * write it out.
>   */
>  void mmb_mark_buffer_dirty(struct buffer_head *bh,
>  			   struct mapping_metadata_bhs *mmb)
>  {
> +	struct inode *inode = mmb->mapping->host;
> +
>  	mark_buffer_dirty(bh);
>  	if (!bh->b_mmb) {
>  		spin_lock(&mmb->lock);
> @@ -641,6 +644,15 @@ void mmb_mark_buffer_dirty(struct buffer_head *bh,

[Severity: Medium]
Can a concurrent mmb_mark_buffer_dirty() set I_METADATA_WRITEBACK before
bh is linked onto mmb->list?

CPU0                                    CPU1
mmb_mark_buffer_dirty(bh, mmb)
  spin_lock(&mmb->lock)
  cmpxchg(&bh->b_mmb, NULL, mmb)
                                        mmb_mark_buffer_dirty(bh, mmb)
                                          if (!bh->b_mmb) // false
                                          inode_state_set(...,
                                            I_METADATA_WRITEBACK)
                                        fsync()
                                          __writeback_single_inode()
                                            inode_state_clear(...,
                                              I_METADATA_WRITEBACK)
                                            mmb_sync(mmb)
                                              if (!mmb_has_buffers(mmb))
                                                return 0;  <-- skips bh
  list_move_tail(..., &mmb->list)
  spin_unlock(&mmb->lock)

Because cmpxchg() sets bh->b_mmb before list_move_tail() adds bh to
mmb->list, CPU1 sees bh->b_mmb != NULL without taking mmb->lock, sets
I_METADATA_WRITEBACK, and returns while mmb->list is still empty.  An
intervening fsync() can then clear I_METADATA_WRITEBACK and return early
from mmb_sync() when its lockless mmb_has_buffers() check sees an empty
list, leaving bh unsynced.

[Severity: Medium]
Can the early return on cmpxchg(&bh->b_mmb, NULL, mmb) != NULL skip
setting I_METADATA_WRITEBACK when racing with mmb_sync()?

CPU0                                    CPU1
__writeback_single_inode()
  inode_state_clear(...,
    I_METADATA_WRITEBACK)
  mmb_sync(mmb)
    spin_lock(&mmb->lock)
    __remove_assoc_queue(mmb, bh)
      bh->b_mmb = NULL
                                        mmb_mark_buffer_dirty(bh, mmb)
                                          mark_buffer_dirty(bh)
                                          if (!bh->b_mmb) // true
    smp_mb()
    if (buffer_dirty(bh))
      list_add(..., &mmb->list)
      bh->b_mmb = mmb
    spin_unlock(&mmb->lock)
                                          spin_lock(&mmb->lock)
                                          cmpxchg(&bh->b_mmb, NULL, mmb)
                                            // returns mmb != NULL
                                          spin_unlock(&mmb->lock)
                                          return;  <-- skips setting bit

__writeback_single_inode() clears I_METADATA_WRITEBACK before calling
sync_inode_metadata() -> mmb_sync().  In the second pass of mmb_sync(),
__remove_assoc_queue() transiently clears bh->b_mmb and then restores
bh->b_mmb = mmb after seeing BH_Dirty from CPU1.  CPU1 then fails the
cmpxchg() and returns early without setting I_METADATA_WRITEBACK, leaving
the redirtied buffer on mmb->list with I_METADATA_WRITEBACK clear on the
inode so a subsequent fsync() or evict() will not write it out.

>  		list_move_tail(&bh->b_assoc_buffers, &mmb->list);
>  		spin_unlock(&mmb->lock);
>  	}
> +	/*
> +	 * The inode itself may never have been dirtied, so attach it to a wb
> +	 * before setting the bit or writeback_single_inode() has nothing to
> +	 * lock when evict() syncs it.
> +	 */
> +	spin_lock(&inode->i_lock);
> +	inode_attach_wb(inode, NULL);
> +	inode_state_set(inode, I_METADATA_WRITEBACK);
> +	spin_unlock(&inode->i_lock);
>  }
>  EXPORT_SYMBOL(mmb_mark_buffer_dirty);

[Severity: High]
Does setting I_METADATA_WRITEBACK here cause a NULL pointer dereference
during ext4 fastcommit replay?

0ecd56573c1f ("ext4: Avoid entering writeback paths during fastcommit
replay") guarded ext4_mark_iloc_dirty() against EXT4_FC_REPLAY so it
would not set I_METADATA_WRITEBACK while SB_ACTIVE is still clear and
s_writepages_rwsem is not yet initialized.  However, fastcommit replay
paths such as ext4_fc_replay_create() and ext4_fc_replay_add_range()
dirty directory or extent blocks with handle == NULL and a non-NULL
inode, which reaches mmb_mark_buffer_dirty():

fs/ext4/ext4_jbd2.c:__ext4_handle_dirty_metadata() {
    ...
	} else {
		if (inode) {
			if (!ext4_i_metadata_bhs(inode))
				ext4_inode_attach_mmb(inode);
			mmb_mark_buffer_dirty(bh, ext4_i_metadata_bhs(inode));
    ...
}

When replay finishes with the inode and calls iput(), iput_final() sees
!(sb->s_flags & SB_ACTIVE) and calls write_inode_now(inode, 1).  With
I_METADATA_WRITEBACK now set, __writeback_single_inode() runs
do_writepages() -> ext4_writepages() -> percpu_down_read() on the
uninitialized s_writepages_rwsem, crashing the kernel, and also calls
ext4_sync_inode_metadata(), which triggers
WARN_ON_ONCE(EXT4_SB(inode->i_sb)->s_journal) and returns -EFSCORRUPTED.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261109-work-inode-metadata_writeback-v3-0-91ac6c024018@kernel.org?part=8

  reply	other threads:[~2026-11-09 17:50 UTC|newest]

Thread overview: 36+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-09 16:51 [PATCH RFC v3 00/14] fs: reclaim inodes with I_METADATA_WRITEBACK Christian Brauner
2026-11-09 16:51 ` [PATCH RFC v3 01/14] ext2: add ext2_inode_block() helper Christian Brauner
2026-11-09 16:57   ` sashiko-bot
2026-11-09 16:51 ` [PATCH RFC v3 02/14] ext2: don't read the inode block in ext2_sync_inode_metadata() Christian Brauner
2026-11-09 17:02   ` sashiko-bot
2026-11-09 16:51 ` [PATCH RFC v3 03/14] ext4: use sb_find_get_block_nonatomic() in ext4_get_inode_loc_noio() Christian Brauner
2026-11-09 17:06   ` sashiko-bot
2026-11-09 16:51 ` [PATCH RFC v3 04/14] ext4: don't read the inode block in ext4_sync_inode_metadata() Christian Brauner
2026-11-09 17:11   ` sashiko-bot
2026-11-09 16:51 ` [PATCH RFC v3 05/14] minix: add minix_inode_block() helper Christian Brauner
2026-11-09 17:13   ` sashiko-bot
2026-11-09 16:51 ` [PATCH RFC v3 06/14] minix: don't read the inode block in minix_sync_inode_metadata() Christian Brauner
2026-11-09 17:16   ` sashiko-bot
2026-11-09 16:51 ` [PATCH RFC v3 07/14] udf: don't allocate the inode block in udf_sync_inode_metadata() Christian Brauner
2026-11-09 17:24   ` sashiko-bot
2026-11-09 16:51 ` [PATCH RFC v3 08/14] fs: set I_METADATA_WRITEBACK in mmb_mark_buffer_dirty() Christian Brauner
2026-11-09 17:50   ` sashiko-bot [this message]
2026-11-10 10:33   ` Jan Kara
2026-11-09 16:51 ` [PATCH RFC v3 09/14] fs: don't strand I_METADATA_WRITEBACK inodes outside the LRU Christian Brauner
2026-11-09 18:18   ` sashiko-bot
2026-11-10 10:28   ` Jan Kara
2026-11-09 16:51 ` [PATCH RFC v3 10/14] ext2: drop mmb_sync() from ext2_evict_inode() Christian Brauner
2026-11-09 18:36   ` sashiko-bot
2026-11-10 10:28   ` Jan Kara
2026-11-09 16:51 ` [PATCH RFC v3 11/14] ext4: drop mmb_sync() from ext4_evict_inode() Christian Brauner
2026-11-09 18:55   ` sashiko-bot
2026-11-10 10:28   ` Jan Kara
2026-11-09 16:51 ` [PATCH RFC v3 12/14] minix: drop mmb_sync() from minix_evict_inode() Christian Brauner
2026-11-09 18:58   ` sashiko-bot
2026-11-10 10:28   ` Jan Kara
2026-11-09 16:51 ` [PATCH RFC v3 13/14] udf: drop mmb_sync() from udf_evict_inode() Christian Brauner
2026-11-09 19:03   ` sashiko-bot
2026-11-10 10:29   ` Jan Kara
2026-11-09 16:51 ` [PATCH RFC v3 14/14] fat: drop mmb_sync() from fat_evict_inode() Christian Brauner
2026-11-09 19:08   ` sashiko-bot
2026-11-10 10:29   ` Jan Kara

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-205344@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=brauner@kernel.org \
    --cc=linux-ext4@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=tytso@mit.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.