[PATCH 2/2] fs: allow positional I/O on files with unsigned offsets
From: Stian Halseth
Date: Thu Sep 17 2026 - 11:51:45 EST
pread64(), pwrite64(), preadv() and pwritev() reject a negative position
with -EINVAL before looking up the file, so FOP_UNSIGNED_OFFSET is never
consulted. lseek() followed by read() on the same descriptor accepts
the position, and rw_verify_area() already handles it, overflow check
included. Only the positional syscall wrappers reject it up front.
Check the position after the file lookup and let a negative value
through for files with unsigned offsets, as vfs_setpos_cookie() and
rw_verify_area() do. Behavior for every other file is unchanged.
On sparc64 userspace is mapped above 2^63, so pread() on /proc/PID/mem
always failed with EINVAL. pldd(1) could not read a target's memory
because of this, and gdb carries a userspace workaround for the same
thing (PR gdb/30525).
Signed-off-by: Stian Halseth <stian@xxxxxx>
---
fs/read_write.c | 36 ++++++++++++++++++------------------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/fs/read_write.c b/fs/read_write.c
index 36f3d8e..e46e814 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -753,13 +753,13 @@ SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
ssize_t ksys_pread64(unsigned int fd, char __user *buf, size_t count,
loff_t pos)
{
- if (pos < 0)
- return -EINVAL;
-
CLASS(fd, f)(fd);
if (fd_empty(f))
return -EBADF;
+ if (pos < 0 && !unsigned_offsets(fd_file(f)))
+ return -EINVAL;
+
if (fd_file(f)->f_mode & FMODE_PREAD)
return vfs_read(fd_file(f), buf, count, &pos);
@@ -783,13 +783,13 @@ COMPAT_SYSCALL_DEFINE5(pread64, unsigned int, fd, char __user *, buf,
ssize_t ksys_pwrite64(unsigned int fd, const char __user *buf,
size_t count, loff_t pos)
{
- if (pos < 0)
- return -EINVAL;
-
CLASS(fd, f)(fd);
if (fd_empty(f))
return -EBADF;
+ if (pos < 0 && !unsigned_offsets(fd_file(f)))
+ return -EINVAL;
+
if (fd_file(f)->f_mode & FMODE_PWRITE)
return vfs_write(fd_file(f), buf, count, &pos);
@@ -1123,14 +1123,14 @@ static ssize_t do_preadv(unsigned long fd, const struct iovec __user *vec,
{
ssize_t ret = -EBADF;
- if (pos < 0)
- return -EINVAL;
-
CLASS(fd, f)(fd);
if (!fd_empty(f)) {
- ret = -ESPIPE;
- if (fd_file(f)->f_mode & FMODE_PREAD)
- ret = vfs_readv(fd_file(f), vec, vlen, &pos, flags);
+ ret = -EINVAL;
+ if (pos >= 0 || unsigned_offsets(fd_file(f))) {
+ ret = -ESPIPE;
+ if (fd_file(f)->f_mode & FMODE_PREAD)
+ ret = vfs_readv(fd_file(f), vec, vlen, &pos, flags);
+ }
}
if (ret > 0)
@@ -1144,14 +1144,14 @@ static ssize_t do_pwritev(unsigned long fd, const struct iovec __user *vec,
{
ssize_t ret = -EBADF;
- if (pos < 0)
- return -EINVAL;
-
CLASS(fd, f)(fd);
if (!fd_empty(f)) {
- ret = -ESPIPE;
- if (fd_file(f)->f_mode & FMODE_PWRITE)
- ret = vfs_writev(fd_file(f), vec, vlen, &pos, flags);
+ ret = -EINVAL;
+ if (pos >= 0 || unsigned_offsets(fd_file(f))) {
+ ret = -ESPIPE;
+ if (fd_file(f)->f_mode & FMODE_PWRITE)
+ ret = vfs_writev(fd_file(f), vec, vlen, &pos, flags);
+ }
}
if (ret > 0)
--
2.43.0