Public bug reported:
Full disclosure: an LLM (Claude Sonnet 5.5) was used to debug/pin point
the root cause and write this bug report that I've reviewed.
**Summary**
On a LUKS2 volume formatted with 4096-byte sectors, `O_DIRECT` reads
into a buffer that is not 4096-byte aligned fail with `EIO`. The `dm`
device advertises `dma_alignment=4095`, so the kernel should reject such
buffers up front with `EINVAL`. There is no kernel message when it
happens. The same failure occurs on a file on ext4 over the LV and on
the raw `/dev/dm-N` device, so it is not ext4-specific.
**Impact**
`qemu-img` probes buffer alignment by reading at `buf+512`. It treats
any error other than `EINVAL` as "aligned", so with this kernel it
concludes 512-byte alignment is fine. Afterwards, `O_DIRECT` reads into
heap buffers that are 512-aligned but not page-aligned fail
intermittently. For example, `qemu-img convert -f qcow2 -O raw -T none
<image> out.raw` failed about 60% of the time with `error while reading
at byte N: Input/output error`. Without `-T none` (buffered), or with
`-T none -m 1`, it did not fail in 30 runs.
**Environment**
- Ubuntu 26.04, kernel `7.0.0-38-generic`
- NVMe disk with 512-byte logical sectors
- Stack: `nvme0n1p3` → LUKS2 `dm_crypt-0` with **4096-byte sectors** → LVM LV →
ext4 (root)
```
$ lsblk -t -s /dev/mapper/ubuntu--vg-ubuntu--lv
NAME ALIGNMENT MIN-IO OPT-IO PHY-SEC LOG-SEC ROTA SCHED
RQ-SIZE RA WSAME
ubuntu--vg-ubuntu--lv 0 4096 0 4096 4096 0
128 0B
└─dm_crypt-0 0 4096 0 4096 4096 0
128 0B
└─nvme0n1p3 0 512 0 512 512 0 none
1023 128 0B
└─nvme0n1 0 512 0 512 512 0 none
1023 128 0B
/sys/block/dm-1: logical_block_size=4096 dma_alignment=4095
virt_boundary_mask=4095
/sys/block/nvme0n1: logical_block_size=512 dma_alignment=3
virt_boundary_mask=4095
```
```
$ sudo cryptsetup status dm_crypt-0
/dev/mapper/dm_crypt-0 is active and is in use.
type: LUKS2
cipher: aes-xts-plain64
keysize: 512 [bits]
key location: keyring
device: /dev/nvme0n1p3
sector size: 4096 [bytes]
offset: 32768 [512-byte units] (16777216 [bytes])
size: 167739392 [512-byte units] (85882568704 [bytes])
mode: read/write
```
**Steps to reproduce** (read-only; run as root for the block device)
1. Save this as `dio-align.py`:
```python
#!/usr/bin/env python3
# Usage: dio-align.py <file-or-blockdev> (read-only)
import mmap, os, sys
fd = os.open(sys.argv[1], os.O_RDONLY | os.O_DIRECT)
buf = mmap.mmap(-1, 3 * 4096) # page-aligned base
for off in (0x0, 0x1, 0x200, 0x400, 0x800, 0xe00, 0x1000):
try:
r = os.preadv(fd, [memoryview(buf)[off:off + 4096]], 0)
except OSError as e:
r = f"errno {e.errno} ({os.strerror(e.errno)})"
print(f"buffer offset {off:#x}: {r}")
```
2. Run it against a file on the encrypted filesystem and against the
LV's block device:
```sh
dd if=/dev/urandom of=/var/tmp/dio-test bs=4096 count=4 status=none
python3 dio-align.py /var/tmp/dio-test
sudo python3 dio-align.py /dev/dm-1 # use the dm-N that backs the LV
(readlink -f /dev/mapper/ubuntu--vg-ubuntu--lv)
```
**Actual result** (identical for both targets)
```
buffer offset 0x0: 4096
buffer offset 0x1: errno 22 (Invalid argument)
buffer offset 0x200: errno 5 (Input/output error)
buffer offset 0x400: errno 5 (Input/output error)
buffer offset 0x800: errno 5 (Input/output error)
buffer offset 0xe00: errno 5 (Input/output error)
buffer offset 0x1000: 4096
```
**Expected result:** `EINVAL` (22) for every offset other than `0x0` and
`0x1000`. `dm-N` advertises `dma_alignment=4095`, so a buffer that is
not 4096-byte aligned should be rejected at submission, as the `0x1`
case is.
**Analysis**
In mainline `drivers/md/dm-crypt.c`, `crypt_io_hints()` sets
`limits->dma_alignment = limits->logical_block_size - 1`, and
`crypt_convert_block_skcipher()` returns `-EIO` when a `bio_vec` length
is not a multiple of `cc->sector_size` ("Reject unexpected unaligned
bio"). A 4 KiB buffer at `+0x200` spans two pages and becomes segments
of 3584 and 512 bytes, which `dm-crypt` rejects. Page-aligned buffers
(`0x0`, `0x1000`) are a single 4096-byte segment and succeed. The
direct-I/O entry check evidently doesn't enforce the advertised 4095
alignment on this kernel, so these buffers get as far as `dm-crypt`.
**Additional information**
- `dmesg` shows no message when the `EIO` happens. All ZFS and NVMe pools are
healthy.
- Buffered I/O is unaffected.
- A `strace` of `qemu-img` shows the probe sequence (`buf+1` → `EINVAL`,
`buf+0x200` → `EIO`) and later `pread64(..., 0x…e00, 65536, …) = -1 EIO`:
The probe sequence:
```
$ sed -n '250,270p' ~/qemu-img-eio.strace
244730 openat(AT_FDCWD</>, "/proc/sys/vm/max_map_count", O_RDONLY|O_CLOEXEC) =
3</proc/sys/vm/max_map_count>
244730 io_uring_setup(128, {flags=IORING_SETUP_NO_SQARRAY, sq_thread_cpu=0,
sq_thread_idle=0, sq_entries=128, cq_entries=256,
features=IORING_FEAT_SINGLE_MMAP|IORING_FEAT_NODROP|IORING_FEAT_SUBMIT_STABLE|IORING_FEAT_RW_CUR_POS|IORING_FEAT_CUR_PERSONALITY|IORING_FEAT_FAST_POLL|IORING_FEAT_POLL_32BITS|IORING_FEAT_SQPOLL_NONFIXED|IORING_FEAT_EXT_ARG|IORING_FEAT_NATIVE_WORKERS|IORING_FEAT_RSRC_TAGS|IORING_FEAT_CQE_SKIP|IORING_FEAT_LINKED_FILE|IORING_FEAT_REG_REG_RING|IORING_FEAT_RECVSEND_BUNDLE|IORING_FEAT_MIN_TIMEOUT|IORING_FEAT_RW_ATTR|IORING_FEAT_NO_IOWAIT,
sq_off={head=0, tail=4, ring_mask=16, ring_entries=24, flags=36, dropped=32,
array=0, user_addr=0}, cq_off={head=8, tail=12, ring_mask=20, ring_entries=28,
overflow=44, cqes=64, flags=40, user_addr=0}}) = 5<anon_inode:[io_uring]>
244730 io_uring_setup(128, {flags=IORING_SETUP_NO_SQARRAY, sq_thread_cpu=0,
sq_thread_idle=0, sq_entries=128, cq_entries=256,
features=IORING_FEAT_SINGLE_MMAP|IORING_FEAT_NODROP|IORING_FEAT_SUBMIT_STABLE|IORING_FEAT_RW_CUR_POS|IORING_FEAT_CUR_PERSONALITY|IORING_FEAT_FAST_POLL|IORING_FEAT_POLL_32BITS|IORING_FEAT_SQPOLL_NONFIXED|IORING_FEAT_EXT_ARG|IORING_FEAT_NATIVE_WORKERS|IORING_FEAT_RSRC_TAGS|IORING_FEAT_CQE_SKIP|IORING_FEAT_LINKED_FILE|IORING_FEAT_REG_REG_RING|IORING_FEAT_RECVSEND_BUNDLE|IORING_FEAT_MIN_TIMEOUT|IORING_FEAT_RW_ATTR|IORING_FEAT_NO_IOWAIT,
sq_off={head=0, tail=4, ring_mask=16, ring_entries=24, flags=36, dropped=32,
array=0, user_addr=0}, cq_off={head=8, tail=12, ring_mask=20, ring_entries=28,
overflow=44, cqes=64, flags=40, user_addr=0}}) = 7<anon_inode:[io_uring]>
244730 openat(AT_FDCWD</>,
"/var/snap/lxd/common/lxd/images/881c273c21f4b25aee6b336e8af993166766b6c263032f9db7a61bdb037bc6a6.rootfs",
O_RDONLY|O_NONBLOCK|O_CLOEXEC) =
9</var/snap/lxd/common/lxd/images/881c273c21f4b25aee6b336e8af993166766b6c263032f9db7a61bdb037bc6a6.rootfs>
244730 openat(AT_FDCWD</>, "/dev/urandom", O_RDONLY|O_CLOEXEC) = 9</dev/urandom>
244730 openat(AT_FDCWD</>, "/dev/null", O_RDWR) = 9</dev/null>
244730 openat(AT_FDCWD</>,
"/var/snap/lxd/common/lxd/images/881c273c21f4b25aee6b336e8af993166766b6c263032f9db7a61bdb037bc6a6.rootfs",
O_RDONLY|O_DIRECT|O_CLOEXEC) =
9</var/snap/lxd/common/lxd/images/881c273c21f4b25aee6b336e8af993166766b6c263032f9db7a61bdb037bc6a6.rootfs>
244730
pread64(9</var/snap/lxd/common/lxd/images/881c273c21f4b25aee6b336e8af993166766b6c263032f9db7a61bdb037bc6a6.rootfs>,
0x635625f7d000, 1, 0) = -1 EINVAL (Invalid argument)
244730
pread64(9</var/snap/lxd/common/lxd/images/881c273c21f4b25aee6b336e8af993166766b6c263032f9db7a61bdb037bc6a6.rootfs>,
0x635625f7d000, 512, 0) = -1 EINVAL (Invalid argument)
244730
pread64(9</var/snap/lxd/common/lxd/images/881c273c21f4b25aee6b336e8af993166766b6c263032f9db7a61bdb037bc6a6.rootfs>,
0x635625f7d000, 1024, 0) = -1 EINVAL (Invalid argument)
244730
pread64(9</var/snap/lxd/common/lxd/images/881c273c21f4b25aee6b336e8af993166766b6c263032f9db7a61bdb037bc6a6.rootfs>,
0x635625f7d000, 2048, 0) = -1 EINVAL (Invalid argument)
244730
pread64(9</var/snap/lxd/common/lxd/images/881c273c21f4b25aee6b336e8af993166766b6c263032f9db7a61bdb037bc6a6.rootfs>,
"QFI\373\0\0\0\3\0\0\0\0\0\0\0\0"..., 4096, 0) = 4096
244730
pread64(9</var/snap/lxd/common/lxd/images/881c273c21f4b25aee6b336e8af993166766b6c263032f9db7a61bdb037bc6a6.rootfs>,
0x635625f7d001, 4096, 0) = -1 EINVAL (Invalid argument)
244730
pread64(9</var/snap/lxd/common/lxd/images/881c273c21f4b25aee6b336e8af993166766b6c263032f9db7a61bdb037bc6a6.rootfs>,
0x635625f7d200, 4096, 0) = -1 EIO (Input/output error)
244730 io_uring_enter(7<anon_inode:[io_uring]>, 1, 0, 0, NULL, 8) = 1
244730 io_uring_enter(7<anon_inode:[io_uring]>, 0, 1, IORING_ENTER_GETEVENTS,
NULL, 8 <unfinished ...>
244732
pread64(9</var/snap/lxd/common/lxd/images/881c273c21f4b25aee6b336e8af993166766b6c263032f9db7a61bdb037bc6a6.rootfs>,
"QFI\373\0\0\0\3\0\0\0\0\0\0\0\0"..., 4096, 0) = 4096
244730 <... io_uring_enter resumed>) = 0
244730 io_uring_enter(7<anon_inode:[io_uring]>, 1, 0, 0, NULL, 8 <unfinished
...>
244732
pread64(9</var/snap/lxd/common/lxd/images/881c273c21f4b25aee6b336e8af993166766b6c263032f9db7a61bdb037bc6a6.rootfs>
<unfinished ...>
```
Failing read
```
$ grep -n 'EIO' ~/qemu-img-eio.strace
264:244730
pread64(9</var/snap/lxd/common/lxd/images/881c273c21f4b25aee6b336e8af993166766b6c263032f9db7a61bdb037bc6a6.rootfs>,
0x635625f7d200, 4096, 0) = -1 EIO (Input/output error)
1714:244735 <... pread64 resumed>, 0x635626084e00, 65536, 25493504) = -1 EIO
(Input/output error)
```
ProblemType: Bug
DistroRelease: Ubuntu 26.04
Package: linux-image-7.0.0-38-generic 7.0.0-38.38
ProcVersionSignature: Ubuntu 7.0.0-38.38-generic 7.0.14
Uname: Linux 7.0.0-38-generic x86_64
NonfreeKernelModules: zfs
ApportVersion: 2.34.1-0ubuntu0.1
Architecture: amd64
CasperMD5CheckResult: pass
CurrentDesktop: ubuntu:GNOME
Date: Sat Oct 24 18:46:16 2026
InstallationDate: Installed on 2026-04-25 (182 days ago)
InstallationMedia: Ubuntu 26.04 "Resolute Raccoon" - Release amd64 (20260423.1)
MachineType: LENOVO 21RVCTO1WW
ProcFB: 0 amdgpudrmfb
ProcKernelCmdLine: BOOT_IMAGE=/vmlinuz-7.0.0-38-generic
root=/dev/mapper/ubuntu--vg-ubuntu--lv ro quiet splash debugfs=off
nmi_watchdog=0 nohibernate nr_cpus=24 panic=300 8250.nr_uarts=1 vsyscall=none
SourcePackage: linux
UpgradeStatus: No upgrade log present (probably fresh install)
dmi.bios.date: 04/16/2025
dmi.bios.release: 1.10
dmi.bios.vendor: LENOVO
dmi.bios.version: R2XET30W (1.10 )
dmi.board.asset.tag: Not Available
dmi.board.name: 21RVCTO1WW
dmi.board.vendor: LENOVO
dmi.board.version: NO DPK
dmi.chassis.asset.tag: No Asset Tag
dmi.chassis.type: 10
dmi.chassis.vendor: LENOVO
dmi.chassis.version: None
dmi.ec.firmware.release: 1.7
dmi.modalias:
dmi:bvnLENOVO:bvrR2XET30W(1.10):bd04/16/2025:br1.10:efr1.7:svnLENOVO:pn21RVCTO1WW:pvrThinkPadP14sGen6AMD:rvnLENOVO:rn21RVCTO1WW:rvrNODPK:cvnLENOVO:ct10:cvrNone:skuLENOVO_MT_21RV_BU_Think_FM_ThinkPadP14sGen6AMD:pfaThinkPadP14sGen6AMD:
dmi.product.family: ThinkPad P14s Gen 6 AMD
dmi.product.name: 21RVCTO1WW
dmi.product.sku: LENOVO_MT_21RV_BU_Think_FM_ThinkPad P14s Gen 6 AMD
dmi.product.version: ThinkPad P14s Gen 6 AMD
dmi.sys.vendor: LENOVO
** Affects: linux (Ubuntu)
Importance: Undecided
Status: New
** Tags: amd64 apport-bug resolute wayland-session
--
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2172145
Title:
`O_DIRECT` read into a 512-aligned (not 4096-aligned) buffer returns
`EIO` instead of `EINVAL` on a 4096-byte-sector LUKS volume (`dm-
crypt`)
To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2172145/+subscriptions
--
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs