block device fixes for large block sizes, IOCB_NOWAIT, and direct I/O - #152
block device fixes for large block sizes, IOCB_NOWAIT, and direct I/O#152blktests-ci-block-trial[bot] wants to merge 7 commits into
Conversation
|
Upstream branch: 1b78070 |
9776a98 to
a309a67
Compare
|
Upstream branch: c20313e |
8f3ff75 to
ef43383
Compare
a309a67 to
b3c6f4f
Compare
|
Upstream branch: 548e7bc |
ef43383 to
be492b9
Compare
b3c6f4f to
a88aa4a
Compare
|
Upstream branch: cf72cbb |
be492b9 to
0f74089
Compare
fa3448f to
b86a2fe
Compare
|
Upstream branch: cf72cbb |
0f74089 to
5041227
Compare
b86a2fe to
5728fd2
Compare
|
Upstream branch: 08dbfad |
5041227 to
6c82ff7
Compare
5728fd2 to
b15a0cf
Compare
|
Upstream branch: a23cbb0 |
6c82ff7 to
9282e5b
Compare
b15a0cf to
d4af556
Compare
|
Upstream branch: 78bb208 |
9282e5b to
51e3878
Compare
d4af556 to
45a2312
Compare
|
Upstream branch: cee9395 |
51e3878 to
fb8ee59
Compare
|
Upstream branch: abdf623 |
2f95478 to
f4f265a
Compare
258e7d0 to
76db21d
Compare
|
Upstream branch: 786262b |
f4f265a to
a3c94fe
Compare
76db21d to
e8377ca
Compare
|
Upstream branch: 89a3129 |
a3c94fe to
d497b31
Compare
|
Upstream branch: 89a3129 |
d497b31 to
5ac21d2
Compare
|
Upstream branch: 89a3129 |
5ac21d2 to
0d2b049
Compare
|
Upstream branch: 89a3129 |
0d2b049 to
1615630
Compare
e8377ca to
1fff761
Compare
|
Upstream branch: 940de59 |
1615630 to
85eb7ed
Compare
1fff761 to
3595ceb
Compare
|
Upstream branch: 841e384 |
85eb7ed to
586f31d
Compare
3595ceb to
4061e04
Compare
With CONFIG_BUFFER_HEAD=n, block devices are written back through iomap, but def_blk_aops uses filemap_dirty_folio, which only sets PG_dirty. It does not set the per-block dirty bits in the folio's iomap_folio_state, so iomap_writeback_folio() finds no dirty range, submits no I/O and clears PG_dirty, resulting in data loss. Other iomap users set .dirty_folio to iomap_dirty_folio, which marks the folio's blocks dirty before calling filemap_dirty_folio(). This is only observable with block size < folio size. With a single block there is no iomap_folio_state to get out of sync and iomap_writeback_folio() marks the whole folio dirty itself. For a page-aligned device, this may require using the BLKBSZSET ioctl to set the block size, which requires CAP_SYS_ADMIN. A device whose size is not page aligned already gets a sub-page block size from set_init_blocksize(), so no ioctl and no privilege is needed. To reproduce, on a device with a sub-page block size, write a known pattern with O_DIRECT, mmap the same range, store to it, msync() and fsync(), then read it back with O_DIRECT. A reproducer is available at [1]. [1] https://gist.github.com/tzussman/18ab05cba4b3fdc79cce0a69d1fd05b4 Fixes: 925c86a ("fs: add CONFIG_BUFFER_HEAD") Reported-by: Sashiko <sashiko-bot@kernel.org> Link: https://sashiko.dev/#/patchset/20260730-blk-dontcache-v7-0-3e8e6850068d%40columbia.edu?part=5 Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Hannes Reinecke <hare@kernel.org>
Commit c0e473a ("block: fix race between set_blocksize and read paths") closed a race between set_blocksize() and block device I/O: with large sector size support, set_blocksize() can change i_blkbits and the mapping's minimum folio order while a concurrent reader still holds a folio of the old, smaller order, leading to crashes. In particular, it made blkdev_write_iter() wrap buffered writes in inode_lock_shared(). However, the direct I/O fallback path was missed in that conversion. blkdev_write_iter() passes blkdev_buffered_write() as an argument to direct_write_fallback() with no lock held. A direct write that completes only partially then finishes as a buffered write with no protection. This can cause a BUG by racing partial direct writes against ioctl(BLKBSZSET). Writer threads issue O_DIRECT pwritev() with a two-segment iovec whose second segment is an unreadable PROT_NONE mapping. The direct path then writes the first segment, fails to pin the second, and returns short, entering the fallback. A second thread keeps toggling the second segment's protection so that some fallbacks get past fault_in_iov_iter_readable() and reach the page cache, a third thread populates the page cache with folios of the current block size via pread() and readahead(), and a fourth thread toggles the block size between 512 bytes and 64K with BLKBSZSET. The minimum folio order only moves with block sizes above PAGE_SIZE, i.e. with CONFIG_TRANSPARENT_HUGEPAGE raising BLK_MAX_BLOCK_SIZE to 64K. On a CONFIG_DEBUG_VM kernel this yields the following BUG: page dumped because: VM_BUG_ON_FOLIO(folio_order(folio) < mapping_min_folio_order(mapping)) kernel BUG at mm/filemap.c:858! Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI RIP: 0010:__filemap_add_folio+0x860/0x8d0 Call Trace: filemap_add_folio+0xc9/0x1f0 __filemap_get_folio_mpol+0x240/0x660 iomap_write_begin+0xa87/0xd70 iomap_file_buffered_write+0x304/0x6a0 blkdev_write_iter+0x255/0x510 do_iter_readv_writev+0x23d/0x3c0 vfs_writev+0x211/0x7d0 do_pwritev+0x121/0x190 do_syscall_64+0x121/0x630 entry_SYSCALL_64_after_hwframe+0x77/0x7f The same workload also trips WARN_ON_ONCE(pos >= folio_pos(folio) + fsize) in iomap_trim_folio_range(). Fix this by calling blkdev_buffered_write() in the fallback path under inode_lock_shared(), matching the plain buffered-write branch. With the fix the same workload runs clean. A short IOCB_NOWAIT direct write reaches the same fallback. Taking i_rwsem there can now block behind set_blocksize(), and the fallback already blocks on writeback of the data it copied in direct_write_fallback(). blkdev_write_iter() already rejects a purely buffered IOCB_NOWAIT write with -EOPNOTSUPP, so do not enter the fallback for IOCB_NOWAIT at all: return the bytes the direct path already wrote, or -EAGAIN if none, and let the caller retry. The reproducer used was written by an LLM, and is available at [1]. [1] https://gist.github.com/tzussman/69d06bc57d42a42989eb038b1b5aeb74 Fixes: 3c20917 ("block/bdev: enable large folio support for large logical block sizes") Reported-by: Sashiko <sashiko-bot@kernel.org> Link: https://sashiko.dev/#/patchset/20260730-blk-dontcache-v7-0-3e8e6850068d%40columbia.edu?part=5 Assisted-by: Claude:claude-fable-5 Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Hannes Reinecke <hare@kernel.org>
def_blk_fops wires ->splice_read directly to filemap_splice_read(), which allocates folios based on mapping_min_folio_order() without any lock against set_blocksize(). A splice from a block device can race set_blocksize() raising the minimum folio order and insert a folio that is too small for the mapping. blkdev_read_iter() wraps filemap_read() in inode_lock_shared() for this reason, but the splice path was missed. Splicing from a block device while toggling the block size between 512 bytes and 64K with BLKBSZSET hits this within seconds on a CONFIG_DEBUG_VM kernel: page dumped because: VM_BUG_ON_FOLIO(folio_order(folio) < mapping_min_folio_order(mapping)) kernel BUG at mm/filemap.c:858! Oops: invalid opcode: 0000 [#1] SMP NOPTI RIP: 0010:__filemap_add_folio+0x51c/0x570 Call Trace: filemap_add_folio+0x64/0x140 page_cache_ra_order+0x1dd/0x3d0 filemap_get_pages+0x153/0x760 filemap_splice_read+0x13f/0x300 splice_file_to_pipe+0xc0/0xd0 do_splice+0x6a8/0x890 __do_splice+0xb0/0x210 __x64_sys_splice+0x80/0x100 do_syscall_64+0x10e/0x520 entry_SYSCALL_64_after_hwframe+0x77/0x7f Take inode_lock_shared() around filemap_splice_read(), like the read path does. Fixes: 3c20917 ("block/bdev: enable large folio support for large logical block sizes") Assisted-by: Claude:claude-fable-5 Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Hannes Reinecke <hare@kernel.org>
blkdev_read_iter() takes inode_lock_shared() unconditionally around filemap_read(). Unlike blkdev_write_iter(), it does not reject IOCB_NOWAIT for buffered I/O, so a non-blocking read, or the buffered tail of a short IOCB_NOWAIT direct read, blocks behind set_blocksize() holding i_rwsem across sync_blockdev(). A preadv2(RWF_NOWAIT) issued while another thread changes the block size with a dirty page cache blocks for as long as sync_blockdev() takes, 4 to 6 seconds on a scsi_debug device with delay=5. Use inode_trylock_shared() for IOCB_NOWAIT and return the bytes the direct path already read, or -EAGAIN if none, when the lock is contended, preserving NOWAIT semantics. Fixes: c0e473a ("block: fix race between set_blocksize and read paths") Reported-by: Sashiko <sashiko-bot@kernel.org> Link: https://sashiko.dev/#/patchset/20260802-blkdev-fixes-v1-0-a82fc549fd74%40columbia.edu?part=2 Assisted-by: Claude:claude-fable-5 Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Hannes Reinecke <hare@kernel.org>
An IOCB_ATOMIC direct write to a block device can silently lose its
torn-write guarantee in two ways:
1. blkdev_direct_write() turns an -EBUSY from page cache invalidation
into a 0 return, so the whole write is retried through
blkdev_buffered_write(), with no atomicity guarantee.
2. On a partial page pin, __blkdev_direct_IO_simple() and
__blkdev_direct_IO_async() submit what was pinned with REQ_ATOMIC
set and leave the rest to the buffered fallback.
The second case can be triggered deterministically. A 16K
pwritev2(RWF_ATOMIC) whose last page is PROT_NONE, on a scsi_debug
device with atomic_wr=1, completes short with only three of the four
pages written, violating RWF_ATOMIC semantics.
Fail the I/O instead. Return -EAGAIN when page cache invalidation fails
for IOCB_ATOMIC rather than retrying through the page cache, matching
__iomap_dio_rw(), which treats the failure as transient and lets the
caller retry. Release a short atomic pin and return -EFAULT before
submission, which is what a direct write already returns when none of
the buffer can be pinned. A sync atomic write can then never return
short with a remainder, so the buffered fallback is never reached.
ext4 has the same fallback and only warns in it. For block devices both
ways in can be detected before any I/O is submitted, so fail early instead.
Fixes: caf336f ("block: Add fops atomic write support")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Link: https://sashiko.dev/#/patchset/20260802-blkdev-fixes-v1-0-a82fc549fd74%40columbia.edu?part=2
Assisted-by: Claude:claude-fable-5
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
bio_iov_iter_align_down() drops trailing bvecs with unpin_user_page(), but a bvec built by iov_iter_extract_bvecs() can span several pages of one folio, each with its own pin. All but the first pin leak. The partially trimmed bvec has the same problem. Shrinking bv_len does not release the pins for the pages cut off by the trim, and __bio_release_pages() only unpins the pages bv_len still covers at completion. Both issues occur only with a logical block size above PAGE_SIZE and a large folio backing the user buffer. On a device with a 64K logical block size, an O_DIRECT pwritev() from a hugetlb mapping that ends 16K past a block boundary leaks one huge page per call, whether the remainder is its own bvec or the tail of a larger one. Unpin all pages of a dropped bvec with unpin_user_folio(), as __bio_release_pages() does, and unpin the pages trimmed off the last bvec as well. Fixes: 20a0e62 ("block: align the bio after building it") Assisted-by: Claude:claude-fable-5 Signed-off-by: Tal Zussman <tz2294@columbia.edu> Reviewed-by: Hannes Reinecke <hare@kernel.org>
Since commit 2729a60 ("block: don't silently ignore metadata for sync read/write"), blkdev_direct_IO() sends every IOCB_HAS_METADATA request to __blkdev_direct_IO(). Remove the now unreachable metadata mapping in __blkdev_direct_IO_async() and the unmap in its completion handler. No functional change. Assisted-by: Claude:claude-fable-5 Signed-off-by: Tal Zussman <tz2294@columbia.edu>
|
Upstream branch: a500db7 |
586f31d to
5766c33
Compare
Pull request for series with
subject: block device fixes for large block sizes, IOCB_NOWAIT, and direct I/O
version: 2
url: http://redsun45:8000/project/linux-block/list/?series=741