]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ntfs: use page allocation for resident attribute inline data
authorNamjae Jeon <linkinjeon@kernel.org>
Wed, 22 Apr 2026 02:56:12 +0000 (11:56 +0900)
committerNamjae Jeon <linkinjeon@kernel.org>
Wed, 22 Apr 2026 10:05:07 +0000 (19:05 +0900)
The current kmemdup() based allocation for IOMAP_INLINE can result in
inline_data pointer having a non-zero page offset. This causes
iomap_inline_data_valid() to fail the check:

    iomap->length <= PAGE_SIZE - offset_in_page(iomap->inline_data)

and triggers the kernel BUG at fs/iomap/buffered-io.c:1061.

This particularly affects workloads with frequent small file access
(e.g. Firefox Nightly profile on NTFS with bind mount) when using the
new ntfs. This fix this by allocating a full page with alloc_page() so that
page_address() always returns a page-aligned address.

Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
fs/ntfs/iomap.c

index 3d1458dea90f90fccd7a6cd61c08ee0374c52310..74a4d3e971f4d2f81fb5de30f597ec0b87e1568d 100644 (file)
@@ -89,6 +89,7 @@ static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, lo
        u32 attr_len;
        int err = 0;
        char *kattr;
+       struct page *ipage;
 
        if (NInoAttr(ni))
                base_ni = ni->ext.base_ntfs_ino;
@@ -129,15 +130,18 @@ static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, lo
 
        kattr = (u8 *)ctx->attr + le16_to_cpu(ctx->attr->data.resident.value_offset);
 
-       iomap->inline_data = kmemdup(kattr, attr_len, GFP_KERNEL);
-       if (!iomap->inline_data) {
+       ipage = alloc_page(GFP_NOFS | __GFP_ZERO);
+       if (!ipage) {
                err = -ENOMEM;
                goto out;
        }
 
+       memcpy(page_address(ipage), kattr, attr_len);
        iomap->type = IOMAP_INLINE;
+       iomap->inline_data = page_address(ipage);
        iomap->offset = 0;
        iomap->length = attr_len;
+       iomap->private = ipage;
 
 out:
        if (ctx)
@@ -285,8 +289,11 @@ static int ntfs_read_iomap_begin(struct inode *inode, loff_t offset, loff_t leng
 static int ntfs_read_iomap_end(struct inode *inode, loff_t pos, loff_t length,
                ssize_t written, unsigned int flags, struct iomap *iomap)
 {
-       if (iomap->type == IOMAP_INLINE)
-               kfree(iomap->inline_data);
+       if (iomap->type == IOMAP_INLINE) {
+               struct page *ipage = iomap->private;
+
+               put_page(ipage);
+       }
 
        return written;
 }
@@ -652,6 +659,7 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset,
        u32 attr_len;
        int err = 0;
        char *kattr;
+       struct page *ipage;
 
        ctx = ntfs_attr_get_search_ctx(ni, NULL);
        if (!ctx) {
@@ -672,16 +680,19 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset,
        attr_len = le32_to_cpu(a->data.resident.value_length);
        kattr = (u8 *)a + le16_to_cpu(a->data.resident.value_offset);
 
-       iomap->inline_data = kmemdup(kattr, attr_len, GFP_KERNEL);
-       if (!iomap->inline_data) {
+       ipage = alloc_page(GFP_NOFS | __GFP_ZERO);
+       if (!ipage) {
                err = -ENOMEM;
                goto out;
        }
 
+       memcpy(page_address(ipage), kattr, attr_len);
        iomap->type = IOMAP_INLINE;
+       iomap->inline_data = page_address(ipage);
        iomap->offset = 0;
        /* iomap requires there is only one INLINE_DATA extent */
        iomap->length = attr_len;
+       iomap->private = ipage;
 
 out:
        if (ctx)
@@ -771,6 +782,7 @@ static int ntfs_write_iomap_end_resident(struct inode *inode, loff_t pos,
        u32 attr_len;
        int err;
        char *kattr;
+       struct page *ipage = iomap->private;
 
        mutex_lock(&ni->mrec_lock);
        ctx = ntfs_attr_get_search_ctx(ni, NULL);
@@ -799,7 +811,7 @@ static int ntfs_write_iomap_end_resident(struct inode *inode, loff_t pos,
        mark_mft_record_dirty(ctx->ntfs_ino);
 err_out:
        ntfs_attr_put_search_ctx(ctx);
-       kfree(iomap->inline_data);
+       put_page(ipage);
        mutex_unlock(&ni->mrec_lock);
        return written;