* [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O
@ 2026-09-17 15:16 Stian Halseth
2026-09-17 15:16 ` [PATCH 1/2] fs: fix llseek() result for files with unsigned offsets Stian Halseth
` (4 more replies)
0 siblings, 5 replies; 8+ messages in thread
From: Stian Halseth @ 2026-09-17 15:16 UTC (permalink / raw)
To: brauner, viro
Cc: jack, linux-fsdevel, linux-kernel, sparclinux, schwab, fweimer,
Stian Halseth
Two generic VFS bugs found while fixing pldd(1) on sparc64, where
userspace is mapped above 2^63 so file positions on /proc/PID/mem have
the top bit set.
FOP_UNSIGNED_OFFSET declares a file's offsets unsigned, and vfs_llseek(),
rw_verify_area() and the mmap path honor it. Two sets of syscall
wrappers do not:
1. sys_llseek() takes a negative result from vfs_llseek() for an error
and returns it truncated to int without filling *result.
2. pread64/pwrite64/preadv/pwritev reject a negative position before
looking up the file.
Both patches only change behavior for a file with FOP_UNSIGNED_OFFSET
and a position with the top bit set. Today that is /proc/PID/mem,
/dev/mem, the sparc ADI driver and the DRM/accel device files; for all
of them lseek() followed by read() at that position already works, so
the wrappers now match it.
Tested on an UltraSPARC T4: pread(), preadv(), pwrite(), pwritev() and
lseek() on /proc/PID/mem at positions above 2^63 return the correct data
and offset; a negative position on a regular file or a pipe still fails
with EINVAL and position 0 on a pipe with ESPIPE, as before; and the
stock pldd(1) works again. On other architectures the change is a no-op
for every file without FOP_UNSIGNED_OFFSET, and userspace addresses
never set the top bit, so the new paths are only reachable by passing
a bogus position to /proc/PID/mem or /dev/mem, which then fails in the
driver instead of the wrapper.
The glibc side is https://sourceware.org/bugzilla/show_bug.cgi?id=34641;
Stian Halseth (2):
fs: fix llseek() result for files with unsigned offsets
fs: allow positional I/O on files with unsigned offsets
fs/read_write.c | 38 +++++++++++++++++++-------------------
1 file changed, 19 insertions(+), 19 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 1/2] fs: fix llseek() result for files with unsigned offsets
2026-09-17 15:16 [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O Stian Halseth
@ 2026-09-17 15:16 ` Stian Halseth
2026-09-17 15:16 ` [PATCH 2/2] fs: allow positional I/O on " Stian Halseth
` (3 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Stian Halseth @ 2026-09-17 15:16 UTC (permalink / raw)
To: brauner, viro
Cc: jack, linux-fsdevel, linux-kernel, sparclinux, schwab, fweimer,
Stian Halseth
sys_llseek() treats a negative result from vfs_llseek() as an error and
returns it truncated to int instead of storing it in *result. For a
file with FOP_UNSIGNED_OFFSET a valid offset can have the top bit set,
so the seek succeeds but userspace gets a bogus return value and an
untouched result buffer. ksys_lseek() has no such check and returns
the offset as is.
Store the offset when the file has unsigned offsets too, still treating
a value in the errno range as an error so a real failure from
->llseek() is reported.
On sparc64, where userspace is mapped above 2^63 and glibc's lseek()
uses _llseek, this makes lseek() on /proc/PID/mem return the offset it
seeked to instead of its low 32 bits.
Signed-off-by: Stian Halseth <stian@itx.no>
---
fs/read_write.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/read_write.c b/fs/read_write.c
index e8c14e2..36f3d8e 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -441,7 +441,7 @@ SYSCALL_DEFINE5(llseek, unsigned int, fd, unsigned long, offset_high,
whence);
retval = (int)offset;
- if (offset >= 0) {
+ if (offset >= 0 || (unsigned_offsets(fd_file(f)) && offset < -MAX_ERRNO)) {
retval = -EFAULT;
if (!copy_to_user(result, &offset, sizeof(offset)))
retval = 0;
--
2.43.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 2/2] fs: allow positional I/O on files with unsigned offsets
2026-09-17 15:16 [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O Stian Halseth
2026-09-17 15:16 ` [PATCH 1/2] fs: fix llseek() result for files with unsigned offsets Stian Halseth
@ 2026-09-17 15:16 ` Stian Halseth
2026-09-17 16:01 ` [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O Florian Weimer
` (2 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Stian Halseth @ 2026-09-17 15:16 UTC (permalink / raw)
To: brauner, viro
Cc: jack, linux-fsdevel, linux-kernel, sparclinux, schwab, fweimer,
Stian Halseth
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@itx.no>
---
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
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O
2026-09-17 15:16 [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O Stian Halseth
2026-09-17 15:16 ` [PATCH 1/2] fs: fix llseek() result for files with unsigned offsets Stian Halseth
2026-09-17 15:16 ` [PATCH 2/2] fs: allow positional I/O on " Stian Halseth
@ 2026-09-17 16:01 ` Florian Weimer
2026-09-17 16:45 ` Stian Halseth
2026-10-18 9:33 ` Stian Halseth
2026-10-21 10:37 ` Christian Brauner
4 siblings, 1 reply; 8+ messages in thread
From: Florian Weimer @ 2026-09-17 16:01 UTC (permalink / raw)
To: Stian Halseth
Cc: brauner, viro, jack, linux-fsdevel, linux-kernel, sparclinux, schwab
* Stian Halseth:
> Tested on an UltraSPARC T4: pread(), preadv(), pwrite(), pwritev() and
> lseek() on /proc/PID/mem at positions above 2^63 return the correct data
> and offset; a negative position on a regular file or a pipe still fails
> with EINVAL and position 0 on a pipe with ESPIPE, as before; and the
> stock pldd(1) works again. On other architectures the change is a no-op
> for every file without FOP_UNSIGNED_OFFSET, and userspace addresses
> never set the top bit, so the new paths are only reachable by passing
> a bogus position to /proc/PID/mem or /dev/mem, which then fails in the
> driver instead of the wrapper.
For lseek, aren't some file offsets (the top 4095 bytes or so just
before 2**64) ambiguous as error indicators? You would have to use
_llseek when accessing /proc/PID/mem.
Thanks,
Florian
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O
2026-09-17 16:01 ` [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O Florian Weimer
@ 2026-09-17 16:45 ` Stian Halseth
2026-09-17 18:39 ` Stian Halseth
0 siblings, 1 reply; 8+ messages in thread
From: Stian Halseth @ 2026-09-17 16:45 UTC (permalink / raw)
To: Florian Weimer
Cc: brauner, viro, jack, linux-fsdevel, linux-kernel, sparclinux, schwab
[-- Attachment #1: Type: text/plain, Size: 1989 bytes --]
Hi,
On Thu, 2026-09-17 at 18:01 +0200, Florian Weimer wrote:
> * Stian Halseth:
>
> > Tested on an UltraSPARC T4: pread(), preadv(), pwrite(), pwritev()
> > and
> > lseek() on /proc/PID/mem at positions above 2^63 return the correct
> > data
> > and offset; a negative position on a regular file or a pipe still
> > fails
> > with EINVAL and position 0 on a pipe with ESPIPE, as before; and
> > the
> > stock pldd(1) works again. On other architectures the change is a
> > no-op
> > for every file without FOP_UNSIGNED_OFFSET, and userspace addresses
> > never set the top bit, so the new paths are only reachable by
> > passing
> > a bogus position to /proc/PID/mem or /dev/mem, which then fails in
> > the
> > driver instead of the wrapper.
>
> For lseek, aren't some file offsets (the top 4095 bytes or so just
> before 2**64) ambiguous as error indicators? You would have to use
> _llseek when accessing /proc/PID/mem.
Yes, for lseek, anything in the top MAX_ERRNO bytes below 2^64 cannot
be separated from -errno.
For that reason _llseek is the interface that can be exact.
Patch 1 ensures that _llseek works for everything except that 4095-byte
window. The patch does _not_ change the fact that _llseek can't return
an offset in said window.
I think fixing the window requires llseek to report errors separately
from the offset. A bigger change, I would need some feedback before
attempting to implement that.
That being said, I _think_ the window is unreachable in practice, and
that no architecture maps user memory there.
I could add a sentense to patch 1 noting the limitation, or look at the
larger change if the VFS maintainers think it's worth it.
On the glibc side: 64-bit glibc uses lseek except on sparc64 and ppc64,
which use _llseek, so those two get the exact result with patch 1,
and the rest carry the lseek ambiguity for that top window regardless.
Best regards,
Stian
>
> Thanks,
> Florian
>
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O
2026-09-17 16:45 ` Stian Halseth
@ 2026-09-17 18:39 ` Stian Halseth
0 siblings, 0 replies; 8+ messages in thread
From: Stian Halseth @ 2026-09-17 18:39 UTC (permalink / raw)
To: Florian Weimer
Cc: brauner, viro, jack, linux-fsdevel, linux-kernel, sparclinux, schwab
[-- Attachment #1: Type: text/plain, Size: 2425 bytes --]
Hi again, a small clarification to my last reply.
On Thu, 2026-09-17 at 18:45 +0200, Stian Halseth wrote:
> Hi,
>
> On Thu, 2026-09-17 at 18:01 +0200, Florian Weimer wrote:
> > * Stian Halseth:
> >
> > > Tested on an UltraSPARC T4: pread(), preadv(), pwrite(),
> > > pwritev()
> > > and
> > > lseek() on /proc/PID/mem at positions above 2^63 return the
> > > correct
> > > data
> > > and offset; a negative position on a regular file or a pipe still
> > > fails
> > > with EINVAL and position 0 on a pipe with ESPIPE, as before; and
> > > the
> > > stock pldd(1) works again. On other architectures the change is
> > > a
> > > no-op
> > > for every file without FOP_UNSIGNED_OFFSET, and userspace
> > > addresses
> > > never set the top bit, so the new paths are only reachable by
> > > passing
> > > a bogus position to /proc/PID/mem or /dev/mem, which then fails
> > > in
> > > the
> > > driver instead of the wrapper.
> >
> > For lseek, aren't some file offsets (the top 4095 bytes or so just
> > before 2**64) ambiguous as error indicators? You would have to use
> > _llseek when accessing /proc/PID/mem.
>
> Yes, for lseek, anything in the top MAX_ERRNO bytes below 2^64 cannot
> be separated from -errno.
>
> For that reason _llseek is the interface that can be exact.
>
> Patch 1 ensures that _llseek works for everything except that 4095-
> byte
> window. The patch does _not_ change the fact that _llseek can't
> return
> an offset in said window.
>
> I think fixing the window requires llseek to report errors separately
> from the offset. A bigger change, I would need some feedback before
> attempting to implement that.
>
> That being said, I _think_ the window is unreachable in practice, and
> that no architecture maps user memory there.
>
> I could add a sentense to patch 1 noting the limitation, or look at
> the
> larger change if the VFS maintainers think it's worth it.
>
> On the glibc side: 64-bit glibc uses lseek except on sparc64 and
> ppc64,
> which use _llseek, so those two get the exact result with patch 1,
> and the rest carry the lseek ambiguity for that top window
> regardless.
Just to clarify:
With patch 1, _llseek for ppc64 and sparc64 behaves just like lseek for
the other arches, correct for every offset outside of that top window.
>
> Best regards,
> Stian
>
> >
> > Thanks,
> > Florian
> >
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O
2026-09-17 15:16 [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O Stian Halseth
` (2 preceding siblings ...)
2026-09-17 16:01 ` [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O Florian Weimer
@ 2026-10-18 9:33 ` Stian Halseth
2026-10-21 10:37 ` Christian Brauner
4 siblings, 0 replies; 8+ messages in thread
From: Stian Halseth @ 2026-10-18 9:33 UTC (permalink / raw)
To: brauner, viro
Cc: jack, linux-fsdevel, linux-kernel, sparclinux, schwab, fweimer
[-- Attachment #1: Type: text/plain, Size: 2332 bytes --]
Ping. The series still applies cleanly to current master (7.3-rc7).
Since posting it has run on an UltraSPARC T4-1 (7.2.y) and a SPARC T7-1
(7.3-rc5) as the production kernel for a month. Is there anything you
would like changed before it can be picked up?
https://lore.kernel.org/linux-fsdevel/20260917151614.376867-1-stian@itx.no/
/Stian
On Thu, 2026-09-17 at 17:16 +0200, Stian Halseth wrote:
> Two generic VFS bugs found while fixing pldd(1) on sparc64, where
> userspace is mapped above 2^63 so file positions on /proc/PID/mem
> have
> the top bit set.
>
> FOP_UNSIGNED_OFFSET declares a file's offsets unsigned, and
> vfs_llseek(),
> rw_verify_area() and the mmap path honor it. Two sets of syscall
> wrappers do not:
>
> 1. sys_llseek() takes a negative result from vfs_llseek() for an
> error
> and returns it truncated to int without filling *result.
> 2. pread64/pwrite64/preadv/pwritev reject a negative position
> before
> looking up the file.
>
> Both patches only change behavior for a file with FOP_UNSIGNED_OFFSET
> and a position with the top bit set. Today that is /proc/PID/mem,
> /dev/mem, the sparc ADI driver and the DRM/accel device files; for
> all
> of them lseek() followed by read() at that position already works, so
> the wrappers now match it.
>
> Tested on an UltraSPARC T4: pread(), preadv(), pwrite(), pwritev()
> and
> lseek() on /proc/PID/mem at positions above 2^63 return the correct
> data
> and offset; a negative position on a regular file or a pipe still
> fails
> with EINVAL and position 0 on a pipe with ESPIPE, as before; and the
> stock pldd(1) works again. On other architectures the change is a
> no-op
> for every file without FOP_UNSIGNED_OFFSET, and userspace addresses
> never set the top bit, so the new paths are only reachable by passing
> a bogus position to /proc/PID/mem or /dev/mem, which then fails in
> the
> driver instead of the wrapper.
>
> The glibc side is
> https://sourceware.org/bugzilla/show_bug.cgi?id=34641;
>
> Stian Halseth (2):
> fs: fix llseek() result for files with unsigned offsets
> fs: allow positional I/O on files with unsigned offsets
>
> fs/read_write.c | 38 +++++++++++++++++++-------------------
> 1 file changed, 19 insertions(+), 19 deletions(-)
>
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O
2026-09-17 15:16 [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O Stian Halseth
` (3 preceding siblings ...)
2026-10-18 9:33 ` Stian Halseth
@ 2026-10-21 10:37 ` Christian Brauner
4 siblings, 0 replies; 8+ messages in thread
From: Christian Brauner @ 2026-10-21 10:37 UTC (permalink / raw)
To: viro, Stian Halseth
Cc: jack, linux-fsdevel, linux-kernel, sparclinux, schwab, fweimer
On Thu, 17 Sep 2026 17:16:12 +0200, Stian Halseth wrote:
> Two generic VFS bugs found while fixing pldd(1) on sparc64, where
> userspace is mapped above 2^63 so file positions on /proc/PID/mem have
> the top bit set.
>
> FOP_UNSIGNED_OFFSET declares a file's offsets unsigned, and vfs_llseek(),
> rw_verify_area() and the mmap path honor it. Two sets of syscall
> wrappers do not:
>
> [...]
Applied to the vfs.fixes branch of the vfs/vfs.git tree.
Patches in the vfs.fixes branch should appear in linux-next soon.
Please report any outstanding bugs that were missed during review in a
new review to the original patch series allowing us to drop it.
It's encouraged to provide Acked-bys and Reviewed-bys even though the
patch has now been applied. If possible patch trailers will be updated.
Note that commit hashes shown below are subject to change due to rebase,
trailer updates or similar. If in doubt, please check the listed branch.
tree: https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
branch: vfs.fixes
[1/2] fs: fix llseek() result for files with unsigned offsets
https://git.kernel.org/vfs/vfs/c/34e5caa86b12
[2/2] fs: allow positional I/O on files with unsigned offsets
https://git.kernel.org/vfs/vfs/c/8c5ba5aa0278
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-10-21 10:37 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-17 15:16 [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O Stian Halseth
2026-09-17 15:16 ` [PATCH 1/2] fs: fix llseek() result for files with unsigned offsets Stian Halseth
2026-09-17 15:16 ` [PATCH 2/2] fs: allow positional I/O on " Stian Halseth
2026-09-17 16:01 ` [PATCH 0/2] fs: honor FOP_UNSIGNED_OFFSET in llseek and positional I/O Florian Weimer
2026-09-17 16:45 ` Stian Halseth
2026-09-17 18:39 ` Stian Halseth
2026-10-18 9:33 ` Stian Halseth
2026-10-21 10:37 ` Christian Brauner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®