Environment
- Exiv2 version: 0.28.7
- Platform: Linux (aarch64), but issue is cross-platform
Description
The MemIo documentation states it uses copy-on-write:
Constructor that accepts a block of memory. A copy-on-write algorithm allows read operations directly from the original data and will create a copy of the buffer on the first write operation.
However, MemIo::mmap(bool isWriteable) ignores the parameter entirely:
// basicio.cpp:789
byte* MemIo::mmap(bool /*isWriteable*/) {
return p_->data_; // Parameter ignored - always returns original buffer
}
When writing TIFF, TiffImage::writeMetadata() calls io_->mmap(true) expecting a writable buffer (tiffimage.cpp:174), then passes it to TiffParser::encode() which writes directly to it.
With MemIo backed by read-only memory, this crashes:
TiffEntryBase::updateValue() at tiffcomposite_int.cpp:195
-> memset(pData_, 0x0, size_); // SIGSEGV: write to read-only memory
Expected behavior
mmap(true) should trigger COW, returning a writable copy of the buffer.
Actual behavior
Original (potentially read-only) pointer is returned, causing crash on write.
Affected formats
Unaffected formats
- JPEG, PNG, WebP (these rebuild the buffer entirely rather than modifying in-place)
Minimal reproducer
#include <exiv2/exiv2.hpp>
#include <fstream>
#include <vector>
int main() {
Exiv2::FileIo fileIo("test.tiff");
fileIo.open("rb"); // Open read-only
Exiv2::byte* data = fileIo.mmap(false); // Map as read-only
size_t size = fileIo.size();
// this memio should do COW!
auto memIo =
std::make_unique<Exiv2::MemIo>(data, size);
auto image = Exiv2::ImageFactory::open(std::move(memIo));
image->readMetadata();
image->exifData()["Exif.Image.Rating"] = uint16_t(3);
image->writeMetadata(); // CRASH on TIFF - MemIo::mmap(true) returns original buffer
return 0;
}
Note you could read-only mmap as input to make it easier to reproduce, but the fact is that even if you used a normal buffer read from file, said buffer would be modified.
Analysis
The root cause is that MemIo::mmap() doesn't implement the isWriteable parameter. The TIFF encoder expects mmap(true) to return a writable buffer (either because the source is writable, or via COW), but MemIo always returns the original pointer.
JPEG/PNG/WebP work because their encoders build a completely new output buffer via MemIo::write(), which does trigger COW. TIFF's encoder instead modifies the mmap'd buffer in-place.
Suggested fix
MemIo::mmap(true) should copy the buffer if it hasn't been copied yet (i.e., trigger COW), similar to how MemIo::write() does.
Environment
Description
The
MemIodocumentation states it uses copy-on-write:However,
MemIo::mmap(bool isWriteable)ignores the parameter entirely:When writing TIFF,
TiffImage::writeMetadata()callsio_->mmap(true)expecting a writable buffer (tiffimage.cpp:174), then passes it toTiffParser::encode()which writes directly to it.With MemIo backed by read-only memory, this crashes:
Expected behavior
mmap(true)should trigger COW, returning a writable copy of the buffer.Actual behavior
Original (potentially read-only) pointer is returned, causing crash on write.
Affected formats
Unaffected formats
Minimal reproducer
Note you could read-only mmap as input to make it easier to reproduce, but the fact is that even if you used a normal buffer read from file, said buffer would be modified.
Analysis
The root cause is that
MemIo::mmap()doesn't implement theisWriteableparameter. The TIFF encoder expectsmmap(true)to return a writable buffer (either because the source is writable, or via COW), but MemIo always returns the original pointer.JPEG/PNG/WebP work because their encoders build a completely new output buffer via
MemIo::write(), which does trigger COW. TIFF's encoder instead modifies the mmap'd buffer in-place.Suggested fix
MemIo::mmap(true)should copy the buffer if it hasn't been copied yet (i.e., trigger COW), similar to howMemIo::write()does.