]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
udf: Fix race between file type conversion and writeback
authorJan Kara <jack@suse.cz>
Thu, 26 Mar 2026 14:06:32 +0000 (15:06 +0100)
committerJan Kara <jack@suse.cz>
Fri, 27 Mar 2026 16:01:40 +0000 (17:01 +0100)
udf_setsize() can race with udf_writepages() as follows:

udf_setsize() udf_writepages()
  if (iinfo->i_alloc_type ==
ICBTAG_FLAG_AD_IN_ICB)
  err = udf_expand_file_adinicb(inode);
  err = udf_extend_file(inode, newsize);
    udf_adinicb_writepages()
      memcpy_from_file_folio() - crash
because inode size is too big.

Fix the problem by checking the file type under folio lock in
udf_handle_page_wb() handler called from __mpage_writepages() which
properly serializes with udf_expand_file_adinicb().

Reported-by: Jianzhou Zhao <luckd0g@163.com>
Link: https://lore.kernel.org/all/f622c01.67ac.19cdbdd777d.Coremail.luckd0g@163.com
Reviewed-by: Christoph Hellwig <hch@lst.de>
Link: https://patch.msgid.link/20260326140635.15895-4-jack@suse.cz
Signed-off-by: Jan Kara <jack@suse.cz>
fs/udf/inode.c

index 7fae8002344a0e1e7e51022e97f1e0e2424185f9..23e894092dab5798fa9da04063c3b1b620260ab1 100644 (file)
@@ -181,22 +181,23 @@ static void udf_write_failed(struct address_space *mapping, loff_t to)
        }
 }
 
-static int udf_adinicb_writepages(struct address_space *mapping,
-                     struct writeback_control *wbc)
+static int udf_handle_page_wb(struct folio *folio,
+                             struct writeback_control *wbc)
 {
-       struct inode *inode = mapping->host;
+       struct inode *inode = folio->mapping->host;
        struct udf_inode_info *iinfo = UDF_I(inode);
-       struct folio *folio = NULL;
-       int error = 0;
 
-       while ((folio = writeback_iter(mapping, wbc, folio, &error))) {
-               BUG_ON(!folio_test_locked(folio));
-               BUG_ON(folio->index != 0);
-               memcpy_from_file_folio(iinfo->i_data + iinfo->i_lenEAttr, folio,
-                               0, i_size_read(inode));
-               folio_unlock(folio);
-       }
+       /*
+        * Inodes in the normal format are handled by the generic code. This
+        * check is race-free as the folio lock protects us from inode type
+        * conversion.
+        */
+       if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB)
+               return 1;
 
+       memcpy_from_file_folio(iinfo->i_data + iinfo->i_lenEAttr, folio,
+                               0, i_size_read(inode));
+       folio_unlock(folio);
        mark_inode_dirty(inode);
        return 0;
 }
@@ -204,12 +205,8 @@ static int udf_adinicb_writepages(struct address_space *mapping,
 static int udf_writepages(struct address_space *mapping,
                          struct writeback_control *wbc)
 {
-       struct inode *inode = mapping->host;
-       struct udf_inode_info *iinfo = UDF_I(inode);
-
-       if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB)
-               return udf_adinicb_writepages(mapping, wbc);
-       return mpage_writepages(mapping, wbc, udf_get_block_wb);
+       return __mpage_writepages(mapping, wbc, udf_get_block_wb,
+                                 udf_handle_page_wb);
 }
 
 static void udf_adinicb_read_folio(struct folio *folio)