Skip to content

block device fixes for large block sizes, IOCB_NOWAIT, and direct I/O - #1189

Open
blktests-ci-kpd[bot] wants to merge 7 commits into
linus-master_basefrom
series/1153263=>linus-master
Open

block device fixes for large block sizes, IOCB_NOWAIT, and direct I/O#1189
blktests-ci-kpd[bot] wants to merge 7 commits into
linus-master_basefrom
series/1153263=>linus-master

Conversation

@blktests-ci-kpd

Copy link
Copy Markdown

Pull request for series with
subject: block device fixes for large block sizes, IOCB_NOWAIT, and direct I/O
version: 2
url: https://patchwork.kernel.org/project/linux-block/list/?series=1153263

@blktests-ci-kpd

Copy link
Copy Markdown
Author

Upstream branch: 502d457
series: https://patchwork.kernel.org/project/linux-block/list/?series=1153263
version: 2

@blktests-ci-kpd

Copy link
Copy Markdown
Author

Upstream branch: cf72cbb
series: https://patchwork.kernel.org/project/linux-block/list/?series=1153263
version: 2

@blktests-ci-kpd
blktests-ci-kpd Bot force-pushed the series/1153263=>linus-master branch from 2b6b335 to e42f6ba Compare August 29, 2026 06:49
@blktests-ci-kpd

Copy link
Copy Markdown
Author

Upstream branch: cee9395
series: https://patchwork.kernel.org/project/linux-block/list/?series=1153263
version: 2

@blktests-ci-kpd
blktests-ci-kpd Bot force-pushed the series/1153263=>linus-master branch from e42f6ba to 6a892bd Compare August 31, 2026 10:43
@blktests-ci-kpd

Copy link
Copy Markdown
Author

Upstream branch: 89a3129
series: https://patchwork.kernel.org/project/linux-block/list/?series=1153263
version: 2

@blktests-ci-kpd
blktests-ci-kpd Bot force-pushed the series/1153263=>linus-master branch from 6a892bd to 36ef117 Compare September 2, 2026 09:07
@blktests-ci-kpd

Copy link
Copy Markdown
Author

Upstream branch: 89a3129
series: https://patchwork.kernel.org/project/linux-block/list/?series=1153263
version: 2

@blktests-ci-kpd
blktests-ci-kpd Bot force-pushed the series/1153263=>linus-master branch from 36ef117 to c83c8fb Compare September 2, 2026 12:47
@blktests-ci-kpd

Copy link
Copy Markdown
Author

Upstream branch: 89a3129
series: https://patchwork.kernel.org/project/linux-block/list/?series=1153263
version: 2

@blktests-ci-kpd
blktests-ci-kpd Bot force-pushed the series/1153263=>linus-master branch from c83c8fb to 7100d43 Compare September 2, 2026 13:05
@blktests-ci-kpd

Copy link
Copy Markdown
Author

Upstream branch: 89a3129
series: https://patchwork.kernel.org/project/linux-block/list/?series=1153263
version: 2

@blktests-ci-kpd
blktests-ci-kpd Bot force-pushed the series/1153263=>linus-master branch from 7100d43 to 356e3a3 Compare September 2, 2026 13:22
@blktests-ci-kpd

Copy link
Copy Markdown
Author

Upstream branch: 89a3129
series: https://patchwork.kernel.org/project/linux-block/list/?series=1153263
version: 2

@blktests-ci-kpd
blktests-ci-kpd Bot force-pushed the series/1153263=>linus-master branch from 356e3a3 to d446a4d Compare September 2, 2026 14:15
@blktests-ci-kpd

Copy link
Copy Markdown
Author

Upstream branch: 89a3129
series: https://patchwork.kernel.org/project/linux-block/list/?series=1153263
version: 2

@blktests-ci-kpd
blktests-ci-kpd Bot force-pushed the series/1153263=>linus-master branch from d446a4d to 2a1ef94 Compare September 2, 2026 14:32
@blktests-ci-kpd

Copy link
Copy Markdown
Author

Upstream branch: bc35965
series: https://patchwork.kernel.org/project/linux-block/list/?series=1153263
version: 2

@blktests-ci-kpd
blktests-ci-kpd Bot force-pushed the series/1153263=>linus-master branch from 2a1ef94 to 9de233b Compare September 4, 2026 06:23
@blktests-ci-kpd

Copy link
Copy Markdown
Author

Upstream branch: df29080
series: https://patchwork.kernel.org/series/1153263/
version: 2

Pull request is NOT updated. Failed to apply https://patchwork.kernel.org/series/1153263/
error message:

Cmd('git') failed due to: exit code(128)
  cmdline: git am --3way
  stdout: 'Patch is empty.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To record the empty patch as an empty commit, run "git am --allow-empty".
To restore the original branch and stop patching, run "git am --abort".'

conflict:


@blktests-ci-kpd

Copy link
Copy Markdown
Author

Upstream branch: 28924df
series: https://patchwork.kernel.org/series/1153263/
version: 2

@blktests-ci-kpd

Copy link
Copy Markdown
Author

Upstream branch: 893e117
series: https://patchwork.kernel.org/series/1153263/
version: 2

@blktests-ci-kpd
blktests-ci-kpd Bot force-pushed the series/1153263=>linus-master branch from 31c5224 to b008303 Compare September 9, 2026 08:15
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>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
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
Reviewed-by: Hannes Reinecke <hare@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
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
Reviewed-by: Hannes Reinecke <hare@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
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
Reviewed-by: Hannes Reinecke <hare@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
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. Make bio_iov_iter_get_pages() release the pins
and return -EINVAL when a REQ_ATOMIC bio doesn't cover the whole
iterator, since an atomic write is submitted as a single bio and a
short one would be torn. That covers iomap as well, where a partially
unmapped buffer could trip the WARN_ON_ONCE() in
iomap_dio_bio_iter_one(). The async block device path currently sets
REQ_ATOMIC after pinning, so set it before.

Skip the buffered fallback in blkdev_write_iter() for IOCB_ATOMIC, as
it already does for IOCB_NOWAIT, so the -EBUSY case returns -EAGAIN and
the caller retries, matching __iomap_dio_rw().

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>
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 bvec_unpin(), and unpin the
pages trimmed off the last bvec as well. Move bvec_unpin() up and split
its page count into a helper so both sites share it.

Fixes: 20a0e62 ("block: align the bio after building it")
Assisted-by: Claude:claude-fable-5
Reviewed-by: Hannes Reinecke <hare@kernel.org>
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
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
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
@blktests-ci-kpd

Copy link
Copy Markdown
Author

Upstream branch: 893e117
series: https://patchwork.kernel.org/series/1161630/
version: 3

@blktests-ci-kpd blktests-ci-kpd Bot added V3 and removed V2 labels Sep 9, 2026
@blktests-ci-kpd
blktests-ci-kpd Bot force-pushed the series/1153263=>linus-master branch from b008303 to 6263511 Compare September 9, 2026 22:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant