Skip to content

MemIo::mmap() ignores isWriteable parameter, causing TIFF writes to crash #3530

Description

@Lectem

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

  • TIFF
  • DNG (TIFF-based)

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions