From: Michael Bommarito <michael.bommarito@gmail.com>
To: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>,
ntfs3@lists.linux.dev
Cc: Mihai Brodschi <m.brodschi@gmail.com>,
linux-fsdevel@vger.kernel.org, stable@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: [PATCH v2] fs/ntfs3: reject an oversized resident attribute on the inline iomap path
Date: Sat, 11 Jul 2026 11:26:30 -0400 [thread overview]
Message-ID: <20260711152630.2975127-1-michael.bommarito@gmail.com> (raw)
attr_data_get_block_locked() maps a resident attribute as a one-page
IOMAP_INLINE extent: it allocates a single page, copies the resident
value into it with memcpy(), and hands that page to ntfs_iomap_begin()
as the inline data. The copy length is the on-disk resident value
length (res.data_size), and mi_enum_attr() only bounds that length
against the MFT record size. A corrupted volume with MFT records larger
than a page can therefore present a resident $DATA whose length exceeds
PAGE_SIZE, and the memcpy() then writes past the single destination page.
Impact: reading or writing a resident file on a mounted crafted NTFS
volume whose MFT records are larger than a page overflows the one-page
inline buffer in attr_data_get_block_locked() with attacker-controlled
bytes.
Reject such an attribute in attr_data_get_block_locked(), before the
page is allocated and the copy is made.
Fixes: 099ef9ab9203 ("fs/ntfs3: implement iomap-based file operations")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
Changes since v1:
- Reworded the code comment and changelog. The oversized resident length
overflows the single alloc_page() page that attr_data_get_block_locked()
copies the resident value into; v1 described the older
iomap_write_end_inline() BUG_ON(!iomap_inline_data_valid()), which was
removed in 7.2-rc1. Thanks to Mihai Brodschi for catching the stale
reference. The fix itself is unchanged.
The unbounded resident length dates to the iomap conversion; only the
manifestation changed when the inline buffer became a single alloc_page():
- 7.2-rc: the memcpy() into the one-page buffer overflows the page
(out-of-bounds write, below).
- 7.0..7.1: the buffer was kmemdup()ed at the real size and the oversized
length instead tripped BUG_ON(!iomap_inline_data_valid()) in
iomap_write_end_inline() (a denial of service).
The same data_size > PAGE_SIZE check prevents both, so it is the correct fix
across the stable range.
Reproduced under QEMU + KASAN on 7.2-rc2. A resident $DATA value length
above PAGE_SIZE, as a volume with MFT records larger than a page presents,
was supplied while the alloc_page() + memcpy() path ran unchanged; the stock
kernel faults with a KASAN out-of-bounds write in
attr_data_get_block_locked():
BUG: KASAN: out-of-bounds in attr_data_get_block_locked
Write of size <data_size> by task ...
__asan_memcpy
attr_data_get_block_locked
attr_data_get_block
ntfs_iomap_begin
iomap_iter
iomap_file_buffered_write
With the patch the same access returns -EINVAL and does not fault. An
in-bounds resident file (control) and a non-resident file are unaffected on
both the stock and patched kernels.
fs/ntfs3/attrib.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c
index c621a4c582f9e..2f73714a2db97 100644
--- a/fs/ntfs3/attrib.c
+++ b/fs/ntfs3/attrib.c
@@ -1034,6 +1034,21 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
if (!attr_b->non_res) {
u32 data_size = le32_to_cpu(attr_b->res.data_size);
+
+ /*
+ * A resident attribute is copied into a single page below
+ * (alloc_page() + memcpy()) and mapped as a one-page
+ * IOMAP_INLINE extent by ntfs_iomap_begin(). mi_enum_attr()
+ * only bounds the resident value length against the MFT record
+ * size, so a corrupted volume whose records are larger than a
+ * page can report data_size > PAGE_SIZE; copying that many bytes
+ * would overflow the single destination page. Reject it.
+ */
+ if (data_size > PAGE_SIZE) {
+ err = -EINVAL;
+ goto out;
+ }
+
*lcn = RESIDENT_LCN;
*len = data_size;
if (res && data_size) {
--
2.53.0
reply other threads:[~2026-07-11 15:26 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
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=20260711152630.2975127-1-michael.bommarito@gmail.com \
--to=michael.bommarito@gmail.com \
--cc=almaz.alexandrovich@paragon-software.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=m.brodschi@gmail.com \
--cc=ntfs3@lists.linux.dev \
--cc=stable@vger.kernel.org \
/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