diff options
author | Richard Yao <richard.yao@alumni.stonybrook.edu> | 2023-03-01 21:19:47 +0000 |
---|---|---|
committer | Brian Behlendorf <behlendorf1@llnl.gov> | 2023-04-21 20:12:35 +0000 |
commit | 4a5950a1298aedabe2cf730c5dc9d36cebbef79c (patch) | |
tree | d979bb211652b4c0d7af20b20c2bc589dfd63889 | |
parent | c7db374ac6b8b73afbc9676ad71e2ce18f23de37 (diff) | |
download | src-4a5950a1298aedabe2cf730c5dc9d36cebbef79c.tar.gz src-4a5950a1298aedabe2cf730c5dc9d36cebbef79c.zip |
Linux: zfs_fillpage() should handle partial pages from end of file
After 89cd2197b94986d315b9b1be707b645baf59af4f was merged, Clang's
static analyzer began complaining about a dead assignment in
`zfs_fillpage()`. Upon inspection, I noticed that the dead assignment
was because we are not using the calculated io_len that we should use to
avoid asking the DMU to read past the end of a file. This should result
in `dmu_buf_hold_array_by_dnode()` calling `zfs_panic_recover()`.
This issue predates 89cd2197b94986d315b9b1be707b645baf59af4f, but its
simplification of zfs_fillpage() eliminated the only use of the
assignment to io_len, which made Clang's static analyzer complain about
the issue.
Also, as a precaution, we add an assertion that io_offset < i_size. If
this ever fails, bad things will happen. Otherwise, we are blindly
trusting the kernel not to give us invalid offsets. We continue to
blindly trust it on non-debug kernels.
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Brian Atkinson <batkinson@lanl.gov>
Signed-off-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Closes #14534
-rw-r--r-- | module/os/linux/zfs/zfs_vnops_os.c | 6 |
1 files changed, 5 insertions, 1 deletions
diff --git a/module/os/linux/zfs/zfs_vnops_os.c b/module/os/linux/zfs/zfs_vnops_os.c index 27e1ab420bb7..c171448080cd 100644 --- a/module/os/linux/zfs/zfs_vnops_os.c +++ b/module/os/linux/zfs/zfs_vnops_os.c @@ -3784,12 +3784,16 @@ zfs_fillpage(struct inode *ip, struct page *pp) u_offset_t io_off = page_offset(pp); size_t io_len = PAGE_SIZE; + ASSERT3U(io_off, <, i_size); + if (io_off + io_len > i_size) io_len = i_size - io_off; void *va = kmap(pp); int error = dmu_read(zfsvfs->z_os, ITOZ(ip)->z_id, io_off, - PAGE_SIZE, va, DMU_READ_PREFETCH); + io_len, va, DMU_READ_PREFETCH); + if (io_len != PAGE_SIZE) + memset((char *)va + io_len, 0, PAGE_SIZE - io_len); kunmap(pp); if (error) { |