Skip to content

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

@Lectem

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions