Skip to contentSkip to conversation

ntfs_resident_attr_value_resize() uses value_offset w/o check

DISCUSSION [email protected] 3 participants 4 messages Oct 31 – Nov 2 NTFS filesystem

lore
Share & tools
lore
#0 lore raw

In ntfs/attrib.c's ntfs_resident_attr_value_resize():

        old_size = le32_to_cpu(a->data.resident.value_length);
        if (new_size > old_size)
                memset((u8 *)a + le16_to_cpu(a->data.resident.value_offset) +
                                old_size, 0, new_size - old_size);

A corrupt NTFS image could have value_offset greater than the attribute length, so that memset() zeros outside of the allocated memory.

A demo:

# wget http://www.rtmrtm.org/rtm/ntfs40e.img.gz
# gunzip ntfs40e.img.gz
# cp ntfs40e.img junk
# mount -t ntfs -o loop,rw junk /mnt
# echo b >> /mnt/a

You'll need to add a printk to ntfs_resident_attr_value_resize() to see the problem (unless you get lucky and the memset() disturbs some critical kernel data).

(gdb) where
#0 ntfs_resident_attr_value_resize (m=<optimized out>, a=0xffffffd6059ce548,

    new_size=new_size@entry=4) at fs/ntfs/attrib.c:1982
#1  0xffffffff804c0ab0 in ntfs_resident_attr_resize (
    attr_ni=attr_ni@entry=0xffffffd603ded000, newsize=newsize@entry=4, 
    prealloc_size=prealloc_size@entry=0, holes=1) at fs/ntfs/attrib.c:4918
#2  0xffffffff804c1814 in ntfs_attr_expand_locked (
    ni=ni@entry=0xffffffd603ded000, newsize=newsize@entry=4, 
    prealloc_size=prealloc_size@entry=0, locked_ni=locked_ni@entry=0x0)
    at fs/ntfs/attrib.c:5207
#3  0xffffffff804c18c0 in ntfs_attr_expand (ni=ni@entry=0xffffffd603ded000, 
    newsize=newsize@entry=4, prealloc_size=prealloc_size@entry=0)
    at fs/ntfs/attrib.c:5224
#4  0xffffffff804c786a in ntfs_expand_for_write (end=4, ni=0xffffffd603ded000)
    at fs/ntfs/file.c:850
#5  ntfs_file_write_iter (iocb=0xffffffc600253dc8, from=0xffffffc600253da0)
    at fs/ntfs/file.c:923
#6  0xffffffff803031d2 in new_sync_write (ppos=0xffffffc600253e40, len=2, 
    buf=0x2aaaae3620 <error: Cannot access memory at address 0x2aaaae3620>, 
    filp=0xffffffd6049351c0) at fs/read_write.c:595
#7  vfs_write (file=file@entry=0xffffffd6049351c0, 
    buf=buf@entry=0x2aaaae3620 <error: Cannot access memory at address 0x2aaaae3620>, count=count@entry=2, pos=pos@entry=0xffffffc600253e40)
    at fs/read_write.c:687
#8  0xffffffff803035fa in ksys_write (fd=<optimized out>, 
    buf=0x2aaaae3620 <error: Cannot access memory at address 0x2aaaae3620>, 
    count=2) at fs/read_write.c:739
#9  0xffffffff80303678 in __do_sys_write (count=<optimized out>, 
    buf=<optimized out>, fd=<optimized out>) at fs/read_write.c:750
#10 __se_sys_write (count=<optimized out>, buf=<optimized out>, 
    fd=<optimized out>) at fs/read_write.c:747
#11 __riscv_sys_write (regs=<optimized out>) at fs/read_write.c:747
#12 0xffffffff80ebc0ce in syscall_handler (syscall=<optimized out>, 
    regs=0xffffffc600253ee0) at ./arch/riscv/include/asm/syscall.h:112

(gdb) print old_size
$1 = 3
(gdb) print new_size
$2 = 4
(gdb) print *a
$3 = {type = 128, length = 8, non_resident = 255 '\377',
name_length = 255 '\377', name_offset = 65535, flags = 0, instance = 0, data = {resident = {value_length = 3, value_offset = 48925,

      flags = 0 '\000', reserved = 0 '\000'}, non_resident = {
      lowest_vcn = 210131274956803, highest_vcn = 4294904417, 
      mapping_pairs_offset = 65535, compression_unit = 255 '\377', 
      reserved = "\377\000\000\000", allocated_size = 210131274956803, 
      data_size = 0, initialized_size = 0, compressed_size = 0}}}

