ntfs_resident_attr_value_resize() uses value_offset w/o check
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]
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_offsetgreater than the attribute length, so thatmemset()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/aYou'll need to add a printk to
ntfs_resident_attr_value_resize()to see the problem (unless you get lucky and thememset()disturbs some critical kernel data).(gdb) where
#0ntfs_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) printnew_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.
Namjae Jeon
maintainerQuoted 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_offsetgreater than the attribute length, so thatmemset()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.
--- fs/ntfs/attrib.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)
fs/ntfs/attrib.c +6 -3
@@ -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
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()usesvalue_offsetw/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_offsetgreater than the attribute length, so thatmemset()zeros outside of the allocated memory.Please check the attached patch. Thanks for the report!