Page MenuHomeFreeBSD

Fix copy_file_range(2) so that it does not truncate the output file erroneously
ClosedPublic

Authored by rmacklem on Dec 31 2023, 4:56 PM.
Tags
None
Referenced Files
Unknown Object (File)
Wed, Oct 23, 3:14 PM
Unknown Object (File)
Tue, Oct 22, 9:45 AM
Unknown Object (File)
Tue, Oct 22, 9:45 AM
Unknown Object (File)
Tue, Oct 22, 9:45 AM
Unknown Object (File)
Tue, Oct 22, 9:45 AM
Unknown Object (File)
Tue, Oct 22, 9:33 AM
Unknown Object (File)
Fri, Oct 18, 4:20 AM
Unknown Object (File)
Oct 3 2024, 10:23 PM
Subscribers

Details

Summary

When copy_file_range(2) was first being developed,
*inoffp + len had to be <= infile_size or an error was
returned. This semantic (as defined by Linux) changed
to allow *inoffp + len to be greater than infile_size and
the copy would end at *inoffp + infile_size.

Unfortunately, the code that decided if the outfd should
be truncated in length did not get updated for this
semantics change.
As such, if a copy_file_range(2) is done, where infile_size - *inoffp
is less that outfile_size but len is large, the outfd file is truncated
when it should not be. (The semantics for this for Linux is to not
truncate outfd in this case.)

This patch fixes the problem. I believe the calculation is safe
for all non-negative values of outsize, *outoffp, *inoffp and insize,
which should be ok, since they are all guaranteed to be non-negative.

Note that this bug is not observed over NFSv4.2, since it truncates
len to infile_size + *inoffp.

Test Plan

Ran a small test program that used copy_file_range()
with len == SSIZE_MAX to copy a range from a smaller
file to a larger one. This truncated the outfd file without
the patch and did not with the patch.

Done on a UFS file system that uses vn_generic_copy_file_range().

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

This looks correct to me, but I haven't tested it in any way. I think it's a good candidate to add to pjdfstest.

This revision is now accepted and ready to land.Dec 31 2023, 5:53 PM
sys/kern/vfs_vnops.c
3401

I think that on 64bit the calculations could overflow, and that we need to check that. I missed it before. More, since off_t is signed, we would got an UB if not -fwrap compiler option.

I believe it would be enough to check e.g. *outoffp <= OFF_MAX - len.

3403

Same there

Added kib@'s lines to the calculation as an extra
safety belt (and to avoid any overflow to a negative
value).

This revision now requires review to proceed.Dec 31 2023, 9:14 PM
rmacklem added inline comments.
sys/kern/vfs_vnops.c
3401

Well, *outoffp has already been checked for >= 0
in vn_copy_file_range() and since both *outoffp
and len are off_t (signed) adding them to-gether
can result in a negative value.

I was thinking that the following check of:
outsize <= *outoffp + len would be sufficient,
since outsize is non-negative.

However, if there was compile/runtime check for
the overflow (I'm guessing that's what you are
referring to by UB.) it would break.

I've added what you suggested as an extra safety belt.

This revision is now accepted and ready to land.Dec 31 2023, 10:33 PM
This revision was automatically updated to reflect the committed changes.
rmacklem marked an inline comment as done.