aboutsummaryrefslogtreecommitdiff
path: root/sys/kern
diff options
context:
space:
mode:
authorRick Macklem <rmacklem@FreeBSD.org>2019-08-08 19:53:07 +0000
committerRick Macklem <rmacklem@FreeBSD.org>2019-08-08 19:53:07 +0000
commit614633146f60d603bc0b9e12b413b5ba9518424b (patch)
tree494a80da15852c63aebc2cf6fe8fe131cf691347 /sys/kern
parentb1b93268466688ef41e6564804459945e3dd134d (diff)
downloadsrc-614633146f60d603bc0b9e12b413b5ba9518424b.tar.gz
src-614633146f60d603bc0b9e12b413b5ba9518424b.zip
Fix copy_file_range(2) for an unlikely race during hole finding.
Since the VOP_IOCTL(FIOSEEKDATA/FIOSEEKHOLE) calls are done with the vnode unlocked, it is possible for another thread to do: - truncate(), lseek(), write() between the two calls and create a hole where FIOSEEKDATA returned the start of data. For this case, VOP_IOCTL(FIOSEEKHOLE) will return the same offset for the hole location. This could result in an infinite loop in the copy code, since copylen is set to 0 and the copy doesn't advance. Usually, this race is avoided because of the use of rangelocks, but the NFS server does not do range locking and could do a sequence like the above to create the hole. This patch checks for this case and makes the hole search fail, to avoid the infinite loop. At this time, it is an open question as to whether or not the NFS server should do range locking to avoid this race.
Notes
Notes: svn path=/head/; revision=350776
Diffstat (limited to 'sys/kern')
-rw-r--r--sys/kern/vfs_vnops.c11
1 files changed, 11 insertions, 0 deletions
diff --git a/sys/kern/vfs_vnops.c b/sys/kern/vfs_vnops.c
index b6fe702d7b0a..e15cee41ead8 100644
--- a/sys/kern/vfs_vnops.c
+++ b/sys/kern/vfs_vnops.c
@@ -2895,6 +2895,17 @@ vn_generic_copy_file_range(struct vnode *invp, off_t *inoffp,
endoff = startoff;
error = VOP_IOCTL(invp, FIOSEEKHOLE, &endoff, 0,
incred, curthread);
+ /*
+ * Since invp is unlocked, it may be possible for
+ * another thread to do a truncate(), lseek(), write()
+ * creating a hole at startoff between the above
+ * VOP_IOCTL() calls, if the other thread does not do
+ * rangelocking.
+ * If that happens, startoff == endoff and finding
+ * the hole has failed, so set an error.
+ */
+ if (error == 0 && startoff == endoff)
+ error = EINVAL; /* Any error. Reset to 0. */
}
if (error == 0) {
if (startoff > *inoffp) {