* [PATCH] fs/ntfs3: validate dlen before copying filename dup in do_action()
@ 2026-11-13 16:46 Junyoung Jang
0 siblings, 0 replies; only message in thread
From: Junyoung Jang @ 2026-11-13 16:46 UTC (permalink / raw)
To: Konstantin Komarov; +Cc: Junyoung Jang, ntfs3, linux-kernel
In log_replay(), data and dlen are derived from the on-disk redo_off/len
and undo_off/len fields. When handling UpdateFileNameRoot and
UpdateFileNameAllocation in do_action(), the code unconditionally copies
sizeof(fname->dup) (56 bytes) from data:
memmove(&fname->dup, data, sizeof(fname->dup));
Neither handler checks that dlen is at least sizeof(fname->dup), and the
generic log record validation does not enforce this operation-specific
minimum.
A crafted journal record with client_data_len=0x98, redo_off=0x90,
redo_len=1, and the multi-page flag set passes the record range check
(0x90 + 1 <= 0x98), allocates a 152-byte buffer in find_log_rec(), and
triggers a 48-byte KASAN slab-out-of-bounds read past the end of that
buffer during a read-write mount:
BUG: KASAN: slab-out-of-bounds in do_action.isra.0+0x353f/0x83c0
Read of size 56 at addr ffff888007d9bc90 by task busybox/67
Call Trace:
<TASK>
__asan_memmove+0x23/0x60
do_action.isra.0+0x353f/0x83c0
find_log_rec+0x277/0x500
...
The buggy address belongs to the object at ffff888007d9bc00
allocated 152-byte region [ffff888007d9bc00, ffff888007d9bc98)
Validate that dlen is at least sizeof(fname->dup) before copying in both
UpdateFileNameRoot and UpdateFileNameAllocation, jumping to dirty_vol on
a short payload.
Fixes: b46acd6a6a62 ("fs/ntfs3: Add NTFS journal")
Cc: stable@vger.kernel.org
Signed-off-by: Junyoung Jang <graypanda.inzag@gmail.com>
---
Notes / Reproducer:
- Tested on Linux 7.4.0-rc2 (067c5c24cef2) x86-64 in QEMU/KVM with
CONFIG_NTFS3_FS=y, CONFIG_KASAN=y, CONFIG_KASAN_INLINE=y, and
CONFIG_FORTIFY_SOURCE=n (patched kernel mounts cleanly with 0 KASAN
splats).
- Note that previously posted patches bounding the destination index
entry or checking off + len <= client_data_len do not catch this case,
because the destination entry is valid and 0x90 + 1 <= 0x98 holds.
- Disclosure: Assisted by a custom LLM-based tool; all results,
including the root-cause analysis, reproducer, and patch, were
manually reviewed and verified by a human.
To reproduce:
1. Fetch windows-interrupted-1.img.gz from:
https://raw.githubusercontent.com/antimatter-studios/rust-fs-ntfs/main/test-disks/windows-interrupted-1.img.gz
2. Patch 3 bytes using poc.py below (`python3 poc.py windows-interrupted-1.img.gz short-source.img`)
and mount read-write (`mount -t ntfs3 -o force short-source.img /mnt`).
Reproducer script (poc.py):
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-only
import argparse, gzip, hashlib
from pathlib import Path
BASE_SHA256 = "c45659e95104eca5f793c3f21061f42705a01783e62d1c7a46c63e4089db0b9a"
TRIGGER_SHA256 = "0e176bdd374e73dc3f40163320d6d69bfe36fe664735a50001a3bcbcad2ec2e4"
CHANGES = (
(0x292F878, 0x00, 0x01), # LFS_RECORD_HDR.flags: separate allocation
(0x292F884, 0x28, 0x90), # LOG_REC_HDR.redo_off low byte
(0x292F886, 0x38, 0x01), # LOG_REC_HDR.redo_len low byte
)
parser = argparse.ArgumentParser()
parser.add_argument("fixture_gz", type=Path)
parser.add_argument("output_img", type=Path)
args = parser.parse_args()
image = bytearray(gzip.decompress(args.fixture_gz.read_bytes()))
assert hashlib.sha256(image).hexdigest() == BASE_SHA256
for offset, old, new in CHANGES:
assert image[offset] == old
image[offset] = new
assert hashlib.sha256(image).hexdigest() == TRIGGER_SHA256
args.output_img.write_bytes(image)
fs/ntfs3/fslog.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c
index bb8d40bb5869..29840814c82e 100644
--- a/fs/ntfs3/fslog.c
+++ b/fs/ntfs3/fslog.c
@@ -3568,6 +3568,8 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe,
e = Add2Ptr(attr, le16_to_cpu(lrh->attr_off));
fname = (struct ATTR_FILE_NAME *)(e + 1);
+ if (dlen < sizeof(fname->dup))
+ goto dirty_vol;
memmove(&fname->dup, data, sizeof(fname->dup)); //
mi->dirty = true;
break;
@@ -3753,6 +3755,8 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe,
}
fname = (struct ATTR_FILE_NAME *)(e + 1);
+ if (dlen < sizeof(fname->dup))
+ goto dirty_vol;
memmove(&fname->dup, data, sizeof(fname->dup));
a_dirty = true;
--
2.43.0
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-11-13 16:46 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-13 16:46 [PATCH] fs/ntfs3: validate dlen before copying filename dup in do_action() Junyoung Jang
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®