[PATCH] erofs-utils: lib: bound SEEK_DATA results by the inode size
Lenno Nagel <[email protected]>
| Newsgroups | org.ozlabs.lists.linux-erofs |
|---|---|
| Message-ID | <g6thRspQKWfiuTooyOe2QlTfKjPaELZs2Im4oTfBTwqb6w9NV1q4Rkc3O3bzQvbkn9lq94pAszmT2jHwKPPi0yWK3NoxpgWPefvUQSp2AOc=@namespace.ee> |
erofs_blob_write_chunked_file() allocates inode->chunkindexes for
DIV_ROUND_UP(inode->i_size, chunksize) entries, but the hole-skipping
loop advances `pos' in chunksize steps until it reaches the offset
reported by SEEK_DATA, without bounding that offset by the inode size.
Since the diskbuf was introduced, `fd' is not necessarily the source
file any more: for tarballs it is a shared scratch region holding the
payload of every member, each reservation rounded up to
max(st_blksize, getpagesize()), so SEEK_DATA can legitimately report
the next member's data instead of failing with ENXIO. If the
filesystem backing the diskbuf stores all-zero regions as holes (e.g.
ZFS with compression enabled, which also reports st_blksize as its
recordsize), an all-zero member is skipped in its entirety and the
loop keeps appending hole entries until it reaches the next member: a
65535-byte all-zero member on a 1 MiB alignment writes 256 entries
into a 16-entry array.
The heap corruption is silent where it happens and surfaces later as
an abort inside an unrelated malloc(), e.g. "malloc(): invalid size
(unsorted)" from erofs_balloc().
Bound the offset with round_up(inode->i_size, chunksize) so that the
bytes past the inode become hole chunks, just as they do on the ENXIO
path above.
Reproducer (out.erofs doubles as the diskbuf, so it must live on a
filesystem which stores all-zero regions as holes, e.g. a ZFS dataset
with compression=on):
head -c 65535 /dev/zero > a-zeros.bin
head -c 4096 /dev/urandom > b-data.bin
tar -cf minimal.tar a-zeros.bin b-data.bin
mkfs.erofs --tar=f -b4096 --chunksize=4096 out.erofs < minimal.tar
Found while converting an OCI layer to EROFS with containerd's erofs
snapshotter, which invokes mkfs.erofs with --tar=f --chunksize=4096.
Fixes: 376fb2dbe66d ("erofs-utils: lib: introduce diskbuf")
Signed-off-by: Lenno Nagel <[email protected]>
---
lib/blobchunk.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/lib/blobchunk.c b/lib/blobchunk.c
index 0523873..a066052 100644
--- a/lib/blobchunk.c
+++ b/lib/blobchunk.c
@@ -377,6 +377,15 @@ int erofs_blob_write_chunked_file(struct erofs_inode *inode, int fd,
} else {
offset -= startoff;
+ /*
+ * `fd' can be a shared diskbuf holding the data of
+ * other inodes as well, thus SEEK_DATA may report the
+ * next data region beyond this inode. The remaining
+ * bytes are then a hole, just like the ENXIO case.
+ */
+ if ((u64)offset > inode->i_size)
+ offset = round_up(inode->i_size, chunksize);
+
if (offset != (offset & ~(chunksize - 1))) {
offset &= ~(chunksize - 1);
if (lseek(fd, offset + startoff, SEEK_SET) !=
base-commit: f36cadb5c563995ab3aa8572a60ed6b721b9557d
--
2.53.0