From: DaeMyung Kang <charsyam@gmail.com>
To: Namjae Jeon <linkinjeon@kernel.org>, Hyunchul Lee <hyc.lee@gmail.com>
Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH] ntfs: fix out-of-bounds write in ntfs_rl_collapse_range() merge path
Date: Tue, 5 May 2026 00:48:51 +0900 [thread overview]
Message-ID: <20260504154851.3034915-1-charsyam@gmail.com> (raw)
ntfs_rl_collapse_range() merges the run on the left of the collapsed
region with the run on its right when both are holes. The contiguous
check correctly clamps the index to 1 when @new_1st_cnt is 0:
i = new_1st_cnt == 0 ? 1 : new_1st_cnt;
if (ntfs_rle_lcn_contiguous(&new_rl[i - 1], &new_rl[i])) {
but the merge itself uses the unclamped value:
s_rl = &new_rl[new_1st_cnt - 1];
s_rl->length += s_rl[1].length;
When @new_1st_cnt is 0 this computes &new_rl[-1] and writes 8 bytes
before the kvcalloc'd runlist buffer. The path is reachable through
fallocate(FALLOC_FL_COLLAPSE_RANGE) starting at vcn 0 against an
attribute whose first run after the collapsed region is a hole. In that
case ntfs_rle_lcn_contiguous() returns true because both sides are
LCN_HOLE, so the merge path is entered with @new_1st_cnt still 0.
Use the same clamped index for the merge as for the contiguous check so
the write always lands inside the buffer.
The out-of-bounds write can corrupt an adjacent slab object. On a
non-KASAN kernel, it is reachable after a crafted NTFS volume has been
mounted read-write with the legacy fs/ntfs driver, by a local user that
has write access to the crafted file.
Fixes: 11ccc9107dc4 ("ntfs: update runlist handling and cluster allocator")
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
Trigger conditions / reproducer notes:
- subsystem: fs/ntfs (legacy NTFS runlist collapse path)
- introduced by: 11ccc9107dc4 ("ntfs: update runlist handling and
cluster allocator"), first released in v7.1-rc1
- affected kernels: v7.1-rc1 through current mainline; also present
in any branch that includes 11ccc9107dc4. Pre-v7.1-rc1 kernels do
not contain this collapse-range code path.
- local: yes
- remote: no direct remote trigger
- authentication required: no
- special privileges required: yes to mount the crafted NTFS volume
read-write in the normal case. After a privileged read-write mount,
no additional privilege is needed for the fallocate trigger if the
caller has write permission on the crafted file.
- filesystem requirement: the legacy fs/ntfs driver must be mounted
read-write against a crafted NTFS volume.
- repeatable: yes, the crafted runlist geometry and collapse range make
the sequence deterministic.
Reproducer summary used during verification:
1. Prepare an NTFS volume containing a file whose runlist makes
@new_1st_cnt equal to 0 and places a hole as the first run after
the collapsed region.
2. Mount the volume read-write with the legacy fs/ntfs driver.
3. Call fallocate(FALLOC_FL_COLLAPSE_RANGE) on the crafted file,
starting at file offset 0.
4. Repeat with different crafted runlist sizes to place the temporary
runlist allocation in different kmalloc caches.
Observed result without this fix:
- KASAN reports a slab out-of-bounds write in
ntfs_rl_collapse_range().
- The write updates the runlist element immediately before the
kvcalloc'd temporary runlist buffer.
- On a non-KASAN kernel, the same trigger corrupts adjacent kmalloc
objects.
- The crafted runlist geometry can steer the temporary runlist
allocation into different kmalloc caches.
- Impact: local kernel heap corruption, reachable only after a
privileged read-write mount of a crafted NTFS volume. Practical
impact in the field is bounded by that mount requirement.
Verification with the fix applied:
- "make M=fs/ntfs modules" succeeds.
- The KASAN QEMU reproducer no longer reports an out-of-bounds write.
- The non-KASAN QEMU observer object is no longer corrupted.
fs/ntfs/runlist.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c
index da21dbeaaf66..e09bc86a7276 100644
--- a/fs/ntfs/runlist.c
+++ b/fs/ntfs/runlist.c
@@ -2058,8 +2058,12 @@ struct runlist_element *ntfs_rl_collapse_range(struct runlist_element *dst_rl, i
merge_cnt = 0;
i = new_1st_cnt == 0 ? 1 : new_1st_cnt;
if (ntfs_rle_lcn_contiguous(&new_rl[i - 1], &new_rl[i])) {
- /* Merge right and left */
- s_rl = &new_rl[new_1st_cnt - 1];
+ /* Merge right and left.
+ *
+ * Use the clamped @i; new_1st_cnt - 1 would index
+ * new_rl[-1] when @new_1st_cnt == 0.
+ */
+ s_rl = &new_rl[i - 1];
s_rl->length += s_rl[1].length;
merge_cnt = 1;
}
--
2.43.0
next reply other threads:[~2026-05-04 15:48 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-04 15:48 DaeMyung Kang [this message]
2026-05-06 5:48 ` [PATCH] ntfs: fix out-of-bounds write in ntfs_rl_collapse_range() merge path Hyunchul Lee
2026-05-06 9:24 ` [PATCH v2] " DaeMyung Kang
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=20260504154851.3034915-1-charsyam@gmail.com \
--to=charsyam@gmail.com \
--cc=hyc.lee@gmail.com \
--cc=linkinjeon@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@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