Linux EXT4 FS development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Christian Brauner" <brauner@kernel.org>, "Jan Kara" <jack@suse.com>
Cc: "Al Viro" <viro@zeniv.linux.org.uk>,
	linux-ext4@vger.kernel.org, linux-mm@kvack.org,
	linux-efi@vger.kernel.org, linux-mtd@lists.infradead.org,
	dsterba@suse.cz, tytso@mit.edu, "Mark Fasheh" <mark@fasheh.com>,
	"Joel Becker" <jlbec@evilplan.org>,
	"Joseph Qi" <joseph.qi@linux.alibaba.com>
Subject: Re: [PATCH v2 28/34] ubifs: don't set SB_RDONLY after errors
Date: Fri, 30 Oct 2026 16:10:12 +0000	[thread overview]
Message-ID: <sashiko-outbox-191247@kernel.org> (raw)
In-Reply-To: <20261029-work-fs-sb_rdonly-v2-28-2f44f64f0b66@kernel.org>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] ubifs: writeback and ubifs_remount_ro() run inconsistently after ro_error
- [Medium] ubifs: unguarded assert in ubifs_reconfigure() when remounting after ro_error
- [Medium] ubifs: setting SB_RDONLY before SB_BORN in ubifs_ro_mode() desyncs ro state
--

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

ubifs: don't set SB_RDONLY after errors

This commit stops setting SB_RDONLY in ubifs_ro_mode() once SB_BORN is set on
the superblock, calling sb_set_emergency_ro() instead. It also allows
read-only remounts to proceed through ubifs_remount_ro() after an error and
checks c->ro_error in the xattr setters.

