Re: [PATCH v2] fs: fix unintentional arithmetic wraparound in offset calculation

Justin Stitt
2024-05-13T22:07:05Z

On Mon, May 13, 2024 at 01:01:57PM -0700, Kees Cook wrote:
> On Thu, May 09, 2024 at 11:42:07PM +0000, Justin Stitt wrote:
> > fs/read_write.c | 18 +++++++++++-------
> > fs/remap_range.c | 12 ++++++------
> > 2 files changed, 17 insertions(+), 13 deletions(-)
> >
> > diff --git a/fs/read_write.c b/fs/read_write.c
> > index d4c036e82b6c..d116e6e3eb3d 100644
> > --- a/fs/read_write.c
> > +++ b/fs/read_write.c
> > @@ -88,7 +88,7 @@ generic_file_llseek_size(struct file *file, loff_t offset, int whence,
> > {
> > switch (whence) {
> > case SEEK_END:
> > - offset += eof;
> > + offset = min(offset, maxsize - eof) + eof;
>
> This seems effectively unchanged compared to v1?
>
> https://lore.kernel.org/all/CAFhGd8qbUYXmgiFuLGQ7dWXFUtZacvT82wD4jSS-xNTvtzXKGQ@mail.gmail.com/
>

Right, please note the timestamps of Jan's review of v1 and when I sent
v2. Essentially, I sent v2 before Jan's review of v1 and as such v2 does
not fix the problem pointed out by Jan (the behavior of seek is
technically different for VERY LARGE offsets).

> > break;
> > case SEEK_CUR:
> > /*
> > @@ -105,7 +105,8 @@ generic_file_llseek_size(struct file *file, loff_t offset, int whence,
> > * like SEEK_SET.
> > */
> > spin_lock(&file->f_lock);
> > - offset = vfs_setpos(file, file->f_pos + offset, maxsize);
> > + offset = vfs_setpos(file, min(file->f_pos, maxsize - offset) +
> > + offset, maxsize);
> > spin_unlock(&file->f_lock);
> > return offset;
> > case SEEK_DATA:
> > @@ -1416,7 +1417,7 @@ static int generic_copy_file_checks(struct file *file_in, loff_t pos_in,
> > struct inode *inode_in = file_inode(file_in);
> > struct inode *inode_out = file_inode(file_out);
> > uint64_t count = *req_count;
> > - loff_t size_in;
> > + loff_t size_in, in_sum, out_sum;
> > int ret;
> >
> > ret = generic_file_rw_checks(file_in, file_out);
> > @@ -1450,8 +1451,8 @@ static int generic_copy_file_checks(struct file *file_in, loff_t pos_in,
> > if (IS_SWAPFILE(inode_in) || IS_SWAPFILE(inode_out))
> > return -ETXTBSY;
> >
> > - /* Ensure offsets don't wrap. */
> > - if (pos_in + count < pos_in || pos_out + count < pos_out)
> > + if (check_add_overflow(pos_in, count, &in_sum) ||
> > + check_add_overflow(pos_out, count, &out_sum))
> > return -EOVERFLOW;
>
> I like these changes -- they make this much more readable.
>
> >
> > /* Shorten the copy to EOF */
> > @@ -1467,8 +1468,8 @@ static int generic_copy_file_checks(struct file *file_in, loff_t pos_in,
> >
> > /* Don't allow overlapped copying within the same file. */
> > if (inode_in == inode_out &&
> > - pos_out + count > pos_in &&
> > - pos_out < pos_in + count)
> > + out_sum > pos_in &&
> > + pos_out < in_sum)
> > return -EINVAL;
> >
> > *req_count = count;
> > @@ -1649,6 +1650,9 @@ int generic_write_check_limits(struct file *file, loff_t pos, loff_t *count)
> > loff_t max_size = inode->i_sb->s_maxbytes;
> > loff_t limit = rlimit(RLIMIT_FSIZE);
> >
> > + if (pos < 0)
> > + return -EINVAL;
> > +
> > if (limit != RLIM_INFINITY) {
> > if (pos >= limit) {
> > send_sig(SIGXFSZ, current, 0);
> > diff --git a/fs/remap_range.c b/fs/remap_range.c
> > index de07f978ce3e..4570be4ef463 100644
> > --- a/fs/remap_range.c
> > +++ b/fs/remap_range.c
> > @@ -36,7 +36,7 @@ static int generic_remap_checks(struct file *file_in, loff_t pos_in,
> > struct inode *inode_out = file_out->f_mapping->host;
> > uint64_t count = *req_count;
> > uint64_t bcount;
> > - loff_t size_in, size_out;
> > + loff_t size_in, size_out, in_sum, out_sum;
> > loff_t bs = inode_out->i_sb->s_blocksize;
> > int ret;
> >
> > @@ -44,17 +44,17 @@ static int generic_remap_checks(struct file *file_in, loff_t pos_in,
> > if (!IS_ALIGNED(pos_in, bs) || !IS_ALIGNED(pos_out, bs))
> > return -EINVAL;
> >
> > - /* Ensure offsets don't wrap. */
> > - if (pos_in + count < pos_in || pos_out + count < pos_out)
> > - return -EINVAL;
> > + if (check_add_overflow(pos_in, count, &in_sum) ||
> > + check_add_overflow(pos_out, count, &out_sum))
> > + return -EOVERFLOW;
>
> Yeah, this is a good error code change. This is ultimately exposed via
> copy_file_range, where this error is documented as possible.
>
> -Kees
>
> --
> Kees Cook