Compare commits

...

15 Commits

Author SHA1 Message Date
Nicolas Fella
ae279c55f4 Update dependency version to 6.24.0 2026-03-07 21:18:31 +01:00
Mirco Miranda
836e0a53bb JP2: fix possible Undefined-shift 2026-03-04 08:15:48 +01:00
Mirco Miranda
5eb09116b0 IFF: fix buffer read overflow 2026-02-24 08:57:45 +01:00
Mirco Miranda
92368ca58f Fix Heap-buffer-overflow WRITE 2026-02-22 18:27:34 +00:00
Mirco Miranda
a91c7ef72f Fixed excessively frequent warning messages 2026-02-22 14:56:21 +01:00
Daniel Novomeský
ea2a4aafab ossfuzz: update aom, libavif, openjpeg 2026-02-19 11:31:12 +01:00
Mirco Miranda
c254875780 ANI: fix possible QByteArray allocation exception 2026-02-16 10:12:05 +01:00
Daniel Novomeský
f3de2e77c1 jxl: adjust metadata size limits
Previously there was no limit for uncompressed metadata,
but when compressed metadata required buffer larger than 4MB,
image decoding stopped.

Now the plugin discards/skips metadata boxes larger than 8MB
without stopping image decoding.
If size of compressed box is above 8MB,
we do not attempt to decompress it.
File with compressed metadata could be rejected only in rare cases
when the decompression buffer grows above 32MB (four times 8M).
2026-02-13 15:44:39 +01:00
Mirco Miranda
1ef779f370 RGB: fix a possible exception on the new 2026-02-12 13:42:58 +01:00
Laurent Montel
169a874cba GIT_SILENT: Bump kf ecm_set_disabled_deprecation_versions. Make sure that it compiles fine without kf 6.23 deprecated methods 2026-02-10 07:52:45 +01:00
Mirco Miranda
ebf77ccdf5 TGA: fix Undefined-shift 2026-02-09 23:02:06 +00:00
Mirco Miranda
359cb039d2 PSD: improve conversion sanity checks 2026-02-09 22:50:51 +00:00
Mirco Miranda
f4b91d8a54 IFF: fix compilation warnings 2026-02-09 22:46:04 +00:00
Mirco Miranda
263b5a88e2 ANI: check for array allocation size 2026-02-09 08:41:40 +01:00
Nicolas Fella
8d07f7db1b Update version to 6.24.0 2026-02-06 13:31:01 +01:00
9 changed files with 78 additions and 37 deletions

View File