> diff --git a/fs/ubifs/io.c b/fs/ubifs/io.c
> index 6c6d682427793..7a59aa1ea5ce9 100644
> --- a/fs/ubifs/io.c
> +++ b/fs/ubifs/io.c
> @@ -72,7 +72,14 @@ void ubifs_ro_mode(struct ubifs_info *c, int err)
>  	if (!c->ro_error) {
>  		c->ro_error = 1;
>  		c->no_chk_data_crc = 0;
> -		c->vfs_sb->s_flags |= SB_RDONLY;
> +		/*
> +		 * Before SB_BORN the mounter holds s_umount, later see
> +		 * sb_set_emergency_ro().
> +		 */
> +		if (!(c->vfs_sb->s_flags & SB_BORN))
> +			c->vfs_sb->s_flags |= SB_RDONLY;
> +		else
> +			sb_set_emergency_ro(c->vfs_sb);

[Severity: Medium]
Can setting SB_RDONLY instead of calling sb_set_emergency_ro() before SB_BORN
is set race on sb->s_flags and leave c->ro_mount and SB_STATE_EMERGENCY_RO out
of sync with sb->s_flags?

In mount_ubifs(), c->ro_mount is latched once at entry and the background
thread c->bgt is started before vfs_get_tree() sets SB_BORN:

fs/ubifs/super.c:mount_ubifs() {
	c->ro_mount = !!sb_rdonly(c->vfs_sb);
	...
	if (!c->ro_mount) {
		/* Create background thread */
		c->bgt = kthread_run(ubifs_bg_thread, c, "%s", c->bgt_name);
}

Before vfs_get_tree() sets SB_BORN, ubifs_ro_mode() can be called without
failing the mount if an assertion fails under the default ASSACT_RO action in
ubifs_assert_failed() during mount_ubifs() or ubifs_iget(sb, UBIFS_ROOT_INO),
or if ubifs_bg_thread() wakes up after ubifs_fill_super() unlocks
c->umount_mutex and hits an error in ubifs_bg_wbufs_sync() or run_bg_commit().

If c->bgt calls ubifs_ro_mode() before SB_BORN is set, modifying
c->vfs_sb->s_flags |= SB_RDONLY happens without holding s_umount while the
mounter thread is modifying sb->s_flags in vfs_get_tree().

In addition, the mount finishes with SB_RDONLY set on sb->s_flags,
c->ro_error == 1, c->ro_mount == 0, and SB_STATE_EMERGENCY_RO clear:

fs/ubifs/super.c:ubifs_reconfigure() {
	if (c->ro_mount && !(fc->sb_flags & SB_RDONLY)) {
		if (c->ro_error) {
			ubifs_msg(c, "cannot re-mount R/W due to prior errors");
			return -EROFS;
		}
		...
	} else if (!c->ro_mount && (fc->sb_flags & SB_RDONLY)) {
		/* After an error ubifs_remount_ro() can't write but still works. */
		ubifs_remount_ro(c);
	}
}

Because sb_emergency_ro(sb) is false and c->ro_mount is 0, a subsequent
remount to read-write passes both reconfigure_super() and ubifs_reconfigure()
and clears SB_RDONLY, while a remount to read-only sees sb_rdonly(sb) as true
in reconfigure_super() and skips sb_prepare_remount_readonly() and
sb_start_ro_state_change() before calling ubifs_remount_ro().

Should ubifs_ro_mode() call sb_set_emergency_ro(c->vfs_sb) unconditionally?

[ ... ]

> diff --git a/fs/ubifs/super.c b/fs/ubifs/super.c
> index 38972786817e7..ef39da4e780b7 100644
> --- a/fs/ubifs/super.c
> +++ b/fs/ubifs/super.c
> @@ -1977,10 +1977,7 @@ static int ubifs_reconfigure(struct fs_context *fc)
>  		if (err)
>  			return err;
>  	} else if (!c->ro_mount && (fc->sb_flags & SB_RDONLY)) {
> -		if (c->ro_error) {
> -			ubifs_msg(c, "cannot re-mount R/O due to prior errors");
> -			return -EROFS;
> -		}
> +		/* After an error ubifs_remount_ro() can't write but still works. */

[Severity: High]
Does allowing ubifs_remount_ro() to run after c->ro_error is set cause a NULL
pointer dereference on c->write_reserve_buf or trigger assertions in
reserve_space() and ubifs_wbuf_sync_nolock()?

Previously, ubifs_ro_mode() set SB_RDONLY on sb->s_flags (causing
sync_filesystem() to return early via sb_rdonly(sb)), and ubifs_reconfigure()
refused to call ubifs_remount_ro(c) when c->ro_error was set.

With SB_RDONLY no longer set on error, writeback still runs
ubifs_writepages() -> ubifs_writepage(). When a dirty folio extends past
ui->synced_i_size, ubifs_writepage() calls ubifs_write_inode():

fs/ubifs/file.c:ubifs_writepage() {
		if (folio_pos(folio) + len > synced_i_size) {
			err = inode->i_sb->s_op->write_inode(inode, NULL);
			if (err)
				goto out_redirty;
		}
		return do_writepage(folio, len);
}

In ubifs_write_inode(), ubifs_jnl_write_inode() fails with -EROFS when
c->ro_error is set without updating ui->synced_i_size, but ubifs_write_inode()
still clears ui->dirty = 0:

fs/ubifs/super.c:ubifs_write_inode() {
	if (inode->i_nlink) {
		err = ubifs_jnl_write_inode(c, inode);
		if (err)
			ubifs_err(c, "can't write inode %llu, error %d",
				  inode->i_ino, err);
		else
			err = dbg_check_inode_size(c, inode, ui->ui_size);
	}

	ui->dirty = 0;
	mutex_unlock(&ui->ui_mutex);
	ubifs_release_dirty_inode_budget(c, ui);
	return err;
}

ubifs_writepage() then jumps to out_redirty and calls
folio_redirty_for_writepage(), which re-marks the folio PAGECACHE_TAG_DIRTY
and marks the inode I_DIRTY_PAGES without calling ubifs_dirty_inode(), leaving
ui->dirty == 0 while requeue_inode() puts the inode back onto wb->b_dirty.

If sync_inodes_sb() during ubifs_reconfigure() is the first writeback pass to
hit c->ro_error on that inode (for example, if writeback_inodes_sb() stopped
earlier after exhausting work->nr_pages or its time slice in
writeback_sb_inodes(), or skipped the inode while I_SYNC was set),
sync_filesystem() finishes with the redirtied folio still on wb->b_dirty and
ui->dirty == 0.

ubifs_reconfigure() ignores the return value of sync_filesystem() and calls
ubifs_remount_ro(), which frees c->write_reserve_buf and sets c->ro_mount = 1:

fs/ubifs/super.c:ubifs_remount_ro() {
	kfree(c->write_reserve_buf);
	c->write_reserve_buf = NULL;
	vfree(c->ileb_buf);
	c->ileb_buf = NULL;
	ubifs_lpt_free(c, 1);
	c->ro_mount = 1;
}

When background writeback (wb_workfn) subsequently flushes wb->b_dirty,
ubifs_writepage() calls ubifs_write_inode() again. Because ui->dirty is 0,
ubifs_write_inode() returns 0 immediately, and ubifs_writepage() proceeds into
do_writepage() -> ubifs_jnl_write_data():

fs/ubifs/journal.c:ubifs_jnl_write_data() {
	data = kmalloc(dlen + auth_len, GFP_NOFS | __GFP_NOWARN);
	if (!data) {
		allocated = 0;
		mutex_lock(&c->write_reserve_mutex);
		data = c->write_reserve_buf;
	}

	data->ch.node_type = UBIFS_DATA_NODE;
}

If kmalloc() fails under memory pressure, data = c->write_reserve_buf is NULL
and data->ch.node_type dereferences NULL. Even if kmalloc() succeeds,
make_reservation() -> reserve_space() hits
ubifs_assert(c, !c->ro_media && !c->ro_mount) before checking c->ro_error:

fs/ubifs/journal.c:reserve_space() {
	ubifs_assert(c, !c->ro_media && !c->ro_mount);
	squeeze = (jhead == BASEHD);
again:
	mutex_lock_nested(&wbuf->io_mutex, wbuf->jhead);

	if (c->ro_error) {
		err = -EROFS;
		goto out_unlock;
	}
}

Furthermore, unlike ubifs_put_super() (which guards ubifs_wbuf_sync() with
if (!c->ro_error)), ubifs_remount_ro() and ubifs_sync_fs() call
ubifs_wbuf_sync() -> ubifs_wbuf_sync_nolock(), which evaluates wbuf geometry
assertions before checking c->ro_error:

fs/ubifs/io.c:ubifs_wbuf_sync_nolock() {
	ubifs_assert(c, !(wbuf->avail & 7));
	ubifs_assert(c, wbuf->offs + wbuf->size <= c->leb_size);
	ubifs_assert(c, wbuf->size >= c->min_io_size);
	ubifs_assert(c, wbuf->size <= c->max_write_size);
	ubifs_assert(c, wbuf->size % c->min_io_size == 0);
	ubifs_assert(c, !c->ro_media && !c->ro_mount);
	if (c->leb_size - wbuf->offs >= c->max_write_size)
		ubifs_assert(c, !((wbuf->offs + wbuf->size) % c->max_write_size));

	if (c->ro_error)
		return -EROFS;
}

If c->ro_error was triggered by a mid-write failure in
ubifs_wbuf_write_nolock() after advancing wbuf->offs while wbuf->used > 0 and
before updating wbuf->size, those assertions can fire during
ubifs_remount_ro() or ubifs_sync_fs().

>  		ubifs_remount_ro(c);

[Severity: Medium]
Can remounting read-only after a garbage collection error trigger the
c->lst.taken_empty_lebs > 0 assertion at the end of ubifs_reconfigure()?

At the end of ubifs_reconfigure(), the code checks:

fs/ubifs/super.c:ubifs_reconfigure() {
	if (!c->need_recovery)
		ubifs_assert(c, c->lst.taken_empty_lebs > 0);
}

When c->lst.taken_empty_lebs is 1 (holding only the reserved GC LEB
c->gc_lnum), ubifs_garbage_collect() -> ubifs_garbage_collect_leb() ->
move_nodes() -> switch_gc_head() consumes c->gc_lnum and sets c->gc_lnum = -1.
Once the first node is written to the GC write-buffer, bud_wbuf_callback() ->
ubifs_update_one_lp() -> ubifs_change_lp() decrements c->lst.taken_empty_lebs
to 0.

If an error then occurs in move_nodes() or gc_sync_wbufs() before a new
c->gc_lnum is retained, ubifs_garbage_collect() switches to read-only error
mode with c->ro_error == 1, c->gc_lnum == -1, and
c->lst.taken_empty_lebs == 0:

fs/ubifs/gc.c:ubifs_garbage_collect() {
out:
	ubifs_assert(c, ret < 0);
	ubifs_assert(c, ret != -ENOSPC && ret != -EAGAIN);
	ubifs_wbuf_sync_nolock(wbuf);
	ubifs_ro_mode(c, ret);
	mutex_unlock(&wbuf->io_mutex);
	if (lp.lnum != -1)
		ubifs_return_leb(c, lp.lnum);
	return ret;
}

Now that ubifs_reconfigure() no longer returns -EROFS on a read-only remount
when c->ro_error is set, should the assertion at the end of
ubifs_reconfigure() also be guarded by !c->ro_error, similar to
ubifs_put_super()?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-work-fs-sb_rdonly-v2-0-2f44f64f0b66@kernel.org?part=28

  parent reply	other threads:[~2026-10-30 16:10 UTC|newest]

Thread overview: 72+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-29 16:06 [PATCH v2 00/34] fs: don't set SB_RDONLY after filesystem errors Christian Brauner
2026-10-29 16:06 ` [PATCH v2 01/34] ecryptfs: don't leak the inode when a lookup can't open the lower file Christian Brauner
2026-10-30 16:09   ` sashiko-bot
2026-10-29 16:06 ` [PATCH v2 02/34] ecryptfs: fall back to a read-only " Christian Brauner
2026-10-30 16:09   ` sashiko-bot
2026-10-29 16:06 ` [PATCH v2 03/34] fs: add a generic emergency read-only state Christian Brauner
2026-10-30 16:09   ` sashiko-bot
2026-10-29 16:06 ` [PATCH v2 04/34] ext2: don't set SB_RDONLY after filesystem errors Christian Brauner
2026-10-30 16:09   ` sashiko-bot
2026-10-29 16:06 ` [PATCH v2 05/34] jfs: " Christian Brauner
2026-10-30 16:09   ` sashiko-bot
2026-10-29 16:06 ` [PATCH v2 06/34] nilfs2: " Christian Brauner
2026-10-30 16:09   ` sashiko-bot
2026-10-29 16:06 ` [PATCH v2 07/34] fat: " Christian Brauner
2026-10-30 16:09   ` sashiko-bot
2026-10-29 16:06 ` [PATCH v2 08/34] exfat: keep the current mount options on remount Christian Brauner
2026-10-30 16:09   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 09/34] exfat: don't set SB_RDONLY after filesystem errors Christian Brauner
2026-10-30 16:09   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 10/34] exfat: use the generic emergency read-only state Christian Brauner
2026-10-30 16:09   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 11/34] hfs: don't set SB_RDONLY after MDB write errors Christian Brauner
2026-10-30 16:09   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 12/34] hfs: use the generic emergency read-only state Christian Brauner
2026-10-30 16:09   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 13/34] hfsplus: don't set SB_RDONLY when creating the attributes tree Christian Brauner
2026-10-30 16:09   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 14/34] affs: don't set SB_RDONLY after filesystem errors Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 15/34] ufs: " Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 16/34] hpfs: " Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 17/34] ntfs: " Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 18/34] ntfs: use the generic emergency read-only state Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 19/34] ocfs2: keep the current mount options on remount Christian Brauner
2026-10-30  2:58   ` Joseph Qi
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 20/34] ocfs2: don't set SB_RDONLY after filesystem errors Christian Brauner
2026-10-30  3:01   ` Joseph Qi
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 21/34] zonefs: don't set SB_RDONLY after I/O errors Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 22/34] zonefs: use the generic emergency read-only state Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 23/34] btrfs: check BTRFS_FS_STATE_RO instead of SB_RDONLY at runtime Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 24/34] btrfs: don't set SB_RDONLY after filesystem errors Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 25/34] gfs2: wait for a pending withdraw before remounting read-only Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 26/34] gfs2: don't set SB_RDONLY on withdraw Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 27/34] efivarfs: stop using SB_RDONLY for read-only efivar ops Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 28/34] ubifs: don't set SB_RDONLY after errors Christian Brauner
2026-10-30  3:42   ` Zhihao Cheng
2026-10-30 16:10   ` sashiko-bot [this message]
2026-10-29 16:07 ` [PATCH v2 29/34] selftests/filesystems: test filesystem errors while frozen Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 30/34] ext4: allow remounting read-only in the emergency state Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 31/34] ext4: use the generic emergency read-only state Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 32/34] selftests/filesystems: test ext4 remounts in the emergency state Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 33/34] xfs: allow remounting read-only after a shutdown Christian Brauner
2026-10-30 16:10   ` sashiko-bot
2026-10-29 16:07 ` [PATCH v2 34/34] selftests/filesystems: test xfs remounts " Christian Brauner
2026-10-30 16:10   ` sashiko-bot

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-191247@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=brauner@kernel.org \
    --cc=dsterba@suse.cz \
    --cc=jack@suse.com \
    --cc=jlbec@evilplan.org \
    --cc=joseph.qi@linux.alibaba.com \
    --cc=linux-efi@vger.kernel.org \
    --cc=linux-ext4@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=linux-mtd@lists.infradead.org \
    --cc=mark@fasheh.com \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=tytso@mit.edu \
    --cc=viro@zeniv.linux.org.uk \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox