diff --git a/src/lib/deskflow/ClipboardChunk.cpp b/src/lib/deskflow/ClipboardChunk.cpp index 7abbdba1aa8b..1a5f3fd61099 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 16150c32fc16..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,33 +103,52 @@ 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; - 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 if (bitmap->bmiHeader.biBitCount > 0 && bitmap->bmiHeader.biBitCount <= 8) { + 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 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/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) { diff --git a/src/lib/platform/OSXClipboardBMPConverter.cpp b/src/lib/platform/OSXClipboardBMPConverter.cpp index 637a1cf6d728..28655759d392 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,43 @@ 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); + + // 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 (bmp.size() >= biSize) { + 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 +111,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 +128,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/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 8d103e3e93a0..038183f94ecc 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,42 @@ 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); + + // 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 (bmp.size() >= biSize) { + 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 +122,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 +139,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); }