Skip to content

Commit 868f9f2

Browse files
amir73iltorvalds
authored andcommitted
vfs: fix copy_file_range() regression in cross-fs copies
A regression has been reported by Nicolas Boichat, found while using the copy_file_range syscall to copy a tracefs file. Before commit 5dae222 ("vfs: allow copy_file_range to copy across devices") the kernel would return -EXDEV to userspace when trying to copy a file across different filesystems. After this commit, the syscall doesn't fail anymore and instead returns zero (zero bytes copied), as this file's content is generated on-the-fly and thus reports a size of zero. Another regression has been reported by He Zhe - the assertion of WARN_ON_ONCE(ret == -EOPNOTSUPP) can be triggered from userspace when copying from a sysfs file whose read operation may return -EOPNOTSUPP. Since we do not have test coverage for copy_file_range() between any two types of filesystems, the best way to avoid these sort of issues in the future is for the kernel to be more picky about filesystems that are allowed to do copy_file_range(). This patch restores some cross-filesystem copy restrictions that existed prior to commit 5dae222 ("vfs: allow copy_file_range to copy across devices"), namely, cross-sb copy is not allowed for filesystems that do not implement ->copy_file_range(). Filesystems that do implement ->copy_file_range() have full control of the result - if this method returns an error, the error is returned to the user. Before this change this was only true for fs that did not implement the ->remap_file_range() operation (i.e. nfsv3). Filesystems that do not implement ->copy_file_range() still fall-back to the generic_copy_file_range() implementation when the copy is within the same sb. This helps the kernel can maintain a more consistent story about which filesystems support copy_file_range(). nfsd and ksmbd servers are modified to fall-back to the generic_copy_file_range() implementation in case vfs_copy_file_range() fails with -EOPNOTSUPP or -EXDEV, which preserves behavior of server-side-copy. fall-back to generic_copy_file_range() is not implemented for the smb operation FSCTL_DUPLICATE_EXTENTS_TO_FILE, which is arguably a correct change of behavior. Fixes: 5dae222 ("vfs: allow copy_file_range to copy across devices") Link: https://lore.kernel.org/linux-fsdevel/[email protected]/ Link: https://lore.kernel.org/linux-fsdevel/CANMq1KDZuxir2LM5jOTm0xx+BnvW=ZmpsG47CyHFJwnw7zSX6Q@mail.gmail.com/ Link: https://lore.kernel.org/linux-fsdevel/20210126135012.1.If45b7cdc3ff707bc1efa17f5366057d60603c45f@changeid/ Link: https://lore.kernel.org/linux-fsdevel/[email protected]/ Reported-by: Nicolas Boichat <[email protected]> Reported-by: kernel test robot <[email protected]> Signed-off-by: Luis Henriques <[email protected]> Fixes: 64bf5ff ("vfs: no fallback for ->copy_file_range") Link: https://lore.kernel.org/linux-fsdevel/[email protected]/ Reported-by: He Zhe <[email protected]> Tested-by: Namjae Jeon <[email protected]> Tested-by: Luis Henriques <[email protected]> Signed-off-by: Amir Goldstein <[email protected]> Signed-off-by: Linus Torvalds <[email protected]>
1 parent 1a0e93d commit 868f9f2

File tree

4 files changed

+68
-37
lines changed

4 files changed

+68
-37
lines changed

fs/ksmbd/smb2pdu.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7810,14 +7810,24 @@ int smb2_ioctl(struct ksmbd_work *work)
78107810
src_off = le64_to_cpu(dup_ext->SourceFileOffset);
78117811
dst_off = le64_to_cpu(dup_ext->TargetFileOffset);
78127812
length = le64_to_cpu(dup_ext->ByteCount);
7813-
cloned = vfs_clone_file_range(fp_in->filp, src_off, fp_out->filp,
7814-
dst_off, length, 0);
7813+
/*
7814+
* XXX: It is not clear if FSCTL_DUPLICATE_EXTENTS_TO_FILE
7815+
* should fall back to vfs_copy_file_range(). This could be
7816+
* beneficial when re-exporting nfs/smb mount, but note that
7817+
* this can result in partial copy that returns an error status.
7818+
* If/when FSCTL_DUPLICATE_EXTENTS_TO_FILE_EX is implemented,
7819+
* fall back to vfs_copy_file_range(), should be avoided when
7820+
* the flag DUPLICATE_EXTENTS_DATA_EX_SOURCE_ATOMIC is set.
7821+
*/
7822+
cloned = vfs_clone_file_range(fp_in->filp, src_off,
7823+
fp_out->filp, dst_off, length, 0);
78157824
if (cloned == -EXDEV || cloned == -EOPNOTSUPP) {
78167825
ret = -EOPNOTSUPP;
78177826
goto dup_ext_out;
78187827
} else if (cloned != length) {
78197828
cloned = vfs_copy_file_range(fp_in->filp, src_off,
7820-
fp_out->filp, dst_off, length, 0);
7829+
fp_out->filp, dst_off,
7830+
length, 0);
78217831
if (cloned != length) {
78227832
if (cloned < 0)
78237833
ret = cloned;

fs/ksmbd/vfs.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,6 +1779,10 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
17791779

17801780
ret = vfs_copy_file_range(src_fp->filp, src_off,
17811781
dst_fp->filp, dst_off, len, 0);
1782+
if (ret == -EOPNOTSUPP || ret == -EXDEV)
1783+
ret = generic_copy_file_range(src_fp->filp, src_off,
1784+
dst_fp->filp, dst_off,
1785+
len, 0);
17821786
if (ret < 0)
17831787
return ret;
17841788

fs/nfsd/vfs.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ __be32 nfsd4_clone_file_range(struct svc_rqst *rqstp,
577577
ssize_t nfsd_copy_file_range(struct file *src, u64 src_pos, struct file *dst,
578578
u64 dst_pos, u64 count)
579579
{
580+
ssize_t ret;
580581

581582
/*
582583
* Limit copy to 4MB to prevent indefinitely blocking an nfsd
@@ -587,7 +588,12 @@ ssize_t nfsd_copy_file_range(struct file *src, u64 src_pos, struct file *dst,
587588
* limit like this and pipeline multiple COPY requests.
588589
*/
589590
count = min_t(u64, count, 1 << 22);
590-
return vfs_copy_file_range(src, src_pos, dst, dst_pos, count, 0);
591+
ret = vfs_copy_file_range(src, src_pos, dst, dst_pos, count, 0);
592+
593+
if (ret == -EOPNOTSUPP || ret == -EXDEV)
594+
ret = generic_copy_file_range(src, src_pos, dst, dst_pos,
595+
count, 0);
596+
return ret;
591597
}
592598

593599
__be32 nfsd4_vfs_fallocate(struct svc_rqst *rqstp, struct svc_fh *fhp,

fs/read_write.c

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,28 +1397,6 @@ ssize_t generic_copy_file_range(struct file *file_in, loff_t pos_in,
13971397
}
13981398
EXPORT_SYMBOL(generic_copy_file_range);
13991399

1400-
static ssize_t do_copy_file_range(struct file *file_in, loff_t pos_in,
1401-
struct file *file_out, loff_t pos_out,
1402-
size_t len, unsigned int flags)
1403-
{
1404-
/*
1405-
* Although we now allow filesystems to handle cross sb copy, passing
1406-
* a file of the wrong filesystem type to filesystem driver can result
1407-
* in an attempt to dereference the wrong type of ->private_data, so
1408-
* avoid doing that until we really have a good reason. NFS defines
1409-
* several different file_system_type structures, but they all end up
1410-
* using the same ->copy_file_range() function pointer.
1411-
*/
1412-
if (file_out->f_op->copy_file_range &&
1413-
file_out->f_op->copy_file_range == file_in->f_op->copy_file_range)
1414-
return file_out->f_op->copy_file_range(file_in, pos_in,
1415-
file_out, pos_out,
1416-
len, flags);
1417-
1418-
return generic_copy_file_range(file_in, pos_in, file_out, pos_out, len,
1419-
flags);
1420-
}
1421-
14221400
/*
14231401
* Performs necessary checks before doing a file copy
14241402
*
@@ -1440,6 +1418,24 @@ static int generic_copy_file_checks(struct file *file_in, loff_t pos_in,
14401418
if (ret)
14411419
return ret;
14421420

1421+
/*
1422+
* We allow some filesystems to handle cross sb copy, but passing
1423+
* a file of the wrong filesystem type to filesystem driver can result
1424+
* in an attempt to dereference the wrong type of ->private_data, so
1425+
* avoid doing that until we really have a good reason.
1426+
*
1427+
* nfs and cifs define several different file_system_type structures
1428+
* and several different sets of file_operations, but they all end up
1429+
* using the same ->copy_file_range() function pointer.
1430+
*/
1431+
if (file_out->f_op->copy_file_range) {
1432+
if (file_in->f_op->copy_file_range !=
1433+
file_out->f_op->copy_file_range)
1434+
return -EXDEV;
1435+
} else if (file_inode(file_in)->i_sb != file_inode(file_out)->i_sb) {
1436+
return -EXDEV;
1437+
}
1438+
14431439
/* Don't touch certain kinds of inodes */
14441440
if (IS_IMMUTABLE(inode_out))
14451441
return -EPERM;
@@ -1505,26 +1501,41 @@ ssize_t vfs_copy_file_range(struct file *file_in, loff_t pos_in,
15051501
file_start_write(file_out);
15061502

15071503
/*
1508-
* Try cloning first, this is supported by more file systems, and
1509-
* more efficient if both clone and copy are supported (e.g. NFS).
1504+
* Cloning is supported by more file systems, so we implement copy on
1505+
* same sb using clone, but for filesystems where both clone and copy
1506+
* are supported (e.g. nfs,cifs), we only call the copy method.
15101507
*/
1508+
if (file_out->f_op->copy_file_range) {
1509+
ret = file_out->f_op->copy_file_range(file_in, pos_in,
1510+
file_out, pos_out,
1511+
len, flags);
1512+
goto done;
1513+
}
1514+
15111515
if (file_in->f_op->remap_file_range &&
15121516
file_inode(file_in)->i_sb == file_inode(file_out)->i_sb) {
1513-
loff_t cloned;
1514-
1515-
cloned = file_in->f_op->remap_file_range(file_in, pos_in,
1517+
ret = file_in->f_op->remap_file_range(file_in, pos_in,
15161518
file_out, pos_out,
15171519
min_t(loff_t, MAX_RW_COUNT, len),
15181520
REMAP_FILE_CAN_SHORTEN);
1519-
if (cloned > 0) {
1520-
ret = cloned;
1521+
if (ret > 0)
15211522
goto done;
1522-
}
15231523
}
15241524

1525-
ret = do_copy_file_range(file_in, pos_in, file_out, pos_out, len,
1526-
flags);
1527-
WARN_ON_ONCE(ret == -EOPNOTSUPP);
1525+
/*
1526+
* We can get here for same sb copy of filesystems that do not implement
1527+
* ->copy_file_range() in case filesystem does not support clone or in
1528+
* case filesystem supports clone but rejected the clone request (e.g.
1529+
* because it was not block aligned).
1530+
*
1531+
* In both cases, fall back to kernel copy so we are able to maintain a
1532+
* consistent story about which filesystems support copy_file_range()
1533+
* and which filesystems do not, that will allow userspace tools to
1534+
* make consistent desicions w.r.t using copy_file_range().
1535+
*/
1536+
ret = generic_copy_file_range(file_in, pos_in, file_out, pos_out, len,
1537+
flags);
1538+
15281539
done:
15291540
if (ret > 0) {
15301541
fsnotify_access(file_in);

0 commit comments

Comments
 (0)