From 2b4d5581b867c2d3a614ae951db33693533a1613 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Apr 2026 09:58:10 +0000 Subject: [PATCH 1/5] fix: handle BMP V4/V5 headers in cross-platform clipboard image transfer macOS generates BMP data with BITMAPV4HEADER (108 bytes) or BITMAPV5HEADER (124 bytes) for screenshots, but the BMP-to-DIB conversion hardcoded the header size to 40 bytes (BITMAPINFOHEADER). This truncated the header while biSize still indicated 108/124 bytes, causing Windows to read pixel data as header data and display a white/corrupt image. Fix toIClipboard: strip only the 14-byte BMP file header, keeping the entire DIB intact (header + color table + pixel data). Fix fromIClipboard: compute the BMP file pixel offset from the actual DIB header size and color table, instead of hardcoding 54. Applied the same fix to both OSX and XWindows clipboard converters. https://claude.ai/code/session_015Y82naHd7Tuj1inyFJtKmP --- src/lib/platform/OSXClipboardBMPConverter.cpp | 54 ++++++++++++++----- .../XWindowsClipboardBMPConverter.cpp | 53 +++++++++++++----- 2 files changed, 83 insertions(+), 24 deletions(-) diff --git a/src/lib/platform/OSXClipboardBMPConverter.cpp b/src/lib/platform/OSXClipboardBMPConverter.cpp index 637a1cf6d728..6958ad7e5b2f 100644 --- a/src/lib/platform/OSXClipboardBMPConverter.cpp +++ b/src/lib/platform/OSXClipboardBMPConverter.cpp @@ -20,6 +20,11 @@ struct CBMPHeader }; // BMP is little-endian +static inline uint16_t fromLEU16(const uint8_t *data) +{ + return static_cast(data[0]) | (static_cast(data[1]) << 8); +} + static inline uint32_t fromLEU32(const uint8_t *data) { return static_cast(data[0]) | (static_cast(data[1]) << 8) | @@ -62,7 +67,38 @@ CFStringRef OSXClipboardBMPConverter::getOSXFormat() const std::string OSXClipboardBMPConverter::fromIClipboard(const std::string &bmp) const { LOG_DEBUG1("getting data from clipboard"); - // create BMP image + + if (bmp.size() < 4) { + return std::string(); + } + + // read the actual DIB header size from biSize + const uint8_t *rawDIB = reinterpret_cast(bmp.data()); + uint32_t biSize = fromLEU32(rawDIB); + + // compute pixel data offset: file header + DIB header + color table + uint32_t pixelOffset = 14 + biSize; + + if (biSize >= 40 && bmp.size() >= 40) { + uint16_t biBitCount = fromLEU16(rawDIB + 14); + uint32_t biCompression = fromLEU32(rawDIB + 16); + uint32_t biClrUsed = fromLEU32(rawDIB + 32); + + // BITMAPINFOHEADER with BI_BITFIELDS has 3 DWORD color masks after header + if (biSize == 40 && biCompression == 3 /* BI_BITFIELDS */) { + pixelOffset += 3 * 4; + } + + // add color table size + if (biBitCount <= 8) { + uint32_t colors = biClrUsed ? biClrUsed : (1u << biBitCount); + pixelOffset += colors * 4; + } else if (biClrUsed > 0) { + pixelOffset += biClrUsed * 4; + } + } + + // create BMP file header uint8_t header[14]; uint8_t *dst = header; toLE(dst, 'B'); @@ -70,13 +106,13 @@ std::string OSXClipboardBMPConverter::fromIClipboard(const std::string &bmp) con toLE(dst, static_cast(14 + bmp.size())); toLE(dst, static_cast(0)); toLE(dst, static_cast(0)); - toLE(dst, static_cast(14 + 40)); + toLE(dst, pixelOffset); return std::string(reinterpret_cast(header), 14) + bmp; } std::string OSXClipboardBMPConverter::toIClipboard(const std::string &bmp) const { - // make sure data is big enough for a BMP file + // make sure data is big enough for a BMP file header + minimal DIB header if (bmp.size() <= 14 + 40) { return std::string(); } @@ -87,13 +123,7 @@ std::string OSXClipboardBMPConverter::toIClipboard(const std::string &bmp) const return std::string(); } - // get offset to image data - uint32_t offset = fromLEU32(rawBMPHeader + 10); - - // construct BMP - if (offset == 14 + 40) { - return bmp.substr(14); - } else { - return bmp.substr(14, 40) + bmp.substr(offset, bmp.size() - offset); - } + // strip the 14-byte BMP file header, keep the entire DIB + // (info header + optional color table + pixel data) + return bmp.substr(14); } diff --git a/src/lib/platform/XWindowsClipboardBMPConverter.cpp b/src/lib/platform/XWindowsClipboardBMPConverter.cpp index 8d103e3e93a0..6c9a9aac994f 100644 --- a/src/lib/platform/XWindowsClipboardBMPConverter.cpp +++ b/src/lib/platform/XWindowsClipboardBMPConverter.cpp @@ -19,6 +19,11 @@ struct CBMPHeader }; // BMP is little-endian +static inline uint16_t fromLEU16(const uint8_t *data) +{ + return static_cast(data[0]) | (static_cast(data[1]) << 8); +} + static inline uint32_t fromLEU32(const uint8_t *data) { return static_cast(data[0]) | (static_cast(data[1]) << 8) | @@ -74,7 +79,37 @@ int XWindowsClipboardBMPConverter::getDataSize() const std::string XWindowsClipboardBMPConverter::fromIClipboard(const std::string &bmp) const { - // create BMP image + if (bmp.size() < 4) { + return std::string(); + } + + // read the actual DIB header size from biSize + const uint8_t *rawDIB = reinterpret_cast(bmp.data()); + uint32_t biSize = fromLEU32(rawDIB); + + // compute pixel data offset: file header + DIB header + color table + uint32_t pixelOffset = 14 + biSize; + + if (biSize >= 40 && bmp.size() >= 40) { + uint16_t biBitCount = fromLEU16(rawDIB + 14); + uint32_t biCompression = fromLEU32(rawDIB + 16); + uint32_t biClrUsed = fromLEU32(rawDIB + 32); + + // BITMAPINFOHEADER with BI_BITFIELDS has 3 DWORD color masks after header + if (biSize == 40 && biCompression == 3 /* BI_BITFIELDS */) { + pixelOffset += 3 * 4; + } + + // add color table size + if (biBitCount <= 8) { + uint32_t colors = biClrUsed ? biClrUsed : (1u << biBitCount); + pixelOffset += colors * 4; + } else if (biClrUsed > 0) { + pixelOffset += biClrUsed * 4; + } + } + + // create BMP file header uint8_t header[14]; uint8_t *dst = header; toLE(dst, 'B'); @@ -82,13 +117,13 @@ std::string XWindowsClipboardBMPConverter::fromIClipboard(const std::string &bmp toLE(dst, static_cast(14 + bmp.size())); toLE(dst, static_cast(0)); toLE(dst, static_cast(0)); - toLE(dst, static_cast(14 + 40)); + toLE(dst, pixelOffset); return std::string(reinterpret_cast(header), 14) + bmp; } std::string XWindowsClipboardBMPConverter::toIClipboard(const std::string &bmp) const { - // make sure data is big enough for a BMP file + // make sure data is big enough for a BMP file header + minimal DIB header if (bmp.size() <= 14 + 40) { return std::string(); } @@ -99,13 +134,7 @@ std::string XWindowsClipboardBMPConverter::toIClipboard(const std::string &bmp) return std::string(); } - // get offset to image data - uint32_t offset = fromLEU32(rawBMPHeader + 10); - - // construct BMP - if (offset == 14 + 40) { - return bmp.substr(14); - } else { - return bmp.substr(14, 40) + bmp.substr(offset, bmp.size() - offset); - } + // strip the 14-byte BMP file header, keep the entire DIB + // (info header + optional color table + pixel data) + return bmp.substr(14); } From e5d3af4e0024228d4a07c67491d283d66c5deada Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Apr 2026 03:23:40 +0000 Subject: [PATCH 2/5] fix: handle V4/V5 DIB headers in Windows bitmap clipboard converter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When Windows re-reads CF_DIB data with a BITMAPV4HEADER (108 bytes) or BITMAPV5HEADER (124 bytes), the pixel data pointer was calculated incorrectly. The code unconditionally added 3 DWORDs (12 bytes) for BI_BITFIELDS color masks after the header, but V4/V5 headers include these masks within the header itself. This caused srcBits to point 12 bytes past the actual pixel data start, potentially reading past the allocated buffer and crashing the server — which disconnected the macOS client. Fix: only add the extra color mask offset for BITMAPINFOHEADER (biSize == 40), since V4/V5 headers already contain the masks. https://claude.ai/code/session_015Y82naHd7Tuj1inyFJtKmP --- .../MSWindowsClipboardBitmapConverter.cpp | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/lib/platform/MSWindowsClipboardBitmapConverter.cpp b/src/lib/platform/MSWindowsClipboardBitmapConverter.cpp index 16150c32fc16..729b41fc0b6f 100644 --- a/src/lib/platform/MSWindowsClipboardBitmapConverter.cpp +++ b/src/lib/platform/MSWindowsClipboardBitmapConverter.cpp @@ -88,16 +88,20 @@ std::string MSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const // find the start of the pixel data const char *srcBits = (const char *)bitmap + bitmap->bmiHeader.biSize; - if (bitmap->bmiHeader.biBitCount >= 16) { - if (bitmap->bmiHeader.biCompression == BI_BITFIELDS && - (bitmap->bmiHeader.biBitCount == 16 || bitmap->bmiHeader.biBitCount == 32)) { - srcBits += 3 * sizeof(DWORD); + // Only BITMAPINFOHEADER (40 bytes) has color masks/table after the header. + // BITMAPV4HEADER (108 bytes) and BITMAPV5HEADER (124 bytes) include + // masks and color space info within the header itself. + if (bitmap->bmiHeader.biSize == sizeof(BITMAPINFOHEADER)) { + if (bitmap->bmiHeader.biBitCount >= 16) { + if (bitmap->bmiHeader.biCompression == BI_BITFIELDS && + (bitmap->bmiHeader.biBitCount == 16 || bitmap->bmiHeader.biBitCount == 32)) { + srcBits += 3 * sizeof(DWORD); + } + } else if (bitmap->bmiHeader.biClrUsed != 0) { + srcBits += bitmap->bmiHeader.biClrUsed * sizeof(RGBQUAD); + } else { + srcBits += (1i64 << bitmap->bmiHeader.biBitCount) * sizeof(RGBQUAD); } - } else if (bitmap->bmiHeader.biClrUsed != 0) { - srcBits += bitmap->bmiHeader.biClrUsed * sizeof(RGBQUAD); - } else { - // http://msdn.microsoft.com/en-us/library/ke55d167(VS.80).aspx - srcBits += (1i64 << bitmap->bmiHeader.biBitCount) * sizeof(RGBQUAD); } // copy source image to destination image From 20498e17a376540c97a61d20bc734538a6d457b5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Apr 2026 03:53:59 +0000 Subject: [PATCH 3/5] fix: prevent macOS client disconnect when clipboard has file data When files are copied on macOS (e.g., in Finder), the pasteboard contains file reference data but no native image types. However, macOS's UTI auto-conversion may claim com.microsoft.bmp is available and attempt to convert file references to BMP format. This conversion can hang indefinitely, blocking the client's event loop and causing a keep-alive timeout disconnect from the server. Fix: before checking for bitmap clipboard data, verify that the pasteboard actually contains native image data (public.tiff or public.png). Skip bitmap processing entirely when only file references are present. https://claude.ai/code/session_015Y82naHd7Tuj1inyFJtKmP --- src/lib/platform/OSXClipboard.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/platform/OSXClipboard.cpp b/src/lib/platform/OSXClipboard.cpp index 02608108ce57..7d76cd9e0ae1 100644 --- a/src/lib/platform/OSXClipboard.cpp +++ b/src/lib/platform/OSXClipboard.cpp @@ -139,6 +139,20 @@ bool OSXClipboard::has(Format format) const PasteboardItemID item; PasteboardGetItemIdentifier(m_pboard, (CFIndex)1, &item); + // For bitmap format, only report available if the pasteboard actually + // contains native image data (TIFF or PNG). Without this check, macOS + // may claim com.microsoft.bmp is available via UTI auto-conversion + // for non-image pasteboard content (e.g. file references), but the + // actual conversion can hang and block the event loop, causing the + // client to disconnect due to keep-alive timeout. + if (format == IClipboard::Format::Bitmap) { + PasteboardFlavorFlags flags; + if (PasteboardGetItemFlavorFlags(m_pboard, item, CFSTR("public.tiff"), &flags) != noErr && + PasteboardGetItemFlavorFlags(m_pboard, item, CFSTR("public.png"), &flags) != noErr) { + return false; + } + } + for (ConverterList::const_iterator index = m_converters.begin(); index != m_converters.end(); ++index) { IOSXClipboardConverter *converter = *index; if (converter->getFormat() == format) { From 9da918866ae9956dd8df4fdd4a99ab9fa7b4bc2b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Apr 2026 04:04:24 +0000 Subject: [PATCH 4/5] fix: harden clipboard code against edge cases in normal use Fixes across all platforms for issues that can occur during regular clipboard operations: Windows: - Fix out-of-bounds read in convertLinefeedToUnix when text ends with \r without following \n (check n > 1 before accessing scan[1]) - Fix missing GlobalUnlock when srcSize <= 1 in text converter - Add null checks for GetDC, CreateDIBSection, CreateCompatibleDC in bitmap converter to prevent crash under GDI resource pressure - Add integer overflow protection for large bitmap dimensions (4 * w * h) using 64-bit arithmetic and reasonable size limits - Validate biBitCount range before bit shift in color table calc - Add bounds check for HTML fragment start/end offsets macOS / XWindows: - Validate biSize range (40-1024) in BMP fromIClipboard to prevent reading past buffer with unusual header sizes - Ensure buffer is large enough before reading DIB header fields - Fix CFStringGetBytes buffSize <= 0 not checked before allocation in text and HTML converters (was checking new return instead) Common: - Add bounds checking in IClipboard::unmarshall to prevent reading past buffer end with truncated/corrupt network data - Fix ClipboardChunk::assemble toULong parse error not checked - Fix LOG_ERR format specifier: use %zu for size_t values - Fix StreamChunker infinite loop when size == 0 https://claude.ai/code/session_015Y82naHd7Tuj1inyFJtKmP --- src/lib/deskflow/ClipboardChunk.cpp | 12 ++++- src/lib/deskflow/IClipboard.cpp | 15 ++++++ src/lib/deskflow/StreamChunker.cpp | 5 +- .../MSWindowsClipboardAnyTextConverter.cpp | 9 ++-- .../MSWindowsClipboardBitmapConverter.cpp | 46 ++++++++++++++++--- .../MSWindowsClipboardHTMLConverter.cpp | 2 +- src/lib/platform/OSXClipboardBMPConverter.cpp | 7 ++- .../platform/OSXClipboardHTMLConverter.cpp | 6 +-- .../platform/OSXClipboardTextConverter.cpp | 6 +-- .../XWindowsClipboardBMPConverter.cpp | 7 ++- 10 files changed, 91 insertions(+), 24 deletions(-) diff --git a/src/lib/deskflow/ClipboardChunk.cpp b/src/lib/deskflow/ClipboardChunk.cpp index 7abbdba1aa8b..61cb426d7257 100644 --- a/src/lib/deskflow/ClipboardChunk.cpp +++ b/src/lib/deskflow/ClipboardChunk.cpp @@ -74,7 +74,12 @@ ClipboardChunk::assemble(deskflow::IStream *stream, std::string &dataCached, Cli } if (mark == ChunkType::DataStart) { - s_expectedSize = QString::fromStdString(data).toULong(); + bool ok = false; + s_expectedSize = QString::fromStdString(data).toULong(&ok); + if (!ok) { + LOG_ERR("invalid clipboard size string"); + return Error; + } LOG_DEBUG("start receiving clipboard data"); dataCached.clear(); return Started; @@ -86,7 +91,10 @@ ClipboardChunk::assemble(deskflow::IStream *stream, std::string &dataCached, Cli if (id >= kClipboardEnd) { return Error; } else if (s_expectedSize != dataCached.size()) { - LOG_ERR("corrupted clipboard data, expected size=%d actual size=%d", s_expectedSize, dataCached.size()); + LOG_ERR( + "corrupted clipboard data, expected size=%zu actual size=%zu", + static_cast(s_expectedSize), dataCached.size() + ); return Error; } return Finished; diff --git a/src/lib/deskflow/IClipboard.cpp b/src/lib/deskflow/IClipboard.cpp index 4b732ad2f7af..ec19a21c644a 100644 --- a/src/lib/deskflow/IClipboard.cpp +++ b/src/lib/deskflow/IClipboard.cpp @@ -19,17 +19,27 @@ void IClipboard::unmarshall(IClipboard *clipboard, const std::string_view &data, assert(clipboard != nullptr); const char *index = data.data(); + const char *dataEnd = data.data() + data.size(); if (clipboard->open(time)) { // clear existing data clipboard->empty(); // read the number of formats + if (index + 4 > dataEnd) { + clipboard->close(); + return; + } const uint32_t numFormats = readUInt32(index); index += 4; // read each format for (uint32_t i = 0; i < numFormats; ++i) { + // need at least 8 bytes for format id + size + if (index + 8 > dataEnd) { + break; + } + // get the format id auto format = static_cast(readUInt32(index)); index += 4; @@ -38,6 +48,11 @@ void IClipboard::unmarshall(IClipboard *clipboard, const std::string_view &data, uint32_t size = readUInt32(index); index += 4; + // validate size doesn't exceed remaining data + if (size > static_cast(dataEnd - index)) { + break; + } + // save the data if it's a known format. if either the client // or server supports more clipboard formats than the other // then one of them will get a format >= TotalFormats here. diff --git a/src/lib/deskflow/StreamChunker.cpp b/src/lib/deskflow/StreamChunker.cpp index b24b712c9cd3..2d8cee5677a0 100644 --- a/src/lib/deskflow/StreamChunker.cpp +++ b/src/lib/deskflow/StreamChunker.cpp @@ -27,7 +27,7 @@ void StreamChunker::sendClipboard( size_t sentLength = 0; size_t chunkSize = g_chunkSize; - while (true) { + while (sentLength < size) { // make sure we don't read too much from the mock data. if (sentLength + chunkSize > size) { chunkSize = size - sentLength; @@ -39,9 +39,6 @@ void StreamChunker::sendClipboard( events->addEvent(Event(EventTypes::ClipboardSending, eventTarget, dataChunk)); sentLength += chunkSize; - if (sentLength == size) { - break; - } } // send last message diff --git a/src/lib/platform/MSWindowsClipboardAnyTextConverter.cpp b/src/lib/platform/MSWindowsClipboardAnyTextConverter.cpp index 655b2becf2e9..b157e3764251 100644 --- a/src/lib/platform/MSWindowsClipboardAnyTextConverter.cpp +++ b/src/lib/platform/MSWindowsClipboardAnyTextConverter.cpp @@ -42,10 +42,13 @@ MSWindowsClipboardAnyTextConverter::fromIClipboard(const std::string &data) cons std::string MSWindowsClipboardAnyTextConverter::toIClipboard(HANDLE data) const { - // get datator + // get data pointer const char *src = (const char *)GlobalLock(data); uint32_t srcSize = (uint32_t)GlobalSize(data); if (src == nullptr || srcSize <= 1) { + if (src != nullptr) { + GlobalUnlock(data); + } return std::string(); } @@ -97,7 +100,7 @@ std::string MSWindowsClipboardAnyTextConverter::convertLinefeedToUnix(const std: uint32_t numNewlines = 0; uint32_t n = (uint32_t)src.size(); for (const char *scan = src.c_str(); n > 0; ++scan, --n) { - if (scan[0] == '\r' && scan[1] == '\n') { + if (scan[0] == '\r' && n > 1 && scan[1] == '\n') { ++numNewlines; } } @@ -112,7 +115,7 @@ std::string MSWindowsClipboardAnyTextConverter::convertLinefeedToUnix(const std: // copy string, converting newlines n = (uint32_t)src.size(); for (const char *scan = src.c_str(); n > 0; ++scan, --n) { - if (scan[0] != '\r' || scan[1] != '\n') { + if (scan[0] != '\r' || n <= 1 || scan[1] != '\n') { dst += scan[0]; } } diff --git a/src/lib/platform/MSWindowsClipboardBitmapConverter.cpp b/src/lib/platform/MSWindowsClipboardBitmapConverter.cpp index 729b41fc0b6f..69194df94e80 100644 --- a/src/lib/platform/MSWindowsClipboardBitmapConverter.cpp +++ b/src/lib/platform/MSWindowsClipboardBitmapConverter.cpp @@ -45,13 +45,18 @@ MSWindowsClipboardBitmapConverter::fromIClipboard(const std::string &data) const std::string MSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const { - // get datator + // get data pointer LPVOID src = GlobalLock(data); if (src == nullptr) { return std::string(); } uint32_t srcSize = (uint32_t)GlobalSize(data); + if (srcSize < sizeof(BITMAPINFOHEADER)) { + GlobalUnlock(data); + return std::string(); + } + // check image type const BITMAPINFO *bitmap = static_cast(src); LOG( @@ -66,12 +71,26 @@ std::string MSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const return image; } + // validate dimensions + LONG w = bitmap->bmiHeader.biWidth; + LONG h = bitmap->bmiHeader.biHeight; + if (w <= 0 || h == 0) { + GlobalUnlock(data); + return std::string(); + } + LONG absH = (h > 0) ? h : -h; + + // check for integer overflow in pixel data size (4 bytes per pixel) + if (w > 32767 || absH > 32767 || static_cast(w) * absH > 0x40000000ULL) { + LOG_WARN("bitmap too large: %dx%d", w, absH); + GlobalUnlock(data); + return std::string(); + } + // create a destination DIB section LOG_INFO("convert image from: depth=%d comp=%d", bitmap->bmiHeader.biBitCount, bitmap->bmiHeader.biCompression); void *raw; BITMAPINFOHEADER info; - LONG w = bitmap->bmiHeader.biWidth; - LONG h = bitmap->bmiHeader.biHeight; info.biSize = sizeof(BITMAPINFOHEADER); info.biWidth = w; info.biHeight = h; @@ -84,7 +103,16 @@ std::string MSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const info.biClrUsed = 0; info.biClrImportant = 0; HDC dc = GetDC(nullptr); + if (dc == nullptr) { + GlobalUnlock(data); + return std::string(); + } HBITMAP dst = CreateDIBSection(dc, (BITMAPINFO *)&info, DIB_RGB_COLORS, &raw, nullptr, 0); + if (dst == nullptr) { + ReleaseDC(nullptr, dc); + GlobalUnlock(data); + return std::string(); + } // find the start of the pixel data const char *srcBits = (const char *)bitmap + bitmap->bmiHeader.biSize; @@ -99,22 +127,28 @@ std::string MSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const } } else if (bitmap->bmiHeader.biClrUsed != 0) { srcBits += bitmap->bmiHeader.biClrUsed * sizeof(RGBQUAD); - } else { + } else if (bitmap->bmiHeader.biBitCount > 0 && bitmap->bmiHeader.biBitCount <= 8) { srcBits += (1i64 << bitmap->bmiHeader.biBitCount) * sizeof(RGBQUAD); } } // copy source image to destination image HDC dstDC = CreateCompatibleDC(dc); + if (dstDC == nullptr) { + DeleteObject(dst); + ReleaseDC(nullptr, dc); + GlobalUnlock(data); + return std::string(); + } HGDIOBJ oldBitmap = SelectObject(dstDC, dst); - SetDIBitsToDevice(dstDC, 0, 0, w, h, 0, 0, 0, h, srcBits, bitmap, DIB_RGB_COLORS); + SetDIBitsToDevice(dstDC, 0, 0, w, absH, 0, 0, 0, absH, srcBits, bitmap, DIB_RGB_COLORS); SelectObject(dstDC, oldBitmap); DeleteDC(dstDC); GdiFlush(); // extract data std::string image((const char *)&info, info.biSize); - image.append((const char *)raw, 4 * w * h); + image.append((const char *)raw, 4LL * w * absH); // clean up GDI DeleteObject(dst); diff --git a/src/lib/platform/MSWindowsClipboardHTMLConverter.cpp b/src/lib/platform/MSWindowsClipboardHTMLConverter.cpp index 9f55bb070a9f..3ee36d450d98 100644 --- a/src/lib/platform/MSWindowsClipboardHTMLConverter.cpp +++ b/src/lib/platform/MSWindowsClipboardHTMLConverter.cpp @@ -67,7 +67,7 @@ std::string MSWindowsClipboardHTMLConverter::doToIClipboard(const std::string &d // convert args to integers int32_t start = (int32_t)atoi(startArg.c_str()); int32_t end = (int32_t)atoi(endArg.c_str()); - if (start <= 0 || end <= 0 || start >= end) { + if (start <= 0 || end <= 0 || start >= end || static_cast(end) > data.size()) { return std::string(); } diff --git a/src/lib/platform/OSXClipboardBMPConverter.cpp b/src/lib/platform/OSXClipboardBMPConverter.cpp index 6958ad7e5b2f..28655759d392 100644 --- a/src/lib/platform/OSXClipboardBMPConverter.cpp +++ b/src/lib/platform/OSXClipboardBMPConverter.cpp @@ -76,10 +76,15 @@ std::string OSXClipboardBMPConverter::fromIClipboard(const std::string &bmp) con const uint8_t *rawDIB = reinterpret_cast(bmp.data()); uint32_t biSize = fromLEU32(rawDIB); + // validate biSize is reasonable + if (biSize < 40 || biSize > 1024) { + return std::string(); + } + // compute pixel data offset: file header + DIB header + color table uint32_t pixelOffset = 14 + biSize; - if (biSize >= 40 && bmp.size() >= 40) { + if (bmp.size() >= biSize) { uint16_t biBitCount = fromLEU16(rawDIB + 14); uint32_t biCompression = fromLEU32(rawDIB + 16); uint32_t biClrUsed = fromLEU32(rawDIB + 32); diff --git a/src/lib/platform/OSXClipboardHTMLConverter.cpp b/src/lib/platform/OSXClipboardHTMLConverter.cpp index 460c86893ef0..8c4ba4a39978 100644 --- a/src/lib/platform/OSXClipboardHTMLConverter.cpp +++ b/src/lib/platform/OSXClipboardHTMLConverter.cpp @@ -34,13 +34,13 @@ std::string OSXClipboardHTMLConverter::convertString( CFStringGetBytes(stringRef, entireString, toEncoding, 0, false, nullptr, 0, &buffSize); - char *buffer = new char[buffSize]; - - if (buffer == nullptr) { + if (buffSize <= 0) { CFRelease(stringRef); return std::string(); } + char *buffer = new char[buffSize]; + CFStringGetBytes(stringRef, entireString, toEncoding, 0, false, (uint8_t *)buffer, buffSize, nullptr); std::string result(buffer, buffSize); diff --git a/src/lib/platform/OSXClipboardTextConverter.cpp b/src/lib/platform/OSXClipboardTextConverter.cpp index 51b24fc305c4..651ee48fb0a5 100644 --- a/src/lib/platform/OSXClipboardTextConverter.cpp +++ b/src/lib/platform/OSXClipboardTextConverter.cpp @@ -31,13 +31,13 @@ std::string OSXClipboardTextConverter::convertString( CFStringGetBytes(stringRef, entireString, toEncoding, 0, false, nullptr, 0, &buffSize); - char *buffer = new char[buffSize]; - - if (buffer == nullptr) { + if (buffSize <= 0) { CFRelease(stringRef); return std::string(); } + char *buffer = new char[buffSize]; + CFStringGetBytes(stringRef, entireString, toEncoding, 0, false, (uint8_t *)buffer, buffSize, nullptr); std::string result(buffer, buffSize); diff --git a/src/lib/platform/XWindowsClipboardBMPConverter.cpp b/src/lib/platform/XWindowsClipboardBMPConverter.cpp index 6c9a9aac994f..038183f94ecc 100644 --- a/src/lib/platform/XWindowsClipboardBMPConverter.cpp +++ b/src/lib/platform/XWindowsClipboardBMPConverter.cpp @@ -87,10 +87,15 @@ std::string XWindowsClipboardBMPConverter::fromIClipboard(const std::string &bmp const uint8_t *rawDIB = reinterpret_cast(bmp.data()); uint32_t biSize = fromLEU32(rawDIB); + // validate biSize is reasonable + if (biSize < 40 || biSize > 1024) { + return std::string(); + } + // compute pixel data offset: file header + DIB header + color table uint32_t pixelOffset = 14 + biSize; - if (biSize >= 40 && bmp.size() >= 40) { + if (bmp.size() >= biSize) { uint16_t biBitCount = fromLEU16(rawDIB + 14); uint32_t biCompression = fromLEU32(rawDIB + 16); uint32_t biClrUsed = fromLEU32(rawDIB + 32); From a9f42ad5442b113804b5becba0a537b2458621be Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Apr 2026 08:24:40 +0000 Subject: [PATCH 5/5] style: fix clang-format formatting in ClipboardChunk.cpp https://claude.ai/code/session_015Y82naHd7Tuj1inyFJtKmP --- src/lib/deskflow/ClipboardChunk.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/deskflow/ClipboardChunk.cpp b/src/lib/deskflow/ClipboardChunk.cpp index 61cb426d7257..1a5f3fd61099 100644 --- a/src/lib/deskflow/ClipboardChunk.cpp +++ b/src/lib/deskflow/ClipboardChunk.cpp @@ -92,8 +92,8 @@ ClipboardChunk::assemble(deskflow::IStream *stream, std::string &dataCached, Cli return Error; } else if (s_expectedSize != dataCached.size()) { LOG_ERR( - "corrupted clipboard data, expected size=%zu actual size=%zu", - static_cast(s_expectedSize), dataCached.size() + "corrupted clipboard data, expected size=%zu actual size=%zu", static_cast(s_expectedSize), + dataCached.size() ); return Error; }