From: Giulia Aloia <giulia@bynar.io>
To: almaz.alexandrovich@paragon-software.com
Cc: ntfs3@lists.linux.dev, linux-kernel@vger.kernel.org
Subject: [PATCH 1/2] fs/ntfs3: fix out-of-bounds read of redo/undo data during log replay
Date: Fri, 25 Sep 2026 13:16:13 +0200 [thread overview]
Message-ID: <20260925111619.68345-2-giulia@bynar.io> (raw)
In-Reply-To: <20260925111619.68345-1-giulia@bynar.io>
check_log_rec() is the common validator for log records consumed during
journal replay. It checks the header size, the transaction table offset,
the 8-byte alignment of redo_off and undo_off, and that the record holds
at least lrh_length(lr) bytes. It does not check that the redo and undo
data ranges themselves lie inside the record.
log_replay() builds those ranges directly from on-disk fields:
data = Add2Ptr(lrh, le16_to_cpu(lrh->redo_off));
dlen = le16_to_cpu(lrh->redo_len);
...
err = do_action(log, oe, lrh, t16, data, dlen, rec_len, &rec_lsn);
do_action() bounds the destination in its copy cases, for example:
case InitializeFileRecordSegment:
if (roff + dlen > record_size)
goto dirty_vol;
memcpy(Add2Ptr(rec, roff), data, dlen);
but it does not bound the source range. The undo pass has the same issue
for normal undo records, and the analysis pass reads DeleteDirtyClusters
LCN ranges and OpenNonresidentAttribute entries and optional names from
unchecked redo/undo ranges.
redo_off, redo_len, undo_off and undo_len are 16-bit fields read from
the on-disk log. A crafted dirty log record can therefore make journal
replay read past the end of the log record while applying redo or undo
data. In the copy cases, the out-of-bounds bytes are copied into the
in-memory file record being replayed.
This is independent of target_attr and restart-table offset validation:
even with a valid open-attribute entry and valid destination bounds,
do_action() can still over-read the log record because redo_off/redo_len
and undo_off/undo_len are not bounded against client_data_len.
Mounting needs CAP_SYS_ADMIN, since ntfs3 is FS_REQUIRES_DEV and not
FS_USERNS_MOUNT, but the image is untrusted wherever removable media are
auto-mounted onto the in-tree driver; replay runs on any rw mount of a
volume whose log is dirty.
Before this fix, mounting the crafted image on an x86-64 KASAN build
produced:
BUG: KASAN: slab-out-of-bounds in do_action+0x2820/0x89e0
Read of size 1024 at addr ffff888102404f30 by task mount/67
Call Trace:
__asan_memcpy+0x23/0x60
do_action+0x2820/0x89e0
log_replay+0xcd38/0xe690
ntfs_loadlog_and_replay+0x3e0/0x500
ntfs_fill_super+0x1fd3/0x4510
get_tree_bdev_flags+0x2ff/0x5d0
vfs_get_tree+0x80/0x2d0
fc_mount+0x15/0x1f0
path_mount+0x77e/0x1d70
__x64_sys_mount+0x207/0x270
do_syscall_64+0xde/0x4b0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Allocated by task 67:
__kmalloc_noprof+0x1cc/0x480
read_log_page+0x354/0x5a0
find_log_rec+0x383/0x5f0
read_log_rec_lcb+0x1c7/0x550
log_replay+0xc85a/0xe690
ntfs_loadlog_and_replay+0x3e0/0x500
ntfs_fill_super+0x1fd3/0x4510
get_tree_bdev_flags+0x2ff/0x5d0
vfs_get_tree+0x80/0x2d0
fc_mount+0x15/0x1f0
path_mount+0x77e/0x1d70
__x64_sys_mount+0x207/0x270
The buggy address is located 3888 bytes inside of
allocated 4096-byte region [ffff888102404000, ffff888102405000)
Reject redo ranges that extend beyond client_data_len. Also reject undo
ranges that extend beyond client_data_len, except for compensation log
records, which can have undo_len set even when no undo bytes are present.
OpenNonresidentAttribute also reads a fixed-size open-attribute entry
from redo_off; validate that the entry is contained in the log record
before reading it. Because check_log_rec() intentionally leaves the CLR
undo range unchecked, also validate the undo range locally before copying
the attribute name from it.
Every caller already turns a false return into -EINVAL and aborts the
replay.
Fixes: b46acd6a6a62 ("fs/ntfs3: Add NTFS journal")
Cc: stable@vger.kernel.org
Assisted-by: Bynario AI
Signed-off-by: Giulia Aloia <giulia@bynar.io>
---
fs/ntfs3/fslog.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c
index ed50c1d0c23e..88bd6ef98467 100644
--- a/fs/ntfs3/fslog.c
+++ b/fs/ntfs3/fslog.c
@@ -694,6 +694,7 @@ static bool check_log_rec(const struct LOG_REC_HDR *lr, u32 bytes, u32 tr,
u32 bytes_per_attr_entry)
{
u16 t16;
+ u32 off, len;
if (bytes < sizeof(struct LOG_REC_HDR))
return false;
@@ -731,6 +732,17 @@ static bool check_log_rec(const struct LOG_REC_HDR *lr, u32 bytes, u32 tr,
if (bytes < lrh_length(lr))
return false;
+ off = le16_to_cpu(lr->redo_off);
+ len = le16_to_cpu(lr->redo_len);
+ if (off > bytes || len > bytes - off)
+ return false;
+
+ off = le16_to_cpu(lr->undo_off);
+ len = le16_to_cpu(lr->undo_len);
+ if (lr->undo_op != cpu_to_le16(CompensationLogRecord) &&
+ (off > bytes || len > bytes - off))
+ return false;
+
return true;
}
@@ -4736,6 +4748,25 @@ int log_replay(struct ntfs_inode *ni, bool *initialized)
}
case OpenNonresidentAttribute:
+ t32 = !rst->major_ver ? SIZEOF_OPENATTRIBUTEENTRY0 :
+ bytes_per_attr_entry;
+ off = le16_to_cpu(lrh->redo_off);
+ if (off > rec_len || t32 > rec_len - off) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * The attribute name is copied from the undo range, which
+ * check_log_rec() does not bound for compensation log records.
+ */
+ off = le16_to_cpu(lrh->undo_off);
+ t32 = le16_to_cpu(lrh->undo_len);
+ if (t32 && (off > rec_len || t32 > rec_len - off)) {
+ err = -EINVAL;
+ goto out;
+ }
+
t16 = le16_to_cpu(lrh->target_attr);
if (t16 >= bytes_per_rt(oatbl)) {
/*
--
2.55.0
next prev parent reply other threads:[~2026-09-25 11:16 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-25 11:16 [PATCH 0/2] fs/ntfs3: fix log replay payload overreads Giulia Aloia
2026-09-25 11:16 ` Giulia Aloia [this message]
2026-09-25 11:16 ` [PATCH 2/2] fs/ntfs3: validate SetNewAttributeSizes payload length Giulia Aloia
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=20260925111619.68345-2-giulia@bynar.io \
--to=giulia@bynar.io \
--cc=almaz.alexandrovich@paragon-software.com \
--cc=linux-kernel@vger.kernel.org \
--cc=ntfs3@lists.linux.dev \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®