Re: [BUG] fat: race between fat32_ent_put FAT update and mmc_spi transmit on shared page

OGAWA Hirofumi <[email protected]>
Newsgroups org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
guibing <[email protected]> writes:

> Hi,
>
> I would like to report a data corruption issue caused by a race 
> condition between the FAT32 filesystem driver and the MMC SPI block 
> device driver during background writeback.
>
> == Problem Description ==
>
> On a dual-core RISC-V platform running the SPEC CPU2006 benchmark suite,
> the SD card inevitably becomes read-only after approximately 2 hours of
> execution.
>
> The issue occurs when Core 0 attempts to write 512 bytes of data to the
> SD card via mmc_spi. The SD card returns a CRC error.
>
> Investigation reveals that in the mmc_spi_writeblock() function, the 
> data in t->tx_buf is correct before calling spi_sync_locked() (verified 
> by backing up tx_buf via memcpy). However, after spi_sync_locked() 
> returns, part of the data in t->tx_buf has been tampered with, causing 
> the data actually sent to the SD card to be incorrect.
>
> We have ruled out the SPI driver itself; it transmits exactly what is in
> tx_buf, but the data is being modified in memory during the transmission
> process.
>
> Relevant Error Log:
> # ./intspeed.sh 483.xalancbmk
> Starting speed 483.xalancbmk run with 1 threads
> [11035.536254] mmc_spi_writeblock:660 :write error eb (-84),use_crc:1
>
> == Debugging Evidence (Page Overlap) ==
>
> To pinpoint the corruption, I added debug variables to capture the
> underlying struct page of both the FAT metadata buffer and the SPI TX
> buffer.
>
> In fs/fat/fatent.c:
>      static volatile struct page *fat_ent_put_page_debug;
>      static void fat32_ent_put(struct fat_entry *fatent, int new)
>      {
>          WARN_ON(new & 0xf0000000);
>          fat_ent_put_page_debug = fatent->bhs[0]->b_page;
>          ...
>      }
>
> In drivers/mmc/host/mmc_spi.c:
>      static volatile struct page *mmc_spi_debug_tx_page;
>      static void mmc_spi_data_do(...)
>      {
>          ...
>          for_each_sg(data->sg, sg, data->sg_len, n_sg) {
>              ...
>              if (direction == DMA_TO_DEVICE) {
>                  mmc_spi_debug_tx_page = sg_page(sg);
>                  status = mmc_spi_writeblock(host, t, timeout);
>              }
>              ...
>          }
>      }
>
> When the corruption occurs, the printed values show:
>      mmc_spi_debug_tx_page == fat_ent_put_page_debug
>
> This proves that the FAT metadata page and the SPI TX buffer page are
> the exact same physical page.
>
> Using GDB to inspect the page flags of this shared page reveals: 0x8136.
> This corresponds to the following flags:
>      PG_writeback
>      PG_dirty
>      PG_lru
>      PG_active
>      PG_private
>      PG_referenced
>
> The presence of PG_writeback confirms that the page is currently being
> written back to the block device when the corruption happens.
>
> == Race Condition Details ==
>
> Core 0 (Background Writeback Path):
> The kernel writeback worker triggers an MMC SPI write.
> Call Trace:
>    worker_thread -> blk_mq_dispatch_rq_list -> mmc_blk_mq_issue_rw_rq ->
>    mmc_spi_request -> spi_sync_locked()
>
> Core 0 uses the page cache page directly as the SPI TX buffer and
> transmits it to the SD card.
>
> Core 1 (FAT Metadata Update Path):
> Concurrently, another core is updating the FAT table during a file write.
> Call Trace:
>    fat_write_begin -> cont_write_begin -> block_write_begin -> 
> fat_get_block ->
>    fat_add_cluster -> fat_alloc_clusters -> fat32_ent_put()
>
> In fat32_ent_put(), the FAT entry is updated directly via the buffer_head:
>      *fatent->u.ent32_p = cpu_to_le32(new);
>
> == Related Issue from Syzbot ==
>
> This race condition has also been detected by Syzbot using KCSAN (Kernel
> Concurrency Sanitizer), which reported a data race between the FAT update
> path and a read path (copy_folio_from_iter_atomic).
>
> Syzbot Report:
> https://syzbot.org/ai_job?id=68b1ff7f-84fd-4057-9672-beed284a98af
>
> == Workaround / Verification ==
>
> Adding the following check in fat32_ent_put() resolves the issue:
>
>   static void fat32_ent_put(struct fat_entry *fatent, int new)
>   {
>          WARN_ON(new & 0xf0000000);
> +
> +       struct buffer_head *bh = fatent->bhs[0];
> +
> +       if (bh && bh->b_page && PageWriteback(bh->b_page)) {
> +               wait_on_page_writeback(bh->b_page);
> +       }
>
> }
>
> This confirms that the corruption is caused by fat32_ent_put() modifying
> the page while it is actively being written back via the SPI path.
>
> == Environment ==
>
> Architecture: Dual-core RISC-V
> Filesystem: FAT32
> Block Device: MMC over SPI
> Kernel: Linux v6.6
> Platform: FPGA/QEMU with crc patch
>
> Any feedback or suggestions on the best way to fix this synchronization
> issue in the FAT block layer would be greatly appreciated.

Looks like this is the stable page issue that FAT is not
supporting. Maybe workaround is - if the block layer driver told
requires the stable page, wait the block write is completed.

Thanks.
-- 
OGAWA Hirofumi <[email protected]>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.