public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
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

             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