* [PATCH 0/3] ntfs: fix quota out-of-date marking
@ 2026-05-11 16:06 DaeMyung Kang
2026-05-11 16:06 ` [PATCH 1/3] ntfs: return view index entry data from lookup DaeMyung Kang
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: DaeMyung Kang @ 2026-05-11 16:06 UTC (permalink / raw)
To: linkinjeon, hyc.lee; +Cc: linux-fsdevel, linux-kernel, DaeMyung Kang
This series fixes NTFS quota out-of-date marking in fs/ntfs.
The driver already has quota compatibility code: it loads
FILE_Extend/$Quota, opens the $Q index, and has
ntfs_mark_quotas_out_of_date() to set QUOTA_FLAG_OUT_OF_DATE so Windows
can rescan quota usage. The failures were not an intentional quota block;
the code could not reach and update the $Quota/$Q defaults entry correctly.
Patch 1 fixes ntfs_index_lookup() so callers looking up view indexes get
the index entry value instead of the key. Directory $I30 callers keep the
existing key-return behavior. The patch uses the index root type, not the
collation rule, to choose between $FILE_NAME keys and view-index values. It
also validates the returned value bounds without truncating the minimum
offset, excludes the child VCN tail from INDEX_ENTRY_NODE value ranges, and
clears the returned context pointers before returning an invalid view-index
entry error. The matched-entry data exposure and validation are moved into
a small helper, ntfs_index_lookup_set_data(), so the done label stays flat
and the bounds check has a single return.
Patch 2 fixes ntfs_mark_quotas_out_of_date() to look up $Quota/$Q by the
$Q index name, not $I30.
Patch 3 calls ntfs_mark_quotas_out_of_date() during initial read-write mount
as well as the existing read-only to read-write remount path. The init
sequence is wrapped in a small helper, ntfs_init_quota_for_mount(), which
captures each call result in a local variable, derives the failure reason,
and gates the read-only downgrade on policy in one place — keeping
ntfs_load_system_files() flat.
QEMU/virtme testing:
- The original remount-rw reproducer failed with:
"Lookup of quota defaults entry failed."
- After changing only the quota index name, the same path exposed the second
bug and failed with:
"Quota defaults entry size is invalid. Run chkdsk."
- With this series applied:
- read-only mount followed by remount,rw succeeds
- with the $Quota/$Q defaults flags prepared as 0x00000011, remount,rw
persists 0x00000211
- direct rw mount with the same prepared image also persists 0x00000211
DaeMyung Kang (3):
ntfs: return view index entry data from lookup
ntfs: use $Q when marking quotas out of date
ntfs: mark quotas out of date on initial rw mount
fs/ntfs/index.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
fs/ntfs/index.h | 3 +-
fs/ntfs/quota.c | 3 +-
fs/ntfs/super.c | 46 ++++++++++++++++++++++++++--------
4 files changed, 110 insertions(+), 17 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 1/3] ntfs: return view index entry data from lookup
2026-05-11 16:06 [PATCH 0/3] ntfs: fix quota out-of-date marking DaeMyung Kang
@ 2026-05-11 16:06 ` DaeMyung Kang
2026-05-11 16:06 ` [PATCH 2/3] ntfs: use $Q when marking quotas out of date DaeMyung Kang
2026-05-11 16:06 ` [PATCH 3/3] ntfs: mark quotas out of date on initial rw mount DaeMyung Kang
2 siblings, 0 replies; 4+ messages in thread
From: DaeMyung Kang @ 2026-05-11 16:06 UTC (permalink / raw)
To: linkinjeon, hyc.lee; +Cc: linux-fsdevel, linux-kernel, DaeMyung Kang
ntfs_index_lookup() always exposes the matched entry key through
icx->data. That is correct for file name indexes, where $I30 callers
expect the key to be a struct file_name_attr and update it in place.
NTFS view indexes store a separate value in index_entry.data.vi. For
example, $Quota/$Q uses the owner_id as the key and stores the
quota_control_entry in the entry value. Returning the key makes quota code
see only the 4-byte owner_id instead of the quota control entry.
Preserve the existing $FILE_NAME index behavior, but return the entry value
for view indexes. Use the index root type for that layout decision:
indexes with ir->type == AT_FILE_NAME expose the entry key, while view
indexes have type 0 and expose the entry value through data.vi. Validate
the value offset and length before exposing the pointer, and clear
icx->data when the key is not found because the context then describes an
insertion point rather than a matched value. Use unsigned arithmetic for
the minimum data offset check, account for the VCN stored at the end of
INDEX_ENTRY_NODE entries, and clear the returned context pointers before
returning an entry-data validation error.
Move the matched-entry data exposure and validation into a small helper,
ntfs_index_lookup_set_data(), so the done label stays flat and the bounds
check has a single return.
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
fs/ntfs/index.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
fs/ntfs/index.h | 3 +-
2 files changed, 73 insertions(+), 5 deletions(-)
diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c
index a547bdc..30dfcbb 100644
--- a/fs/ntfs/index.c
+++ b/fs/ntfs/index.c
@@ -696,6 +696,53 @@ static int ntfs_icx_parent_dec(struct ntfs_index_context *icx)
}
/*
+ * ntfs_index_lookup_set_data - populate icx->data for the matched entry
+ *
+ * For directory $FILE_NAME indexes the caller updates the key in place, so
+ * expose the key. For view indexes the value lives at data.vi; validate
+ * its offset and length before exposing the pointer. Return 0 on success,
+ * or -EIO if the entry value is out of bounds.
+ */
+static int ntfs_index_lookup_set_data(struct ntfs_index_context *icx,
+ struct index_root *ir,
+ struct index_entry *ie)
+{
+ u16 data_len, data_off, entry_len, found_key_len;
+ unsigned int data_end, min_data_off;
+
+ if (ie->flags & INDEX_ENTRY_END)
+ return -EIO;
+
+ found_key_len = le16_to_cpu(ie->key_length);
+ if (ir->type == AT_FILE_NAME) {
+ icx->data = (u8 *)ie + offsetof(struct index_entry, key);
+ icx->data_len = found_key_len;
+ return 0;
+ }
+
+ entry_len = le16_to_cpu(ie->length);
+ data_off = le16_to_cpu(ie->data.vi.data_offset);
+ data_len = le16_to_cpu(ie->data.vi.data_length);
+ data_end = entry_len;
+ if (ie->flags & INDEX_ENTRY_NODE) {
+ /* The trailing child VCN is not part of the entry value. */
+ if (data_end < sizeof(s64))
+ return -EIO;
+ data_end -= sizeof(s64);
+ }
+
+ min_data_off = offsetof(struct index_entry, key) + found_key_len;
+ if (data_off < min_data_off || data_off > data_end)
+ return -EIO;
+ if (data_len > data_end - data_off)
+ return -EIO;
+
+ icx->data = (u8 *)ie + data_off;
+ icx->data_len = data_len;
+ return 0;
+}
+
+/*
* ntfs_index_lookup - find a key in an index and return its index entry
* @key: key for which to search in the index
* @key_len: length of @key in bytes
@@ -710,7 +757,8 @@ static int ntfs_icx_parent_dec(struct ntfs_index_context *icx)
* If the @key is found in the index, 0 is returned and @icx is setup to
* describe the index entry containing the matching @key. @icx->entry is the
* index entry and @icx->data and @icx->data_len are the index entry data and
- * its length in bytes, respectively.
+ * its length in bytes, respectively. For file name indexes @icx->data points
+ * to the entry key; for view indexes @icx->data points to the entry value.
*
* If the @key is not found in the index, -ENOENT is returned and
* @icx is setup to describe the index entry whose key collates immediately
@@ -836,11 +884,30 @@ err_out:
return err;
done:
icx->entry = ie;
- icx->data = (u8 *)ie + offsetof(struct index_entry, key);
- icx->data_len = le16_to_cpu(ie->key_length);
+ if (err) {
+ /* When the key is not found, ie is an insertion point. */
+ icx->data = NULL;
+ icx->data_len = 0;
+ ntfs_debug("Done.\n");
+ return err;
+ }
+ err = ntfs_index_lookup_set_data(icx, ir, ie);
+ if (err) {
+ ntfs_error(sb,
+ "Index entry data out of bounds in inode 0x%llx.",
+ (unsigned long long)ni->mft_no);
+ icx->entry = NULL;
+ icx->data = NULL;
+ icx->data_len = 0;
+ if (!icx->is_in_root) {
+ kvfree(ib);
+ ib = NULL;
+ icx->ib = NULL;
+ }
+ goto err_out;
+ }
ntfs_debug("Done.\n");
return err;
-
}
static struct index_block *ntfs_ib_alloc(s64 ib_vcn, u32 ib_size,
diff --git a/fs/ntfs/index.h b/fs/ntfs/index.h
index e68d6fa..4b8c5c8 100644
--- a/fs/ntfs/index.h
+++ b/fs/ntfs/index.h
@@ -58,7 +58,8 @@
* To obtain a context call ntfs_index_ctx_get().
*
* We use this context to allow ntfs_index_lookup() to return the found index
* @entry and its @data without having to allocate a buffer and copy the @entry
- * and/or its @data into it.
+ * and/or its @data into it. For file name indexes @data points to the entry
+ * key; for view indexes @data points to the entry value.
*
* When finished with the @entry and its @data, call ntfs_index_ctx_put() to
* free the context and other associated resources.
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 2/3] ntfs: use $Q when marking quotas out of date
2026-05-11 16:06 [PATCH 0/3] ntfs: fix quota out-of-date marking DaeMyung Kang
2026-05-11 16:06 ` [PATCH 1/3] ntfs: return view index entry data from lookup DaeMyung Kang
@ 2026-05-11 16:06 ` DaeMyung Kang
2026-05-11 16:06 ` [PATCH 3/3] ntfs: mark quotas out of date on initial rw mount DaeMyung Kang
2 siblings, 0 replies; 4+ messages in thread
From: DaeMyung Kang @ 2026-05-11 16:06 UTC (permalink / raw)
To: linkinjeon, hyc.lee; +Cc: linux-fsdevel, linux-kernel, DaeMyung Kang
ntfs_mark_quotas_out_of_date() operates on vol->quota_q_ino, which is the
$Quota/$Q index inode opened by load_and_init_quota(). However, it creates
the index context with the $I30 name.
That asks the $Quota file for an $INDEX_ROOT named $I30 and fails before
the quota defaults entry can be looked up. Use the $Q index name when
marking quotas out of date.
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
fs/ntfs/quota.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/fs/ntfs/quota.c b/fs/ntfs/quota.c
index b443243..7086951 100644
--- a/fs/ntfs/quota.c
+++ b/fs/ntfs/quota.c
@@ -21,6 +21,7 @@ bool ntfs_mark_quotas_out_of_date(struct ntfs_volume *vol)
{
struct ntfs_index_context *ictx;
struct quota_control_entry *qce;
+ static __le16 Q[3] = { cpu_to_le16('$'), cpu_to_le16('Q'), 0 };
const __le32 qid = QUOTA_DEFAULTS_ID;
int err;
@@ -32,7 +33,7 @@ bool ntfs_mark_quotas_out_of_date(struct ntfs_volume *vol)
return false;
}
inode_lock(vol->quota_q_ino);
- ictx = ntfs_index_ctx_get(NTFS_I(vol->quota_q_ino), I30, 4);
+ ictx = ntfs_index_ctx_get(NTFS_I(vol->quota_q_ino), Q, 2);
if (!ictx) {
ntfs_error(vol->sb, "Failed to get index context.");
goto err_out;
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 3/3] ntfs: mark quotas out of date on initial rw mount
2026-05-11 16:06 [PATCH 0/3] ntfs: fix quota out-of-date marking DaeMyung Kang
2026-05-11 16:06 ` [PATCH 1/3] ntfs: return view index entry data from lookup DaeMyung Kang
2026-05-11 16:06 ` [PATCH 2/3] ntfs: use $Q when marking quotas out of date DaeMyung Kang
@ 2026-05-11 16:06 ` DaeMyung Kang
2 siblings, 0 replies; 4+ messages in thread
From: DaeMyung Kang @ 2026-05-11 16:06 UTC (permalink / raw)
To: linkinjeon, hyc.lee; +Cc: linux-fsdevel, linux-kernel, DaeMyung Kang
The remount read-write path marks quotas out of date after emptying the
logfile, but initial read-write mount only loads $Quota and never calls
ntfs_mark_quotas_out_of_date().
That leaves quota tracking metadata looking up to date even though the
driver can modify the volume. Call the same helper after $Quota is loaded
during initial read-write mount. If marking quotas out of date fails and
the mount policy is on_errors=remount-ro, convert the mount to read-only
and set the volume error state, matching the nearby $Quota load failure
handling.
This intentionally follows the initial mount error policy rather than the
remount-rw path, where a quota marking failure rejects the remount with
-EROFS.
Move the load/mark/policy handling into a small helper,
ntfs_init_quota_for_mount(), which captures each call result in a local,
derives the failure reason, and gates the read-only downgrade on policy
in one place. This avoids calling helpers inside compound if conditions
and keeps ntfs_load_system_files() flat.
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
fs/ntfs/super.c | 46 +++++++++++++++++++++++++++++++++--------------
1 file changed, 35 insertions(+), 11 deletions(-)
diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c
index 22dc786..8fa298f 100644
--- a/fs/ntfs/super.c
+++ b/fs/ntfs/super.c
@@ -1221,6 +1221,39 @@ static bool load_and_init_quota(struct ntfs_volume *vol)
return true;
}
+/*
+ * ntfs_init_quota_for_mount - finish quota setup at mount time
+ * @sb: super block of the volume being mounted
+ * @vol: ntfs volume to set up
+ *
+ * Load $Quota and, on a read-write mount, mark quotas out of date so that
+ * Windows rescans them on next boot. On failure, downgrade the mount to
+ * read-only when on_errors=remount-ro, matching the volume error policy.
+ */
+static void ntfs_init_quota_for_mount(struct super_block *sb,
+ struct ntfs_volume *vol)
+{
+ bool quota_loaded, quota_marked = true;
+ const char *reason = NULL;
+
+ quota_loaded = load_and_init_quota(vol);
+ if (quota_loaded && !sb_rdonly(sb))
+ quota_marked = ntfs_mark_quotas_out_of_date(vol);
+
+ if (!quota_loaded)
+ reason = "Failed to load $Quota";
+ else if (!quota_marked)
+ reason = "Failed to mark quotas out of date";
+
+ if (!reason || vol->on_errors != ON_ERRORS_REMOUNT_RO)
+ return;
+
+ sb->s_flags |= SB_RDONLY;
+ ntfs_error(sb, "%s. Mounting read-only. Run chkdsk.", reason);
+ /* This will prevent a read-write remount. */
+ NVolSetErrors(vol);
+}
+
/*
* load_and_init_attrdef - load the attribute definitions table for a volume
* @vol: ntfs super block describing device whose attrdef to load
@@ -1638,16 +1671,7 @@ get_ctx_vol_failed:
ntfs_error(sb, "Failed to load $Extend.");
goto iput_sec_err_out;
}
- /* Find the quota file, load it if present, and set it up. */
- if (!load_and_init_quota(vol) &&
- vol->on_errors == ON_ERRORS_REMOUNT_RO) {
- static const char *es1 = "Failed to load $Quota";
- static const char *es2 = ". Run chkdsk.";
-
- sb->s_flags |= SB_RDONLY;
- ntfs_error(sb, "%s. Mounting read-only%s", es1, es2);
- /* This will prevent a read-write remount. */
- NVolSetErrors(vol);
- }
+ /* Find the quota file, load it if present, and set it up. */
+ ntfs_init_quota_for_mount(sb, vol);
return true;
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-05-11 16:06 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-11 16:06 [PATCH 0/3] ntfs: fix quota out-of-date marking DaeMyung Kang
2026-05-11 16:06 ` [PATCH 1/3] ntfs: return view index entry data from lookup DaeMyung Kang
2026-05-11 16:06 ` [PATCH 2/3] ntfs: use $Q when marking quotas out of date DaeMyung Kang
2026-05-11 16:06 ` [PATCH 3/3] ntfs: mark quotas out of date on initial rw mount DaeMyung Kang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox