When reading to / writing from non-growable exports, we cap the I/O size
by `offset - blk_len`. This will underflow for accesses that are
completely past the disk end.
Check and handle that case explicitly.
This is also enough to ensure that `offset + size` will not overflow;
blk_len is int64_t, offset is uint32_t, `offset < blk_len`, so from
`INT64_MAX + UINT32_MAX < UINT64_MAX` it follows that `offset + size`
cannot overflow.
Just one catch: We have to allow write accesses to growable exports past
the EOF, so then we cannot rely on `offset < blk_len`, but have to
verify explicitly that `offset + size` does not overflow.
The negative consequences of not having this commit are luckily limited
because blk_pread() and blk_pwrite() will reject post-EOF requests
anyway, so a `size` underflow post-EOF will just result in an I/O error.
So:
- Post-EOF reads will incorrectly result in I/O errors instead of just
0-length reads. We will also attempt to allocate a very large buffer,
which is wrong and not good, but not terrible.
- Post-EOF writes on non-growable exports will result in I/O errors
instead of 0-length writes (which generally indicate ENOSPC).
- Post-EOF writes on growable exports can theoretically overflow on EOF
and truncate the export down to a much too small size, but in
practice, FUSE will never send an offset greater than signed INT_MAX,
preventing a uint64_t overflow. (fuse_write_args_fill() in the kernel
uses loff_t for the offset, which is signed.)
Signed-off-by: Hanna Czenczek <hreitz@redhat.com>
Message-ID: <20260309150856.26800-15-hreitz@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>