* [PATCH] ntfs: fix out-of-bounds write race in NTFS_IOC_LIST_STREAMS
@ 2026-11-05 7:34 Xingyuan Mo
2026-11-05 9:26 ` liubaolin
2026-11-05 12:33 ` Namjae Jeon
0 siblings, 2 replies; 3+ messages in thread
From: Xingyuan Mo @ 2026-11-05 7:34 UTC (permalink / raw)
To: Namjae Jeon, Hyunchul Lee
Cc: open list:NTFS FILESYSTEM, open list, Xingyuan Mo
NTFS_IOC_LIST_STREAMS walks the base inode's MFT attributes twice:
a first pass sizes the output buffer, a second pass fills it, both
times filtering out streams that ntfs_stream_unlinked() reports as
unlinked. That filter is the culprit: it treats a stream inode that
is no longer in the inode hash as live, which is precisely the state
a stream being deleted ends up in.
When a base file is deleted while one of its named streams is still
open, ntfs_cleanup_deleted_inode() sets NInoStreamUnlinked on the
stream inode and removes it from the inode hash after dropping the
MFT record lock. Nothing serializes this against the ioctl's two
passes, so if the flag and the unhash land between them, the first
pass excludes the stream while the second pass includes it, and the
ioctl writes an entry the allocation never accounted for.
The per-entry bounds check cannot catch this divergence, because
"offset > required - entry_size" underflows when entry_size exceeds
required. With a 1-character stream sizing the buffer at 48 bytes and
a 250-character stream surfacing only in the second pass, a 544-byte
entry is written into the 48-byte kmalloc-64 allocation.
Close the hole in two steps:
- Reject base inodes that are being deleted before walking their
attributes, like the other named stream entry points in this file
already do. This also stops the listing from reporting streams of a
deleted base file as live, which is reproducible even without the
race.
- Make the bounds check underflow-safe so any remaining divergence
between the passes can only produce -EOVERFLOW, never an
out-of-bounds write.
KASAN report from the reproducer (timestamps stripped):
BUG: KASAN: slab-out-of-bounds in ntfs_ioctl_list_streams+0x7a9/0xa00
Write of size 500 at addr ffff88800b1931a8 by task ntfs_ads_race/305
CPU: 2 UID: 0 PID: 305 Comm: ntfs_ads_race
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0x73/0xb0
print_report+0xd1/0x610
kasan_report+0xff/0x130
kasan_check_range+0x10c/0x1c0
__asan_memcpy+0x3f/0x70
ntfs_ioctl_list_streams+0x7a9/0xa00
ntfs_ioctl+0x3b6/0x420
__x64_sys_ioctl+0xd1/0x120
x64_sys_call+0x2a5/0x2220
do_syscall_64+0x109/0x5e0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>
Allocated by task 305:
kasan_save_stack+0x3d/0x60
kasan_save_track+0x18/0x40
kasan_save_alloc_info+0x3b/0x50
__kasan_kmalloc+0x9c/0xa0
__kvmalloc_node_noprof+0x2dd/0x800
ntfs_ioctl_list_streams+0x57f/0xa00
ntfs_ioctl+0x3b6/0x420
__x64_sys_ioctl+0xd1/0x120
x64_sys_call+0x2a5/0x2220
do_syscall_64+0x109/0x5e0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff88800b193180
which belongs to the cache kmalloc-64 of size 64
The buggy address is located 40 bytes inside of
allocated 48-byte region [ffff88800b193180, ffff88800b1931b0)
Fixes: 48996054b706 ("ntfs: add named stream ioctls support")
Signed-off-by: Xingyuan Mo <hdthky0@gmail.com>
---
fs/ntfs/named_stream.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/fs/ntfs/named_stream.c b/fs/ntfs/named_stream.c
index 95a3c05f9821..70c589207713 100644
--- a/fs/ntfs/named_stream.c
+++ b/fs/ntfs/named_stream.c
@@ -1632,6 +1632,10 @@ int ntfs_ioctl_list_streams(struct file *filp, unsigned long arg)
ubuf = (void __user *)arg + sizeof(hdr);
mutex_lock(&ni->mrec_lock);
+ if (NInoBeingDeleted(ni) || !inode->i_nlink) {
+ mutex_unlock(&ni->mrec_lock);
+ return -ENOENT;
+ }
actx = ntfs_attr_get_search_ctx(ni, NULL);
if (!actx) {
mutex_unlock(&ni->mrec_lock);
@@ -1731,7 +1735,7 @@ int ntfs_ioctl_list_streams(struct file *filp, unsigned long arg)
}
entry_size = ALIGN(name_offset + name_len, 8);
- if (offset > required - entry_size) {
+ if (offset > required || entry_size > required - offset) {
ret = -EOVERFLOW;
kfree(sn);
sn = NULL;
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] ntfs: fix out-of-bounds write race in NTFS_IOC_LIST_STREAMS
2026-11-05 7:34 [PATCH] ntfs: fix out-of-bounds write race in NTFS_IOC_LIST_STREAMS Xingyuan Mo
@ 2026-11-05 9:26 ` liubaolin
2026-11-05 12:33 ` Namjae Jeon
1 sibling, 0 replies; 3+ messages in thread
From: liubaolin @ 2026-11-05 9:26 UTC (permalink / raw)
To: Xingyuan Mo, Namjae Jeon, Hyunchul Lee
Cc: open list:NTFS FILESYSTEM, open list
在 2026/11/5 15:34, Xingyuan Mo 写道:
> NTFS_IOC_LIST_STREAMS walks the base inode's MFT attributes twice:
> a first pass sizes the output buffer, a second pass fills it, both
> times filtering out streams that ntfs_stream_unlinked() reports as
> unlinked. That filter is the culprit: it treats a stream inode that
> is no longer in the inode hash as live, which is precisely the state
> a stream being deleted ends up in.
>
> When a base file is deleted while one of its named streams is still
> open, ntfs_cleanup_deleted_inode() sets NInoStreamUnlinked on the
> stream inode and removes it from the inode hash after dropping the
> MFT record lock. Nothing serializes this against the ioctl's two
> passes, so if the flag and the unhash land between them, the first
> pass excludes the stream while the second pass includes it, and the
> ioctl writes an entry the allocation never accounted for.
>
> The per-entry bounds check cannot catch this divergence, because
> "offset > required - entry_size" underflows when entry_size exceeds
> required. With a 1-character stream sizing the buffer at 48 bytes and
> a 250-character stream surfacing only in the second pass, a 544-byte
> entry is written into the 48-byte kmalloc-64 allocation.
>
> Close the hole in two steps:
>
> - Reject base inodes that are being deleted before walking their
> attributes, like the other named stream entry points in this file
> already do. This also stops the listing from reporting streams of a
> deleted base file as live, which is reproducible even without the
> race.
>
> - Make the bounds check underflow-safe so any remaining divergence
> between the passes can only produce -EOVERFLOW, never an
> out-of-bounds write.
>
> KASAN report from the reproducer (timestamps stripped):
>
> BUG: KASAN: slab-out-of-bounds in ntfs_ioctl_list_streams+0x7a9/0xa00
> Write of size 500 at addr ffff88800b1931a8 by task ntfs_ads_race/305
> CPU: 2 UID: 0 PID: 305 Comm: ntfs_ads_race
> Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> Call Trace:
> <TASK>
> dump_stack_lvl+0x73/0xb0
> print_report+0xd1/0x610
> kasan_report+0xff/0x130
> kasan_check_range+0x10c/0x1c0
> __asan_memcpy+0x3f/0x70
> ntfs_ioctl_list_streams+0x7a9/0xa00
> ntfs_ioctl+0x3b6/0x420
> __x64_sys_ioctl+0xd1/0x120
> x64_sys_call+0x2a5/0x2220
> do_syscall_64+0x109/0x5e0
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> </TASK>
>
> Allocated by task 305:
> kasan_save_stack+0x3d/0x60
> kasan_save_track+0x18/0x40
> kasan_save_alloc_info+0x3b/0x50
> __kasan_kmalloc+0x9c/0xa0
> __kvmalloc_node_noprof+0x2dd/0x800
> ntfs_ioctl_list_streams+0x57f/0xa00
> ntfs_ioctl+0x3b6/0x420
> __x64_sys_ioctl+0xd1/0x120
> x64_sys_call+0x2a5/0x2220
> do_syscall_64+0x109/0x5e0
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> The buggy address belongs to the object at ffff88800b193180
> which belongs to the cache kmalloc-64 of size 64
> The buggy address is located 40 bytes inside of
> allocated 48-byte region [ffff88800b193180, ffff88800b1931b0)
>
> Fixes: 48996054b706 ("ntfs: add named stream ioctls support")
> Signed-off-by: Xingyuan Mo <hdthky0@gmail.com>
> ---
> fs/ntfs/named_stream.c | 6 +++++-
> 1 file changed, 5 insertions(+), 1 deletion(-)
>
> diff --git a/fs/ntfs/named_stream.c b/fs/ntfs/named_stream.c
> index 95a3c05f9821..70c589207713 100644
> --- a/fs/ntfs/named_stream.c
> +++ b/fs/ntfs/named_stream.c
> @@ -1632,6 +1632,10 @@ int ntfs_ioctl_list_streams(struct file *filp, unsigned long arg)
> ubuf = (void __user *)arg + sizeof(hdr);
>
> mutex_lock(&ni->mrec_lock);
> + if (NInoBeingDeleted(ni) || !inode->i_nlink) {
> + mutex_unlock(&ni->mrec_lock);
> + return -ENOENT;
> + }
> actx = ntfs_attr_get_search_ctx(ni, NULL);
> if (!actx) {
> mutex_unlock(&ni->mrec_lock);
> @@ -1731,7 +1735,7 @@ int ntfs_ioctl_list_streams(struct file *filp, unsigned long arg)
> }
>
> entry_size = ALIGN(name_offset + name_len, 8);
> - if (offset > required - entry_size) {
> + if (offset > required || entry_size > required - offset) {
> ret = -EOVERFLOW;
> kfree(sn);
> sn = NULL;
Looks good to me.
Reviewed-by: Baolin Liu <liubaolin@kylinos.cn>
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] ntfs: fix out-of-bounds write race in NTFS_IOC_LIST_STREAMS
2026-11-05 7:34 [PATCH] ntfs: fix out-of-bounds write race in NTFS_IOC_LIST_STREAMS Xingyuan Mo
2026-11-05 9:26 ` liubaolin
@ 2026-11-05 12:33 ` Namjae Jeon
1 sibling, 0 replies; 3+ messages in thread
From: Namjae Jeon @ 2026-11-05 12:33 UTC (permalink / raw)
To: Xingyuan Mo; +Cc: Hyunchul Lee, open list:NTFS FILESYSTEM, open list
On Thu, Nov 5, 2026 at 4:34 PM Xingyuan Mo <hdthky0@gmail.com> wrote:
>
> NTFS_IOC_LIST_STREAMS walks the base inode's MFT attributes twice:
> a first pass sizes the output buffer, a second pass fills it, both
> times filtering out streams that ntfs_stream_unlinked() reports as
> unlinked. That filter is the culprit: it treats a stream inode that
> is no longer in the inode hash as live, which is precisely the state
> a stream being deleted ends up in.
>
> When a base file is deleted while one of its named streams is still
> open, ntfs_cleanup_deleted_inode() sets NInoStreamUnlinked on the
> stream inode and removes it from the inode hash after dropping the
> MFT record lock. Nothing serializes this against the ioctl's two
> passes, so if the flag and the unhash land between them, the first
> pass excludes the stream while the second pass includes it, and the
> ioctl writes an entry the allocation never accounted for.
>
> The per-entry bounds check cannot catch this divergence, because
> "offset > required - entry_size" underflows when entry_size exceeds
> required. With a 1-character stream sizing the buffer at 48 bytes and
> a 250-character stream surfacing only in the second pass, a 544-byte
> entry is written into the 48-byte kmalloc-64 allocation.
>
> Close the hole in two steps:
>
> - Reject base inodes that are being deleted before walking their
> attributes, like the other named stream entry points in this file
> already do. This also stops the listing from reporting streams of a
> deleted base file as live, which is reproducible even without the
> race.
>
> - Make the bounds check underflow-safe so any remaining divergence
> between the passes can only produce -EOVERFLOW, never an
> out-of-bounds write.
>
> KASAN report from the reproducer (timestamps stripped):
>
> BUG: KASAN: slab-out-of-bounds in ntfs_ioctl_list_streams+0x7a9/0xa00
> Write of size 500 at addr ffff88800b1931a8 by task ntfs_ads_race/305
> CPU: 2 UID: 0 PID: 305 Comm: ntfs_ads_race
> Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> Call Trace:
> <TASK>
> dump_stack_lvl+0x73/0xb0
> print_report+0xd1/0x610
> kasan_report+0xff/0x130
> kasan_check_range+0x10c/0x1c0
> __asan_memcpy+0x3f/0x70
> ntfs_ioctl_list_streams+0x7a9/0xa00
> ntfs_ioctl+0x3b6/0x420
> __x64_sys_ioctl+0xd1/0x120
> x64_sys_call+0x2a5/0x2220
> do_syscall_64+0x109/0x5e0
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> </TASK>
>
> Allocated by task 305:
> kasan_save_stack+0x3d/0x60
> kasan_save_track+0x18/0x40
> kasan_save_alloc_info+0x3b/0x50
> __kasan_kmalloc+0x9c/0xa0
> __kvmalloc_node_noprof+0x2dd/0x800
> ntfs_ioctl_list_streams+0x57f/0xa00
> ntfs_ioctl+0x3b6/0x420
> __x64_sys_ioctl+0xd1/0x120
> x64_sys_call+0x2a5/0x2220
> do_syscall_64+0x109/0x5e0
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> The buggy address belongs to the object at ffff88800b193180
> which belongs to the cache kmalloc-64 of size 64
> The buggy address is located 40 bytes inside of
> allocated 48-byte region [ffff88800b193180, ffff88800b1931b0)
>
> Fixes: 48996054b706 ("ntfs: add named stream ioctls support")
> Signed-off-by: Xingyuan Mo <hdthky0@gmail.com>
Applied it to #ntfs-next.
Thanks!
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-11-05 12:34 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-05 7:34 [PATCH] ntfs: fix out-of-bounds write race in NTFS_IOC_LIST_STREAMS Xingyuan Mo
2026-11-05 9:26 ` liubaolin
2026-11-05 12:33 ` Namjae Jeon
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®