memfs_ll: reject out-of-range read and write offsets

memfs_write passed offset and size straight to Inode::write_content,
which computes offset + size to size the content vector. A negative
offset converts to a huge unsigned value in that expression and aborts
the resize; an end position past the vector's index ceiling truncates
on the resize/iterator arithmetic (size_t/ptrdiff_t), which on a 32-bit
build is below the off_t range the kernel can hand us.

memfs_read only guarded offset >= content_size(). On a 64-bit build the
unsigned compare incidentally rejects a negative offset, but on 32-bit
the comparison is signed, so a negative offset slips through, truncates
in (size_t)offset, and indexes before the content buffer.

Validate at both handlers before touching the content store: reject a
negative offset with EINVAL, and in write reject an end position past
PTRDIFF_MAX (the std::vector size/index limit on both 32- and 64-bit
builds) with EFBIG.

Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
This commit is contained in:
Bernd Schubert
2026-06-17 14:55:38 +02:00
parent f884d0f362
commit 2137d4b0cc
+20
View File
@@ -665,6 +665,19 @@ static void memfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
return;
}
// A negative offset, or an end position past the content vector's index
// type, would convert to a huge value in write_content and abort the
// resize. PTRDIFF_MAX is the std::vector size/index ceiling on both 32-
// and 64-bit builds.
if (offset < 0) {
fuse_reply_err(req, EINVAL);
return;
}
if ((uint64_t)offset > (uint64_t)PTRDIFF_MAX - size) {
fuse_reply_err(req, EFBIG);
return;
}
inode->write_content(buf, size, offset);
fuse_reply_write(req, size);
}
@@ -686,6 +699,13 @@ static void memfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
return;
}
// A negative offset would slip past the offset >= content_size() guard
// on a 32-bit build (signed compare) and index before the buffer.
if (offset < 0) {
fuse_reply_err(req, EINVAL);
return;
}
inode->lock();
if (offset >= inode->content_size()) {