Robert Morris
[email protected]

#1 lore raw
Quoted 64 lines · 在 2026/11/1 04:40, [email protected] 写道:

In ntfs/attrib.c's ntfs_resident_attr_value_resize():

         old_size = le32_to_cpu(a->data.resident.value_length);
         if (new_size > old_size)
                 memset((u8 *)a + le16_to_cpu(a->data.resident.value_offset) +
                                 old_size, 0, new_size - old_size);

A corrupt NTFS image could have value_offset greater than the attribute length, so that memset() zeros outside of the allocated memory.

A demo:

# wget http://www.rtmrtm.org/rtm/ntfs40e.img.gz
# gunzip ntfs40e.img.gz
# cp ntfs40e.img junk
# mount -t ntfs -o loop,rw junk /mnt
# echo b >> /mnt/a

You'll need to add a printk to ntfs_resident_attr_value_resize() to see the problem (unless you get lucky and the memset() disturbs some critical kernel data).

(gdb) where
#0 ntfs_resident_attr_value_resize (m=<optimized out>, a=0xffffffd6059ce548,

     new_size=new_size@entry=4) at fs/ntfs/attrib.c:1982
#1  0xffffffff804c0ab0 in ntfs_resident_attr_resize (
     attr_ni=attr_ni@entry=0xffffffd603ded000, newsize=newsize@entry=4,
     prealloc_size=prealloc_size@entry=0, holes=1) at fs/ntfs/attrib.c:4918
#2  0xffffffff804c1814 in ntfs_attr_expand_locked (
     ni=ni@entry=0xffffffd603ded000, newsize=newsize@entry=4,
     prealloc_size=prealloc_size@entry=0, locked_ni=locked_ni@entry=0x0)
     at fs/ntfs/attrib.c:5207
#3  0xffffffff804c18c0 in ntfs_attr_expand (ni=ni@entry=0xffffffd603ded000,
     newsize=newsize@entry=4, prealloc_size=prealloc_size@entry=0)
     at fs/ntfs/attrib.c:5224
#4  0xffffffff804c786a in ntfs_expand_for_write (end=4, ni=0xffffffd603ded000)
     at fs/ntfs/file.c:850
#5  ntfs_file_write_iter (iocb=0xffffffc600253dc8, from=0xffffffc600253da0)
     at fs/ntfs/file.c:923
#6  0xffffffff803031d2 in new_sync_write (ppos=0xffffffc600253e40, len=2,
     buf=0x2aaaae3620 <error: Cannot access memory at address 0x2aaaae3620>,
     filp=0xffffffd6049351c0) at fs/read_write.c:595
#7  vfs_write (file=file@entry=0xffffffd6049351c0,
     buf=buf@entry=0x2aaaae3620 <error: Cannot access memory at address 0x2aaaae3620>, count=count@entry=2, pos=pos@entry=0xffffffc600253e40)
     at fs/read_write.c:687
#8  0xffffffff803035fa in ksys_write (fd=<optimized out>,
     buf=0x2aaaae3620 <error: Cannot access memory at address 0x2aaaae3620>,
     count=2) at fs/read_write.c:739
#9  0xffffffff80303678 in __do_sys_write (count=<optimized out>,
     buf=<optimized out>, fd=<optimized out>) at fs/read_write.c:750
#10 __se_sys_write (count=<optimized out>, buf=<optimized out>,
     fd=<optimized out>) at fs/read_write.c:747
#11 __riscv_sys_write (regs=<optimized out>) at fs/read_write.c:747
#12 0xffffffff80ebc0ce in syscall_handler (syscall=<optimized out>,
     regs=0xffffffc600253ee0) at ./arch/riscv/include/asm/syscall.h:112

(gdb) print old_size
$1 = 3
(gdb) print new_size
$2 = 4
(gdb) print *a
$3 = {type = 128, length = 8, non_resident = 255 '\377',
name_length = 255 '\377', name_offset = 65535, flags = 0, instance = 0, data = {resident = {value_length = 3, value_offset = 48925,

Quoted 8 lines
       flags = 0 '\000', reserved = 0 '\000'}, non_resident = {
       lowest_vcn = 210131274956803, highest_vcn = 4294904417,
       mapping_pairs_offset = 65535, compression_unit = 255 '\377',
       reserved = "\377\000\000\000", allocated_size = 210131274956803,
       data_size = 0, initialized_size = 0, compressed_size = 0}}}

