[PATCH] fs/ntfs3: bound asize by SIZEOF_NONRESIDENT in check_attr()

From: Zhan Xusheng

Date: Fri Sep 04 2026 - 03:36:39 EST


check_attr() validates a file record attribute during log replay. Its
non-resident branch reads through attr->nres.valid_size, which ends
SIZEOF_NONRESIDENT bytes into the attribute, but only checks that the
attribute fits the record, not that it is long enough to hold those
fields. check_file_record() admits an attribute starting as late as
record_size - SIZEOF_RESIDENT, so one placed there with non_res set reads
up to 40 bytes past mi->mrec, a kmalloc(record_size) buffer. The later
"run_off >= asize" rejection is too late: data_size, svcn, evcn and
valid_size have already been read. Nothing earlier covers it, mi_read()
checks only rec->total, and do_action() gets here for the ordinary
record-modifying operations, so a crafted volume reaches it through
normal replay.

Reject an attribute that leaves fewer than SIZEOF_NONRESIDENT bytes in the
record, as mi_enum_attr() already does outside the log path. A real
non-resident attribute cannot be smaller than its own header, so nothing
that mounts today is rejected.

Fixes: b46acd6a6a62 ("fs/ntfs3: Add NTFS journal")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Zhan Xusheng <zhanxusheng@xxxxxxxxxx>
---
Reproduced with ASAN in a userspace replica of the layout and of
check_attr()'s bounds: an attribute at record_size - SIZEOF_RESIDENT
gives "READ of size 8 ... located 24 bytes after 1024-byte region",
and is rejected once the guard is added.

I did not craft a volume, so there is no mount-level reproducer and no
KASAN splat from a real replay; the reachability above comes from
reading do_action(), mi_read() and ntfs_fix_post_read().

fs/ntfs3/fslog.c | 3 +++
1 file changed, 3 insertions(+)

diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c
index ed50c1d0c23e..034d72929ac0 100644
--- a/fs/ntfs3/fslog.c
+++ b/fs/ntfs3/fslog.c
@@ -2772,6 +2772,9 @@ static inline bool check_attr(const struct MFT_REC *rec,
break;

case 1:
+ if (asize < SIZEOF_NONRESIDENT)
+ return false;
+
dsize = le64_to_cpu(attr->nres.data_size);
svcn = le64_to_cpu(attr->nres.svcn);
evcn = le64_to_cpu(attr->nres.evcn);

base-commit: a500db7819c50db59e55f1b4fa1c3baa5a2616f3
--
2.43.0