Skip to content

Commit

Permalink
Bugfix/fix uio partial copies
Browse files Browse the repository at this point in the history
In zfs_write(), the loop continues to the next iteration without
accounting for partial copies occurring in uiomove_iov when 
copy_from_user/__copy_from_user_inatomic return a non-zero status.
This results in "zfs: accessing past end of object..." in the 
kernel log, and the write failing.

Account for partial copies and update uio struct before returning
EFAULT, leave a comment explaining the reason why this is done.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: ilbsmart <wgqimut@gmail.com>
Signed-off-by: Fabio Scaccabarozzi <fsvm88@gmail.com>
Closes openzfs#8673 
Closes openzfs#10148
  • Loading branch information
fsvm88 authored and tonyhutter committed Apr 22, 2020
1 parent df3c04f commit b50d4d2
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 8 deletions.
25 changes: 17 additions & 8 deletions module/zcommon/zfs_uio.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,31 @@ uiomove_iov(void *p, size_t n, enum uio_rw rw, struct uio *uio)
if (copy_to_user(iov->iov_base+skip, p, cnt))
return (EFAULT);
} else {
unsigned long b_left = 0;
if (uio->uio_fault_disable) {
if (!zfs_access_ok(VERIFY_READ,
(iov->iov_base + skip), cnt)) {
return (EFAULT);
}
pagefault_disable();
if (__copy_from_user_inatomic(p,
(iov->iov_base + skip), cnt)) {
pagefault_enable();
return (EFAULT);
}
b_left =
__copy_from_user_inatomic(p,
(iov->iov_base + skip), cnt);
pagefault_enable();
} else {
if (copy_from_user(p,
(iov->iov_base + skip), cnt))
return (EFAULT);
b_left =
copy_from_user(p,
(iov->iov_base + skip), cnt);
}
if (b_left > 0) {
unsigned long c_bytes =
cnt - b_left;
uio->uio_skip += c_bytes;
ASSERT3U(uio->uio_skip, <,
iov->iov_len);
uio->uio_resid -= c_bytes;
uio->uio_loffset += c_bytes;
return (EFAULT);
}
}
break;
Expand Down
9 changes: 9 additions & 0 deletions module/zfs/zfs_vnops.c
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,15 @@ zfs_write(struct inode *ip, uio_t *uio, int ioflag, cred_t *cr)
uio->uio_fault_disable = B_FALSE;
if (error == EFAULT) {
dmu_tx_commit(tx);
/*
* Account for partial writes before
* continuing the loop.
* Update needs to occur before the next
* uio_prefaultpages, or prefaultpages may
* error, and we may break the loop early.
*/
if (tx_bytes != uio->uio_resid)
n -= tx_bytes - uio->uio_resid;
if (uio_prefaultpages(MIN(n, max_blksz), uio)) {
break;
}
Expand Down

0 comments on commit b50d4d2

Please sign in to comment.