[PATCH 05/25] vfs: avoid problematic remapping requests into partial EOF block

Darrick J. Wong
2018-10-11T04:12:54Z

A deduplication data corruption is exposed by fstests generic/505 on
XFS. It is caused by extending the block match range to include the
partial EOF block, but then allowing unknown data beyond EOF to be
considered a "match" to data in the destination file because the
comparison is only made to the end of the source file. This corrupts the
destination file when the source extent is shared with it.

The VFS remapping prep functions only support whole block dedupe, but
we still need to appear to support whole file dedupe correctly. Hence
if the dedupe request includes the last block of the souce file, don't
include it in the actual dedupe operation. If the rest of the range
dedupes successfully, then reject the entire request. A subsequent
patch will enable us to shorten dedupe requests correctly.

When reflinking sub-file ranges, a data corruption can occur when the
source file range includes a partial EOF block. This shares the unknown
data beyond EOF into the second file at a position inside EOF, exposing
stale data in the second file.

If the reflink request includes the last block of the souce file, only
proceed with the reflink operation if it lands at or past the
destination file's current EOF. If it lands within the destination file
EOF, reject the entire request with -EINVAL and make the caller go the
hard way. A subsequent patch will enable us to shorten reflink requests
correctly.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
---
fs/read_write.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)

diff --git a/fs/read_write.c b/fs/read_write.c
index d6e8e242a15f..8498991e2f33 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -1723,6 +1723,7 @@ int vfs_clone_file_prep(struct file *file_in, loff_t pos_in,
{
struct inode *inode_in = file_inode(file_in);
struct inode *inode_out = file_inode(file_out);
+ u64 blkmask = i_blocksize(inode_in) - 1;
bool same_inode = (inode_in == inode_out);
int ret;

@@ -1785,6 +1786,27 @@ int vfs_clone_file_prep(struct file *file_in, loff_t pos_in,
return -EBADE;
}

+ /* Are we doing a partial EOF block remapping of some kind? */
+ if (*len & blkmask) {
+ /*
+ * If the dedupe data matches, don't try to dedupe the partial
+ * EOF block.
+ *
+ * If the user is attempting to remap a partial EOF block and
+ * it's inside the destination EOF then reject it.
+ *
+ * We don't support shortening requests, so we can only reject
+ * them.
+ */
+ if (is_dedupe)
+ ret = -EBADE;
+ else if (pos_out + *len < i_size_read(inode_out))
+ ret = -EINVAL;
+
+ if (ret)
+ return ret;
+ }
+
return 1;
}
EXPORT_SYMBOL(vfs_clone_file_prep);