git: 2319ca6a0181 - main - vfs_vnops.c: Fix vn_generic_copy_file_range() for truncation

From: Rick Macklem <rmacklem_at_FreeBSD.org>
Date: Sun, 31 Dec 2023 23:57:52 UTC
The branch main has been updated by rmacklem:

URL: https://cgit.FreeBSD.org/src/commit/?id=2319ca6a01816f7fc85d623097c639f239e18c6a

commit 2319ca6a01816f7fc85d623097c639f239e18c6a
Author:     Rick Macklem <rmacklem@FreeBSD.org>
AuthorDate: 2023-12-31 23:55:24 +0000
Commit:     Rick Macklem <rmacklem@FreeBSD.org>
CommitDate: 2023-12-31 23:55:24 +0000

    vfs_vnops.c: Fix vn_generic_copy_file_range() for truncation
    
    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.
    
    PR:     276045
    Reviewed by:    asomers, kib
    MFC after:      3 days
    Differential Revision:  https://reviews.freebsd.org/D43258
---
 sys/kern/vfs_vnops.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/sys/kern/vfs_vnops.c b/sys/kern/vfs_vnops.c
index 7fe861ccaee7..1852f2b1ef00 100644
--- a/sys/kern/vfs_vnops.c
+++ b/sys/kern/vfs_vnops.c
@@ -3361,8 +3361,7 @@ vn_generic_copy_file_range(struct vnode *invp, off_t *inoffp,
 		goto out;
 	if (VOP_PATHCONF(invp, _PC_MIN_HOLE_SIZE, &holein) != 0)
 		holein = 0;
-	if (holein > 0)
-		error = vn_getsize_locked(invp, &insize, incred);
+	error = vn_getsize_locked(invp, &insize, incred);
 	VOP_UNLOCK(invp);
 	if (error != 0)
 		goto out;
@@ -3398,7 +3397,11 @@ vn_generic_copy_file_range(struct vnode *invp, off_t *inoffp,
 		 */
 		if (error == 0)
 			error = vn_getsize_locked(outvp, &outsize, outcred);
-		if (error == 0 && outsize > *outoffp && outsize <= *outoffp + len) {
+		if (error == 0 && outsize > *outoffp &&
+		    *outoffp <= OFF_MAX - len && outsize <= *outoffp + len &&
+		    *inoffp < insize &&
+		    *outoffp <= OFF_MAX - (insize - *inoffp) &&
+		    outsize <= *outoffp + (insize - *inoffp)) {
 #ifdef MAC
 			error = mac_vnode_check_write(curthread->td_ucred,
 			    outcred, outvp);