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 --- 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 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 --- 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 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 --- 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