mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Shawn <shawnq1@163.com>
To: almaz.alexandrovich@paragon-software.com
Cc: ntfs3@lists.linux.dev, linux-kernel@vger.kernel.org
Subject: [PATCH] fs/ntfs3: reject short restart table entries
Date: Wed, 9 Sep 2026 21:33:58 +0800 (CST)	[thread overview]
Message-ID: <72bf4f2c.9eb3.1a08660120d.Coremail.shawnq1@163.com> (raw)

Hello,

check_rstbl() in fs/ntfs3/fslog.c accepts a restart-table entry size smaller
than the four bytes it unconditionally reads for every entry.  A crafted NTFS
$LogFile can therefore cause a kernel heap out-of-bounds read while mounting a
read-only filesystem image.

This is a manually constructed reproducer and does not use syzkaller.

HEAD commit:    940de590b839f71d6dc846160534bf202401b8b7
git tree:       upstream Linux checkout, local branch master
source version: Linux 7.3.0-rc1-00101-g940de590b839
compiler:       gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3), GNU ld 2.38

console output:
https://gist.githubusercontent.com/shaos26/a7382d96d2a86c51c279fd994d9a2a1c/raw/6dce660d99e244275dcc87ef79391e10ab66b9f3/console-output.txt
kernel config:
https://gist.githubusercontent.com/shaos26/a7382d96d2a86c51c279fd994d9a2a1c/raw/6dce660d99e244275dcc87ef79391e10ab66b9f3/kernel-config.txt
reproducer:
https://gist.githubusercontent.com/shaos26/a7382d96d2a86c51c279fd994d9a2a1c/raw/6dce660d99e244275dcc87ef79391e10ab66b9f3/poc.c

The experiment uses QEMU TCG only.  No hardware, kernel module, kprobe,
address leak, or modified kernel code is required for the trigger.

To reproduce, create a clean NTFS image on the host and apply the on-disk log
mutation with poc.c:

    cc -O2 -Wall -Wextra -o ntfs3_make_restart poc.c
    truncate -s 32M ntfs3-candidate-base.img
    mkntfs -F -Q ntfs3-candidate-base.img
    cp ntfs3-candidate-base.img ntfs3-candidate.img
    ./ntfs3_make_restart ntfs3-candidate.img --candidate

Boot the unpatched KASAN kernel in a disposable QEMU guest, pass
ntfs3-candidate.img as a read-only second virtio disk, and run:

    mount -t proc proc /proc
    mount -t tmpfs tmpfs /tmp
    mkdir -p /tmp/ntfs
    mount -o ro -t ntfs3 /dev/vdb /tmp/ntfs

Use the following QEMU command for the disposable guest:

    qemu-img create -f qcow2 -F raw \
      -b <buildroot-base-image> /tmp/ntfs3-repro-root.qcow2

    qemu-system-x86_64 -m 2G -smp 1 \
      -kernel <linux-tree>/arch/x86/boot/bzImage \
      -append 'quiet loglevel=1 acpi=off console=ttyS0 root=/dev/vda1 init=/bin/sh' \
      -drive file=/tmp/ntfs3-repro-root.qcow2,format=qcow2,if=virtio \
      -drive file=/tmp/ntfs3-candidate.img,format=raw,if=virtio,readonly=on \
      -nographic -monitor none -machine pc -no-reboot \
      -virtfs local,path=<research-root>,mount_tag=research,security_model=none

Replace <buildroot-base-image>, <linux-tree>, and <research-root> with local
paths.  The second virtio disk is /dev/vdb in the guest.

The vulnerable kernel reports:

    BUG: KASAN: slab-out-of-bounds in log_replay+0x6fc8/0xf290
    Read of size 4 at addr ffff88802a5cfec4 by task mount/4914
    Call Trace:
      log_replay
      ntfs_loadlog_and_replay
      ntfs_fill_super
      get_tree_bdev_flags
      vfs_get_tree
      path_mount
      __x64_sys_mount
    The buggy address belongs to the cache kmalloc-64 of size 64
    The buggy address is located 4 bytes to the right of
    allocated 64-byte region

The access is reached through the normal mount path:

    mount(2)
      -> ntfs_fill_super()
      -> ntfs_loadlog_and_replay()
      -> log_replay()
      -> read_log_rec_lcb()
      -> find_log_rec()
      -> check_rstbl()
      -> 4-byte out-of-bounds read

The crafted DPT restart table has these relevant fields:

    client_data_len = 0x40
    restart-table bytes = 0x20
    restart-table size = 1
    restart-table used = 8
    restart-table total = 8
    entries = 0xff x 8

check_rstbl() computes:

    ts = 1 * 8 + sizeof(struct RESTART_TABLE) = 0x20

The existing checks reject size == 0, but accept size == 1.  The loop then
advances by one byte while loading a __le32 from each entry.  For the final
entry, the four-byte load extends beyond the allocated kmalloc-64 object.

This is distinct from the existing restart-table growth and DPT-specific
page_lcns[] capacity checks: the generic check_rstbl() four-byte load happens
before those later validations.

Impact and privilege boundary:

This is a kernel heap out-of-bounds read caused by a malicious NTFS filesystem
image.  A read-only mount is sufficient; the image does not need to be
writable and no special hardware is required.  The current reproducer used
root/CAP_SYS_ADMIN in the disposable guest to attach the block image and mount
it.  An unprivileged direct mount path has not been established.  No privilege
escalation, information disclosure, or code execution has been demonstrated.

Reject restart-table entry sizes smaller than the width used by the validation
loop:

Fixes: b46acd6a6a62 ("fs/ntfs3: Add NTFS journal")
Cc: stable@vger.kernel.org
Signed-off-by: shaos <YOUR_EMAIL>
---
 fs/ntfs3/fslog.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c
index ed50c1d0c23e3..c24a0713b8599 100644
--- a/fs/ntfs3/fslog.c
+++ b/fs/ntfs3/fslog.c
@@ -745,7 +745,7 @@ static bool check_rstbl(const struct RESTART_TABLE *rt, size_t bytes)
 
    ts = rsize * ne + sizeof(struct RESTART_TABLE);
 
-   if (!rsize || rsize > bytes ||
+   if (rsize < sizeof(__le32) || rsize > bytes ||
        rsize + sizeof(struct RESTART_TABLE) > bytes || bytes < ts ||
        le16_to_cpu(rt->total) > ne || ff > ts - sizeof(__le32) ||
        lf > ts - sizeof(__le32) ||

Validation results:

    unfixed KASAN kernel:  1/1 trigger, KASAN slab-out-of-bounds
    unfixed stability:     20/20 mount/umount entries completed; one same-site KASAN report
    fixed KASAN kernel:     1/1 A/B run, mount_exit=0, no KASAN/OOB/BUG report

The 20/20 figure is the successful mount/umount entry count, not 20
independent KASAN reports; KASAN suppresses repeated reports from the same
site.

Thanks,

shaos

                 reply	other threads:[~2026-09-09 13:34 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=72bf4f2c.9eb3.1a08660120d.Coremail.shawnq1@163.com \
    --to=shawnq1@163.com \
    --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®