Robert Morris
[email protected]

Hi Robert,
Thanks for reporting this issue. I checked the latest ntfs-next, and
the issue is still present.
The fix should validate the resident value offset before resizing,
and reject any resize that would make the attribute record smaller than its header. This prevents a corrupt offset from causing the resize to overwrite the attribute header before the code clears the newly extended value.
If you’re interested, please feel free to submit a patch. We’d be
happy to review it.

Regards,
Baolin.

maintainer
#2 lore raw
In reply to [email protected]
Quoted 11 lines · On Sun, Nov 1, 2026 at 5:40 AM <[email protected]> wrote:

In ntfs/attrib.c's ntfs_resident_attr_value_resize():

        old_size = le32_to_cpu(a->data.resident.value_length);
        if (new_size > old_size)
                memset((u8 *)a + le16_to_cpu(a->data.resident.value_offset) +
                                old_size, 0, new_size - old_size);

A corrupt NTFS image could have value_offset greater than the attribute length, so that memset() zeros outside of the allocated memory.

Please check the attached patch.
Thanks for the report!

From 5b8e316a6dfeb1f1fa220c002ae27dbb8e6845eb Mon Sep 17 00:00:00 2001
From: Namjae Jeon <[email protected]>
Date: Mon, 2 Nov 2026 20:47:51 +0900
Subject: [PATCH] ntfs: don't allow resident values to overlap the attribute
header

The resident value bounds check allows value_offset to point inside the attribute header. The reported image has an unnamed $DATA attribute with a value_offset of zero and a value_length of two, which passes lookup.

Appending to this file makes ntfs_resident_attr_value_resize() shrink the attribute record to eight bytes. Moving the following attributes then overwrites the resident header fields, so the subsequent memset() uses a corrupted value_offset and writes outside the MFT record buffer.

Require the resident value to start after the complete resident header. Both lookup paths then return -EIO for the corrupt record, before callers can resize or access its value.

Fixes 495e90fa3348 ("ntfs: update attrib operations")
---
 fs/ntfs/attrib.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
fs/ntfs/attrib.c +6 -3
index fce7144d7bef..ce7c1b524180 100644
@@ -704,17 +704,20 @@ static bool ntfs_resident_attr_value_get(const struct attr_record *a,
 					 struct ntfs_resident_attr_value *value)
 {
 	u32 attr_len;
+	u32 min_len;
 	u16 value_offset;
 
 	attr_len = le32_to_cpu(a->length);
-	if (attr_len < offsetof(struct attr_record, data.resident.reserved) +
-			sizeof(a->data.resident.reserved))
+	min_len = offsetof(struct attr_record, data.resident.reserved) +
+		  sizeof(a->data.resident.reserved);
+	if (attr_len < min_len)
 		return false;
 
 	value->len = le32_to_cpu(a->data.resident.value_length);
 	value_offset = le16_to_cpu(a->data.resident.value_offset);
 
-	if (value->len > attr_len || value_offset > attr_len - value->len)
+	if (value_offset < min_len || value->len > attr_len ||
+	    value_offset > attr_len - value->len)
 		return false;
 
 	value->data = (const u8 *)a + value_offset;
Signature · 1 line
2.25.1
#3 lore raw

Yes -- this patch does indeed make the problem go away for me.

Robert

Quoted 20 lines

From: Namjae Jeon <[email protected]>
Date: Mon, 2 Nov 2026 22:51:17 +0900
Subject: Re: ntfs_resident_attr_value_resize() uses value_offset w/o check
To: [email protected]
Cc: Hyunchul Lee <[email protected]>, [email protected]

Quoted 11 lines · On Sun, Nov 1, 2026 at 5:40 AM <[email protected]> wrote:

In ntfs/attrib.c's ntfs_resident_attr_value_resize():

        old_size = le32_to_cpu(a->data.resident.value_length);
        if (new_size > old_size)
                memset((u8 *)a + le16_to_cpu(a->data.resident.value_offset) +
                                old_size, 0, new_size - old_size);

A corrupt NTFS image could have value_offset greater than the attribute length, so that memset() zeros outside of the allocated memory.

Please check the attached patch. Thanks for the report!