summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXiang Mei <xmei5@asu.edu>2026-06-17 16:13:42 -0700
committerKonstantin Komarov <almaz.alexandrovich@paragon-software.com>2026-07-29 13:04:50 +0200
commit006cb7713dec10368e699abc4367e5faa334c9a5 (patch)
treec898e304c9f3e5afae80010fd9bb1a7a93aa5f8c
parent111f8d74a19d85942ecbb3aba78f6f3c88e59391 (diff)
downloadlinux-next-006cb7713dec10368e699abc4367e5faa334c9a5.tar.gz
linux-next-006cb7713dec10368e699abc4367e5faa334c9a5.zip
fs/ntfs3: validate dirty page table on log replay
Each DIR_PAGE_ENTRY ends in a page_lcns[] array whose length is the on-disk lcns_follow field. check_rstbl() validates the table bookkeeping but never checks that this array fits in the entry, so a crafted lcns_follow lets the v0->v1 conversion memmove and later replay passes run off the entry. Add check_dp_table() to reject, right after check_rstbl(), any entry larger than its size claims via struct_size() (the same expression used to allocate these entries, so the check is overflow-safe by construction). All consumers can then trust lcns_follow as the real capacity. This covers every page_lcns[] access whose index is bounded by the entry itself (the conversion memmove, the HotFix store via find_dp(), and the self-bounded scan loops). Accesses whose index comes from the log record need a separate bound and are handled in a follow-up patch. Fixes: b46acd6a6a62 ("fs/ntfs3: Add NTFS journal") Cc: stable@vger.kernel.org Reported-by: Weiming Shi <bestswngs@gmail.com> Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei <xmei5@asu.edu> Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
-rw-r--r--fs/ntfs3/fslog.c19
1 files changed, 19 insertions, 0 deletions
diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c
index ab210c33f770..db5eec0aab61 100644
--- a/fs/ntfs3/fslog.c
+++ b/fs/ntfs3/fslog.c
@@ -789,6 +789,20 @@ static bool check_rstbl(const struct RESTART_TABLE *rt, size_t bytes)
return true;
}
+static bool check_dp_table(const struct RESTART_TABLE *dptbl)
+{
+ u32 rsize = le16_to_cpu(dptbl->size);
+ struct DIR_PAGE_ENTRY *dp = NULL;
+
+ while ((dp = enum_rstbl((struct RESTART_TABLE *)dptbl, dp))) {
+ if (struct_size(dp, page_lcns, le32_to_cpu(dp->lcns_follow)) >
+ rsize)
+ return false;
+ }
+
+ return true;
+}
+
/*
* free_rsttbl_idx - Free a previously allocated index a Restart Table.
*/
@@ -4293,6 +4307,11 @@ check_dirty_page_table:
goto out;
}
+ if (!check_dp_table(rt)) {
+ err = -EINVAL;
+ goto out;
+ }
+
dptbl = kmemdup(rt, t32, GFP_NOFS);
if (!dptbl) {
err = -ENOMEM;