[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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.