Created attachment 351072 [details, diff] Patch to fix issue generic_file_llseek_size() handles whence values in a switch statement, but it lacks cases for both SEEK_SET and invalid values. This causes it to treat all invalid whence values as SEEK_SET, which is wrong. I have sent this patch upstream, but it should be safe to apply without any approval from upstream. The official documentation is clear that EINVAL should be returned on an invalid whence value: http://linux.die.net/man/2/llseek With that said, all kernels in the main tree are affected. However, generic_file_llseek_size() was called generic_file_llseek_unlocked() in 2.6.32.
Care to post exact filesystem name and exact source-file reproducer? The manpage you linked is outdated. It does not mention SEEK_DATA / SEEK_HOLE (man 2 lseek does though). Value sanitizers live in syscal calls: fs/read_write.c: SYSCALL_DEFINE3(lseek, unsigned int, fd, off_t, offset, unsigned int, whence) { off_t retval; struct fd f = fdget(fd); if (!f.file) return -EBADF; retval = -EINVAL; if (whence <= SEEK_MAX) { loff_t res = vfs_llseek(f.file, offset, whence); retval = res; if (res != (loff_t)retval) retval = -EOVERFLOW; /* LFS: should only happen on 32 bit platforms */ } fdput(f); return retval; } #ifdef CONFIG_COMPAT COMPAT_SYSCALL_DEFINE3(lseek, unsigned int, fd, compat_off_t, offset, unsigned int, whence) { return sys_lseek(fd, offset, whence); } #endif #ifdef __ARCH_WANT_SYS_LLSEEK SYSCALL_DEFINE5(llseek, unsigned int, fd, unsigned long, offset_high, unsigned long, offset_low, loff_t __user *, result, unsigned int, whence) { int retval; struct fd f = fdget(fd); loff_t offset; if (!f.file) return -EBADF; retval = -EINVAL; if (whence > SEEK_MAX) goto out_putf; offset = vfs_llseek(f.file, ((loff_t) offset_high << 32) | offset_low, whence); retval = (int)offset; if (offset >= 0) { retval = -EFAULT; if (!copy_to_user(result, &offset, sizeof(offset))) retval = 0; } out_putf: fdput(f); return retval; } #endif include/uapi/linux/fs.h: #define SEEK_SET 0 /* seek relative to beginning of file */ #define SEEK_CUR 1 /* seek relative to current file position */ #define SEEK_END 2 /* seek relative to end of file */ #define SEEK_DATA 3 /* seek to the next data */ #define SEEK_HOLE 4 /* seek to the next hole */ #define SEEK_MAX SEEK_HOLE
(In reply to Sergei Trofimovich from comment #1) > Care to post exact filesystem name and exact source-file reproducer? > > The manpage you linked is outdated. > It does not mention SEEK_DATA / SEEK_HOLE > (man 2 lseek does though). > > Value sanitizers live in syscal calls: > > fs/read_write.c: > > SYSCALL_DEFINE3(lseek, unsigned int, fd, off_t, offset, unsigned int, whence) > { > off_t retval; > struct fd f = fdget(fd); > if (!f.file) > return -EBADF; > > retval = -EINVAL; > if (whence <= SEEK_MAX) { > loff_t res = vfs_llseek(f.file, offset, whence); > retval = res; > if (res != (loff_t)retval) > retval = -EOVERFLOW; /* LFS: should only happen > on 32 bit platforms */ > } > fdput(f); > return retval; > } > > > #ifdef CONFIG_COMPAT > COMPAT_SYSCALL_DEFINE3(lseek, unsigned int, fd, compat_off_t, offset, > unsigned int, whence) > { > return sys_lseek(fd, offset, whence); > } > #endif > > #ifdef __ARCH_WANT_SYS_LLSEEK > SYSCALL_DEFINE5(llseek, unsigned int, fd, unsigned long, offset_high, > unsigned long, offset_low, loff_t __user *, result, > unsigned int, whence) > { > int retval; > struct fd f = fdget(fd); > loff_t offset; > > if (!f.file) > return -EBADF; > > retval = -EINVAL; > if (whence > SEEK_MAX) > goto out_putf; > > offset = vfs_llseek(f.file, ((loff_t) offset_high << 32) | > offset_low, > whence); > > retval = (int)offset; > if (offset >= 0) { > retval = -EFAULT; > if (!copy_to_user(result, &offset, sizeof(offset))) > retval = 0; > } > out_putf: > fdput(f); > return retval; > } > #endif > > include/uapi/linux/fs.h: > > #define SEEK_SET 0 /* seek relative to beginning of file */ > #define SEEK_CUR 1 /* seek relative to current file position */ > #define SEEK_END 2 /* seek relative to end of file */ > #define SEEK_DATA 3 /* seek to the next data */ > #define SEEK_HOLE 4 /* seek to the next hole */ > #define SEEK_MAX SEEK_HOLE It had not occurred to me to check the VFS code between the filesystem interface and the llseek() implementation for additional sanitization. It appears that the VFS calls themselves have checks to prevent this, which means that this will not happen, but it is remarkably ugly code. It should be cleaned up at upstream. Next time, I will make time to write a test case. Thanks for looking into this.