From: Hongling Zeng <zhongling0719@126.com>
To: Hongling Zeng <zenghongling@kylinos.cn>,
linkinjeon@kernel.org, hyc.lee@gmail.com
Cc: ntfs@lists.linux.dev, linux-kernel@vger.kernel.org,
stable@vger.kernel.org
Subject: Re: [PATCH 1/3] ntfs: fix volume flag update races
Date: Thu, 03 Sep 2026 10:21:15 +0800 [thread overview]
Message-ID: <6A98D99B.5020504@126.com> (raw)
In-Reply-To: <20260903021930.201169-1-zenghongling@kylinos.cn>
Hi,
Sorry for the many versions of this patch.
The ntfs_sync_fs() race you pointed out is fixed in patch 1/3:
NVolErrors() is checked and VOLUME_IS_DIRTY is cleared inside the same
mrec_lock critical section, so every interleaving with a concurrent
error path
leaves the volume dirty on disk.
Patches 2/3 and 3/3 close the writer-side window: the runtime error
paths now record NVolErrors() before taking the mrec_lock to persist
VOLUME_IS_DIRTY, so a clear path running afterwards sees the flag under
the lock
and leaves the dirty bit alone.
Looking forward to your review and feedback.
Thanks,
Hongling
在 2026年09月03日 10:19, Hongling Zeng 写道:
> ntfs_set_volume_flags() and ntfs_clear_volume_flags() both read
> vol->vol_flags outside any lock to compute the new value before handing
> it to ntfs_write_volume_flags(), which only takes ni->mrec_lock around
> the actual write. The read-modify-write is therefore not atomic, and two
> concurrent callers can lose an update: ntfs_sync_fs() may derive a
> "clean" value from vol->vol_flags while a writer concurrently records an
> error and sets VOLUME_IS_DIRTY; the locked write then silently
> overwrites the freshly-set dirty bit. The on-disk volume looks clean
> despite the recorded errors, so chkdsk will not run on the next mount
> and corrupted metadata can persist.
>
> Fix by moving the read-modify-write inside the mrec_lock: pass the bits
> to set and to clear separately, and combine them with the current flag
> state under the lock inside ntfs_write_volume_flags(). The set/clear
> helpers pass only the bits to modify, not the complete flag state. The
> bit manipulation is done on CPU-endian values, and the result is
> converted back to little-endian before storing it. The wrappers keep
> their signatures so callers are unchanged.
>
> Cc: stable@vger.kernel.org
> Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn>
> ---
> - Also fix the ntfs_sync_fs() race by checking NVolErrors() and clearing
> VOLUME_IS_DIRTY under ni->mrec_lock.
> - Keep ntfs_set_volume_flags() and ntfs_clear_volume_flags() semantics
> unchanged.
> - Do not tie setting VOLUME_IS_DIRTY to NVolSetErrors() in the generic
> set helper.
> ---
> fs/ntfs/super.c | 62 +++++++++++++++++++++++++++++++++++--------------
> 1 file changed, 45 insertions(+), 17 deletions(-)
>
> diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c
> index a1813093222b..a7977b95b967 100644
> --- a/fs/ntfs/super.c
> +++ b/fs/ntfs/super.c
> @@ -353,31 +353,45 @@ void ntfs_handle_error(struct super_block *sb)
> }
>
> /*
> - * ntfs_write_volume_flags - write new flags to the volume information flags
> + * ntfs_write_volume_flags - apply flag changes to the volume information flags
> * @vol: ntfs volume on which to modify the flags
> - * @flags: new flags value for the volume information flags
> + * @set_bits: bits to set in the volume information flags
> + * @clear_bits: bits to clear in the volume information flags
> *
> * Internal function. You probably want to use ntfs_{set,clear}_volume_flags()
> * instead (see below).
> *
> - * Replace the volume information flags on the volume @vol with the value
> - * supplied in @flags. Note, this overwrites the volume information flags, so
> - * make sure to combine the flags you want to modify with the old flags and use
> - * the result when calling ntfs_write_volume_flags().
> + * Combine @set_bits and @clear_bits with the current in-memory flag state and
> + * write the result back. The set/clear helpers pass only the bits to modify,
> + * not the complete flag state. The read-modify-write happens under
> + * ni->mrec_lock so that concurrent set/clear operations cannot lose updates.
> + * All bit manipulation is done on CPU-endian values, and the result is
> + * converted back to little-endian before storing it.
> *
> * Return 0 on success and -errno on error.
> */
> -static int ntfs_write_volume_flags(struct ntfs_volume *vol, const __le16 flags)
> +static int ntfs_write_volume_flags(struct ntfs_volume *vol,
> + const __le16 set_bits, const __le16 clear_bits,
> + const bool skip_if_errors)
> {
> struct ntfs_inode *ni = NTFS_I(vol->vol_ino);
> struct volume_information *vi;
> struct ntfs_attr_search_ctx *ctx;
> + u16 flags;
> int err;
>
> - ntfs_debug("Entering, old flags = 0x%x, new flags = 0x%x.",
> - le16_to_cpu(vol->vol_flags), le16_to_cpu(flags));
> mutex_lock(&ni->mrec_lock);
> - if (vol->vol_flags == flags)
> +
> + if (skip_if_errors && NVolErrors(vol))
> + goto done;
> +
> + flags = le16_to_cpu(vol->vol_flags);
> + flags |= le16_to_cpu(set_bits) & le16_to_cpu(VOLUME_FLAGS_MASK);
> + flags &= ~(le16_to_cpu(clear_bits) & le16_to_cpu(VOLUME_FLAGS_MASK));
> + ntfs_debug("Entering, old flags = 0x%x, new flags = 0x%x.",
> + le16_to_cpu(vol->vol_flags), flags);
> +
> + if (le16_to_cpu(vol->vol_flags) == flags)
> goto done;
>
> ctx = ntfs_attr_get_search_ctx(ni, NULL);
> @@ -393,7 +407,7 @@ static int ntfs_write_volume_flags(struct ntfs_volume *vol, const __le16 flags)
>
> vi = (struct volume_information *)((u8 *)ctx->attr +
> le16_to_cpu(ctx->attr->data.resident.value_offset));
> - vol->vol_flags = vi->flags = flags;
> + vol->vol_flags = vi->flags = cpu_to_le16(flags);
> mark_mft_record_dirty(ctx->ntfs_ino);
> ntfs_attr_put_search_ctx(ctx);
> done:
> @@ -414,13 +428,14 @@ static int ntfs_write_volume_flags(struct ntfs_volume *vol, const __le16 flags)
> * @flags: flags to set on the volume
> *
> * Set the bits in @flags in the volume information flags on the volume @vol.
> + * The bits are combined with the current flag state under the lock in
> + * ntfs_write_volume_flags(), so concurrent updates are not lost.
> *
> * Return 0 on success and -errno on error.
> */
> int ntfs_set_volume_flags(struct ntfs_volume *vol, __le16 flags)
> {
> - flags &= VOLUME_FLAGS_MASK;
> - return ntfs_write_volume_flags(vol, vol->vol_flags | flags);
> + return ntfs_write_volume_flags(vol, flags, 0, false);
> }
>
> /*
> @@ -429,14 +444,27 @@ int ntfs_set_volume_flags(struct ntfs_volume *vol, __le16 flags)
> * @flags: flags to clear on the volume
> *
> * Clear the bits in @flags in the volume information flags on the volume @vol.
> + * The bits are combined with the current flag state under the lock in
> + * ntfs_write_volume_flags(), so concurrent updates are not lost.
> *
> * Return 0 on success and -errno on error.
> */
> int ntfs_clear_volume_flags(struct ntfs_volume *vol, __le16 flags)
> {
> - flags &= VOLUME_FLAGS_MASK;
> - flags = vol->vol_flags & cpu_to_le16(~le16_to_cpu(flags));
> - return ntfs_write_volume_flags(vol, flags);
> + return ntfs_write_volume_flags(vol, 0, flags, false);
> +}
> +
> +/*
> + * ntfs_clear_volume_dirty_if_no_errors - clear dirty bit if no errors exist
> + * @vol: ntfs volume whose dirty bit should be cleared
> + *
> + * Check NVolErrors() and clear VOLUME_IS_DIRTY under the same mrec_lock so
> + * ntfs_sync_fs() cannot clear the dirty bit after a concurrent error has been
> + * recorded.
> + */
> +static int ntfs_clear_volume_dirty_if_no_errors(struct ntfs_volume *vol)
> +{
> + return ntfs_write_volume_flags(vol, 0, VOLUME_IS_DIRTY, true);
> }
>
> int ntfs_write_volume_label(struct ntfs_volume *vol, char *label)
> @@ -1862,7 +1890,7 @@ static int ntfs_sync_fs(struct super_block *sb, int wait)
> return 0;
>
> /* If there are some dirty buffers in the bdev inode */
> - if (ntfs_clear_volume_flags(vol, VOLUME_IS_DIRTY)) {
> + if (ntfs_clear_volume_dirty_if_no_errors(vol)) {
> ntfs_warning(sb, "Failed to clear dirty bit in volume information flags. Run chkdsk.");
> err = -EIO;
> }
next prev parent reply other threads:[~2026-09-03 2:25 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-03 2:19 [PATCH 1/3] ntfs: fix volume flag update races Hongling Zeng
2026-09-03 2:19 ` [PATCH 2/3] ntfs: add helper to record error flag before setting volume dirty bit Hongling Zeng
2026-09-03 2:19 ` [PATCH 3/3] ntfs: convert runtime error paths to mark volume dirty after recording errors Hongling Zeng
2026-09-03 2:21 ` Hongling Zeng [this message]
2026-09-03 2:59 ` [PATCH 1/3] ntfs: fix volume flag update races liubaolin
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=6A98D99B.5020504@126.com \
--to=zhongling0719@126.com \
--cc=hyc.lee@gmail.com \
--cc=linkinjeon@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=ntfs@lists.linux.dev \
--cc=stable@vger.kernel.org \
--cc=zenghongling@kylinos.cn \
/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.