From 164aa1e9419bc445cf3f9aa816572fa6fbc26a7e Mon Sep 17 00:00:00 2001 From: Rick Macklem Date: Wed, 30 Sep 2020 02:18:09 +0000 Subject: [PATCH] Make copy_file_range(2) Linux compatible for overflow of offset + len. Without this patch, if a call to copy_file_range(2) specifies an input file offset + len that would wrap around, EINVAL is returned. I thought that was the Linux behaviour, but recent testing showed that Linux accepts this case and does the copy_file_range() to EOF. This patch changes the FreeBSD code to exhibit the same behaviour as Linux for this case. Reviewed by: asomers, kib Differential Revision: https://reviews.freebsd.org/D26569 --- sys/kern/vfs_vnops.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/sys/kern/vfs_vnops.c b/sys/kern/vfs_vnops.c index 171985dcb19..e31e6289987 100644 --- a/sys/kern/vfs_vnops.c +++ b/sys/kern/vfs_vnops.c @@ -2790,27 +2790,33 @@ vn_copy_file_range(struct vnode *invp, off_t *inoffp, struct vnode *outvp, { int error; size_t len; - uint64_t uvalin, uvalout; + uint64_t uval; len = *lenp; *lenp = 0; /* For error returns. */ error = 0; /* Do some sanity checks on the arguments. */ - uvalin = *inoffp; - uvalin += len; - uvalout = *outoffp; - uvalout += len; if (invp->v_type == VDIR || outvp->v_type == VDIR) error = EISDIR; - else if (*inoffp < 0 || uvalin > INT64_MAX || uvalin < - (uint64_t)*inoffp || *outoffp < 0 || uvalout > INT64_MAX || - uvalout < (uint64_t)*outoffp || invp->v_type != VREG || - outvp->v_type != VREG) + else if (*inoffp < 0 || *outoffp < 0 || + invp->v_type != VREG || outvp->v_type != VREG) error = EINVAL; if (error != 0) goto out; + /* Ensure offset + len does not wrap around. */ + uval = *inoffp; + uval += len; + if (uval > INT64_MAX) + len = INT64_MAX - *inoffp; + uval = *outoffp; + uval += len; + if (uval > INT64_MAX) + len = INT64_MAX - *outoffp; + if (len == 0) + goto out; + /* * If the two vnode are for the same file system, call * VOP_COPY_FILE_RANGE(), otherwise call vn_generic_copy_file_range()