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

Reply via email to