[PATCH v1 1/2] fs: reject negative offsets for SEEK_DATA and SEEK_HOLE in default_llseek()

From: Siddharth Chintamaneni

Date: Sun Nov 01 2026 - 01:01:41 EST


In default_llseek(), the range checks for SEEK_DATA and SEEK_HOLE compare
the signed loff_t offset directly against inode->i_size:

if (offset >= inode->i_size) {
retval = -ENXIO;
goto out;
}

When a negative offset is supplied to SEEK_HOLE (e.g. lseek(fd, -10,
SEEK_HOLE) on a sysctl file in /proc/sys), the signed comparison evaluates
to false and execution proceeds to:

offset = inode->i_size;

This overwrites the negative offset with a non-negative value before the
final `if (offset >= 0 || unsigned_offsets(file))` check, causing
default_llseek() to update file->f_pos and return inode->i_size instead of
failing with -ENXIO. Similarly, for SEEK_DATA on a file with
FOP_UNSIGNED_OFFSET, a negative offset bypasses the range check and
succeeds via unsigned_offsets(file).

Read the inode size once via i_size_read(inode) after acquiring the inode
lock and compare (unsigned long long)offset >= eof for both SEEK_DATA and
SEEK_HOLE, matching must_set_pos().

Fixes: 982d816581ee ("fs: add SEEK_HOLE and SEEK_DATA flags")
Signed-off-by: Siddharth Chintamaneni <sidchintamaneni@xxxxxxxxx>
---
fs/read_write.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/fs/read_write.c b/fs/read_write.c
index 676728310899..86ce8cea10d7 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -333,13 +333,15 @@ loff_t default_llseek(struct file *file, loff_t offset, int whence)
{
struct inode *inode = file_inode(file);
loff_t retval;
+ loff_t eof;

retval = inode_lock_killable(inode);
if (retval)
return retval;
+ eof = i_size_read(inode);
switch (whence) {
case SEEK_END:
- offset += i_size_read(inode);
+ offset += eof;
break;
case SEEK_CUR:
if (offset == 0) {
@@ -354,7 +356,7 @@ loff_t default_llseek(struct file *file, loff_t offset, int whence)
* long as offset isn't at the end of the file then the
* offset is data.
*/
- if (offset >= inode->i_size) {
+ if ((unsigned long long)offset >= eof) {
retval = -ENXIO;
goto out;
}
@@ -365,11 +367,11 @@ loff_t default_llseek(struct file *file, loff_t offset, int whence)
* as long as offset isn't i_size or larger, return
* i_size.
*/
- if (offset >= inode->i_size) {
+ if ((unsigned long long)offset >= eof) {
retval = -ENXIO;
goto out;
}
- offset = inode->i_size;
+ offset = eof;
break;
}
retval = -EINVAL;
--
2.50.1 (Apple Git-155)