@@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.27)
set(KF_VERSION "6.23.0") # handled by release scripts
set(KF_DEP_VERSION "6.23.0") # handled by release scripts
set(KF_VERSION "6.24.0") # handled by release scripts
set(KF_DEP_VERSION "6.24.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION})
include(FeatureSummary)
find_package(ECM 6.23.0 NO_MODULE)
find_package(ECM 6.24.0 NO_MODULE)
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
@@ -106,7 +106,7 @@ add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR
ecm_set_disabled_deprecation_versions(
QT 6.11.0
KF 6.21.0
KF 6.23.0
)
add_subdirectory(src)

View File

@@ -33,10 +33,10 @@ git clone --depth 1 -b master https://invent.kde.org/frameworks/extra-cmake-modu
git clone --depth 1 --branch=dev git://code.qt.io/qt/qtbase.git
git clone --depth 1 --branch=dev git://code.qt.io/qt/qttools.git
git clone --depth 1 -b master https://invent.kde.org/frameworks/karchive.git
git clone --depth 1 -b v3.12.0 https://aomedia.googlesource.com/aom
git clone --depth 1 -b v1.2.1 https://github.com/AOMediaCodec/libavif.git
git clone --depth 1 -b v3.13.1 https://aomedia.googlesource.com/aom
git clone --depth 1 -b v1.3.0 https://github.com/AOMediaCodec/libavif.git
git clone --depth 1 https://github.com/strukturag/libde265.git
git clone --depth 1 -b v2.5.3 https://github.com/uclouvain/openjpeg.git
git clone --depth 1 -b v2.5.4 https://github.com/uclouvain/openjpeg.git
git clone --depth 1 https://github.com/strukturag/libheif.git
git clone --depth=1 --recursive --shallow-submodules https://github.com/libjxl/libjxl.git
git clone --depth 1 https://github.com/LibRaw/LibRaw

View File

@@ -5,6 +5,7 @@
*/
#include "ani_p.h"
#include "util_p.h"
#include <QImage>
#include <QLoggingCategory>
@@ -101,7 +102,7 @@ bool ANIHandler::read(QImage *outImage)
}
const auto frameSize = *(reinterpret_cast<const quint32_le *>(frameSizeData.data()));
if (!frameSize) {
if (!frameSize || frameSize > quint32(kMaxQVectorSize)) {
return false;
}
@@ -417,6 +418,9 @@ bool ANIHandler::ensureScanned() const
// IART and INAM are technically inside LIST->INFO but "INFO" is supposedly optional
// so just handle those two attributes wherever we encounter them
} else if (chunkId == "INAM" || chunkId == "IART") {
if (chunkSize > kMaxQVectorSize) {
return false;
}
const QByteArray value = device()->read(chunkSize);
if (static_cast<quint32_le>(value.size()) != chunkSize) {

View File

@@ -1080,6 +1080,7 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMH
// (red and green modify operations are unavailable)
auto ctlbits = bitplanes > 5 ? 2 : 1;
auto max = (1 << (bitplanes - ctlbits)) - 1;
auto wrongIdx = false;
quint8 prev[3] = {};
for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
for (qint32 j = 0; j < 8; ++j, ++cnt) {
@@ -1111,7 +1112,7 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMH
prev[1] = qGreen(pal.at(idx));
prev[2] = qBlue(pal.at(idx));
} else {
qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave(): palette index" << idx << "is out of range";
wrongIdx = true;
}
break;
}
@@ -1121,6 +1122,9 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMH
ba[cnt3 + 2] = char(prev[2]);
}
}
if (wrongIdx) {
qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave(): HAM palette index out of range!";
}
} else if ((modeId & CAMGChunk::ModeId::HalfBrite) && (cmap) &&
(bitplanes >= BITPLANES_HALFBRIDE_MIN && bitplanes <= BITPLANES_HALFBRIDE_MAX)) {
// From A Quick Introduction to IFF.txt:
@@ -1133,6 +1137,7 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMH
// absolute colors.
ba = QByteArray(rowLen * 8, char());
auto palSize = cmap->count();
auto wrongIdx = false;
for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
for (qint32 j = 0; j < 8; ++j, ++cnt) {
quint8 idx = 0, ctl = 0;
@@ -1147,10 +1152,13 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMH
if (idx < palSize) {
ba[cnt] = ctl ? idx + palSize : idx;
} else {
qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave(): palette index" << idx << "is out of range";
wrongIdx = true;
}
}
}
if (wrongIdx) {
qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave(): HalfBrite palette index out of range!";
}
} else {
// From A Quick Introduction to IFF.txt:
//
@@ -2538,7 +2546,7 @@ static QByteArray decompressVdat(const QByteArray &comp)
static QByteArray vdatToIlbmPlane(const QByteArray &vdatData, const BMHDChunk *header)
{
QByteArray ba(vdatData.size(), char());
auto rowLen = header->rowLen();
auto rowLen = qint32(header->rowLen());
for (auto x = 0, n = 0; x < rowLen; x += 2) {
for (auto y = 0, off = x, h = header->height(); y < h; y++, off += rowLen) {
if ((off + 1 >= ba.size()) || n + 1 >= vdatData.size()) {
@@ -2844,7 +2852,7 @@ bool PLTEChunk::isValid() const
if (dataBytes() < 4) {
return false;
}
if (dataBytes() - 4 < total() * 3) {
if (dataBytes() - 4 < quint32(total()) * 3) {
return false;
}
return chunkId() == PLTEChunk::defaultChunkId();
@@ -3004,7 +3012,7 @@ QByteArray IDATChunk::strideRead(QIODevice *d, qint32 y, const IHDRChunk *header
}
if (header->model() == IHDRChunk::CLut4) {
if (rr.size() < header->width() / 2) {
if (rr.size() < (qint64(header->width()) + 1) / 2) {
return {};
}
QByteArray tmp(header->width(), char());

View File

@@ -253,20 +253,27 @@ public:
bool jp2ToImage(QImage *img) const
{
Q_ASSERT(img->depth() == 8 * sizeof(T) || img->depth() == 32 * sizeof(T));
for (qint32 c = 0, cc = m_jp2_image->numcomps; c < cc; ++c) {
auto cs = cc == 1 ? 1 : 4;
if (img->width() < 1 || img->height() < 1) {
return false;
}
auto maxChannels = qint32(img->bytesPerLine() / sizeof(T) / img->width());
for (qint32 c = 0, cc = std::min(qint32(m_jp2_image->numcomps), maxChannels); c < cc; ++c) {
auto cs = std::min(cc == 1 ? 1 : 4, maxChannels);
auto &&jc = m_jp2_image->comps[c];
if (jc.data == nullptr)
if (jc.data == nullptr) {
return false;
if (qint32(jc.w) != img->width() || qint32(jc.h) != img->height())
}
if (qint32(jc.w) != img->width() || qint32(jc.h) != img->height()) {
return false;
}
// discriminate between int and float (avoid complicating things by creating classes with template specializations)
if (std::numeric_limits<T>::is_integer) {
auto divisor = 1;
if (jc.prec > sizeof(T) * 8) {
auto divisor = 1ull;
auto prec = std::min(size_t(jc.prec), sizeof(*jc.data) * 8);
if (prec > sizeof(T) * 8 && prec < 64) {
// convert to the wanted precision (e.g. 16-bit -> 8-bit: divisor = 65535 / 255 = 257)
divisor = std::max(1, int(((1ll << jc.prec) - 1) / ((1ll << (sizeof(T) * 8)) - 1)));
divisor = std::max(1ull, (((1ull << prec) - 1) / ((1ull << (sizeof(T) * 8)) - 1)));
}
for (qint32 y = 0, h = img->height(); y < h; ++y) {
auto ptr = reinterpret_cast<T *>(img->scanLine(y));

View File

@@ -2008,6 +2008,11 @@ bool QJpegXLHandler::extractBox(QByteArray &output, size_t container_size)
return false;
}
if (rawboxsize > 8388608) { // 8MB limit
qCWarning(LOG_JXLPLUGIN, "Skipped decoding of big JXL metadata box");
return true;
}
output.resize(rawboxsize);
status = JxlDecoderSetBoxBuffer(m_decoder, reinterpret_cast<uint8_t *>(output.data()), output.size());
if (status != JXL_DEC_SUCCESS) {
@@ -2021,7 +2026,7 @@ bool QJpegXLHandler::extractBox(QByteArray &output, size_t container_size)
if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
size_t bytes_remains = JxlDecoderReleaseBoxBuffer(m_decoder);
if (output.size() > 4194304) { // approx. 4MB limit for decompressed metadata box
if (output.size() > 33554432) { // approx. 32MB (4*8) limit for decompressed metadata box
qCWarning(LOG_JXLPLUGIN, "JXL metadata box is too large");
m_parseState = ParseJpegXLError;
return false;

View File

@@ -1017,7 +1017,7 @@ inline void rawChannelCopy(uchar *target, qint32 targetChannels, qint32 targetCh
template<class T>
inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
inline bool cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
{
auto s = reinterpret_cast<const T*>(source);
auto t = reinterpret_cast<T*>(target);
@@ -1026,7 +1026,7 @@ inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source,
if (sourceChannels < 2) {
qCDebug(LOG_PSDPLUGIN) << "cmykToRgb: image is not a valid MCH/CMYK!";
return;
return false;
}
for (qint32 w = 0; w < width; ++w) {
@@ -1047,6 +1047,7 @@ inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source,
*(pt + 3) = std::numeric_limits<T>::max();
}
}
return true;
}
inline double finv(double v)
@@ -1066,7 +1067,7 @@ inline double gammaCorrection(double linear)
}
template<class T>
inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
inline bool labToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false)
{
auto s = reinterpret_cast<const T*>(source);
auto t = reinterpret_cast<T*>(target);
@@ -1075,7 +1076,7 @@ inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, q
if (sourceChannels < 3) {
qCDebug(LOG_PSDPLUGIN) << "labToRgb: image is not a valid LAB!";
return;
return false;
}
for (qint32 w = 0; w < width; ++w) {
@@ -1110,6 +1111,7 @@ inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, q
*(pt + 3) = std::numeric_limits<T>::max();
}
}
return true;
}
bool readChannel(QByteArray &target, QDataStream &stream, quint32 compressedSize, quint16 compression)
@@ -1450,10 +1452,13 @@ bool PSDHandler::read(QImage *image)
// Conversion to RGB
if (header.color_mode == CM_CMYK || header.color_mode == CM_MULTICHANNEL) {
if (tmpCmyk.isNull()) {
if (header.depth == 8)
cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
else if (header.depth == 16)
cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
if (header.depth == 8) {
if (!cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha))
return false;
} else if (header.depth == 16) {
if (!cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha))
return false;
}
} else if (header.depth == 8) {
rawChannelsCopyToCMYK<quint8>(tmpCmyk.bits(), 4, psdScanline.data(), header.channel_count, header.width);
if (auto rgbPtr = iccConv.convertedScanLine(tmpCmyk, 0))
@@ -1469,10 +1474,13 @@ bool PSDHandler::read(QImage *image)
}
}
if (header.color_mode == CM_LABCOLOR) {
if (header.depth == 8)
labToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
else if (header.depth == 16)
labToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
if (header.depth == 8) {
if (!labToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha))
return false;
} else if (header.depth == 16) {
if (!labToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha))
return false;
}
}
if (header.color_mode == CM_RGB) {
if (header.depth == 8)

View File

@@ -319,7 +319,10 @@ bool SGIImagePrivate::readImage(QImage &img)
if (_rle) {
uint l;
_starttab = new quint32[_numrows];
_starttab = new (std::nothrow) quint32[_numrows];
if (_starttab == nullptr) {
return false;
}
for (l = 0; !_stream.atEnd() && l < _numrows; l++) {
_stream >> _starttab[l];
_starttab[l] -= 512 + _numrows * 2 * sizeof(quint32);
@@ -331,7 +334,10 @@ bool SGIImagePrivate::readImage(QImage &img)
_starttab[l] = 0;
}
_lengthtab = new quint32[_numrows];
_lengthtab = new (std::nothrow) quint32[_numrows];
if (_lengthtab == nullptr) {
return false;
}
for (l = 0; !_stream.atEnd() && l < _numrows; l++) {
_stream >> _lengthtab[l];
if (_stream.status() != QDataStream::Ok) {
@@ -794,7 +800,10 @@ bool SGIImagePrivate::writeImage(const QImage &image)
_pixmax = 0;
_colormap = NORMAL;
_numrows = _ysize * _zsize;
_starttab = new quint32[_numrows];
_starttab = new (std::nothrow) quint32[_numrows];
if (_starttab == nullptr) {
return false;
}
_rlemap.setBaseOffset(512 + _numrows * 2 * sizeof(quint32));
if (!scanData(image, tfmt, tcs)) {

View File

@@ -731,7 +731,7 @@ static bool LoadTGA(QIODevice *dev, const TgaHeader &tga, QImage &img)
if (div == 0)
hasAlpha = false;
for (int x = x_start; x != x_end; x += x_step) {
const int alpha = hasAlpha ? int((src[3]) << (8 - numAlphaBits)) * 255 / div : 255;
const int alpha = hasAlpha ? int(quint8(src[3]) << (8 - numAlphaBits)) * 255 / div : 255;
scanline[x] = qRgba(src[2], src[1], src[0], alpha);
src += 4;
}