Package: release.debian.org Severity: normal X-Debbugs-Cc: kf6-karch...@packages.debian.org, Debian Qt/KDE Maintainers <debian-qt-...@lists.debian.org> Control: affects -1 + src:kf6-karchive User: release.debian....@packages.debian.org Usertags: unblock
Dear Release Team, please unblock package kf6-karchive. [ Reason ] I contains the following changes: * Backport upstream commits: - Fix 2 issues reading malformed zip archives. - Fix reading zip archives with prepended arbitrary data. (kde#329579) - Fix out-of-bounds read crash on malformed 7-zip files. - Fix buffer overflow, crash and infinite loop on malformed zip files. [ Tests ] I don’t have the faulty zip file test cases at hand but I did test with several zip files that there were no regression. Upstream also has a test suite with several zip file that passes. [ Risks ] Patches are small, further upstream fixes could easily be backported. [ Checklist ] [x] all changes are documented in the d/changelog [x] I reviewed all changes and I approve them [x] attach debdiff against the package in testing Thanks! unblock kf6-karchive/6.13.0-2
diff -Nru kf6-karchive-6.13.0/debian/changelog kf6-karchive-6.13.0/debian/changelog --- kf6-karchive-6.13.0/debian/changelog 2025-04-12 19:33:24.000000000 +0200 +++ kf6-karchive-6.13.0/debian/changelog 2025-05-17 00:45:55.000000000 +0200 @@ -1,3 +1,14 @@ +kf6-karchive (6.13.0-2) unstable; urgency=medium + + [ Aurélien COUDERC ] + * Backport upstream commits: + - Fix 2 issues reading malformed zip archives. + - Fix reading zip archives with prepended arbitrary data. (kde#329579) + - Fix out-of-bounds read crash on malformed 7-zip files. + - Fix buffer overflow, crash and infinite loop on malformed zip files. + + -- Aurélien COUDERC <couc...@debian.org> Sat, 17 May 2025 00:45:55 +0200 + kf6-karchive (6.13.0-1) unstable; urgency=medium [ Patrick Franz ] diff -Nru kf6-karchive-6.13.0/debian/patches/series kf6-karchive-6.13.0/debian/patches/series --- kf6-karchive-6.13.0/debian/patches/series 1970-01-01 01:00:00.000000000 +0100 +++ kf6-karchive-6.13.0/debian/patches/series 2025-05-17 00:45:55.000000000 +0200 @@ -0,0 +1,7 @@ +upstream_28b763d7_Avoid-searching-uninitialized-bytes-in-header-indexOf-PK-.patch +upstream_c33e581b_Skip-two-bytes-on-invalid-header-signature-in-seekToNextHeaderToken-.patch +upstream_e20f8694_kzip-Fix-position-calculation-for-archives-with-prepended-arbitrary-data.patch +upstream_3ac1505b_Fix-heap-buffer-overflow.patch +upstream_736eae2b_kzip-Fix-crash-on-malformed-files.patch +upstream_09ddacdd_k7zip-Fix-crash-on-malformed-file.patch +upstream_7ef21db4_k7zip-Fix-infinite-loop-on-malformed-file.patch diff -Nru kf6-karchive-6.13.0/debian/patches/upstream_09ddacdd_k7zip-Fix-crash-on-malformed-file.patch kf6-karchive-6.13.0/debian/patches/upstream_09ddacdd_k7zip-Fix-crash-on-malformed-file.patch --- kf6-karchive-6.13.0/debian/patches/upstream_09ddacdd_k7zip-Fix-crash-on-malformed-file.patch 1970-01-01 01:00:00.000000000 +0100 +++ kf6-karchive-6.13.0/debian/patches/upstream_09ddacdd_k7zip-Fix-crash-on-malformed-file.patch 2025-05-17 00:45:55.000000000 +0200 @@ -0,0 +1,27 @@ +From 09ddacdded2ae78bd63d95ba5e07cc84823d9474 Mon Sep 17 00:00:00 2001 +From: Albert Astals Cid <aa...@kde.org> +Date: Tue, 27 May 2025 00:43:47 +0200 +Subject: [PATCH] k7zip: Fix crash on malformed file + +https://issues.oss-fuzz.com/issues/410420649 +--- + src/k7zip.cpp | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/k7zip.cpp b/src/k7zip.cpp +index 90e719ca..b689eb92 100644 +--- a/src/k7zip.cpp ++++ b/src/k7zip.cpp +@@ -978,6 +978,9 @@ bool K7Zip::K7ZipPrivate::readUnpackInfo() + + for (int i = 0; i < numFolders; ++i) { + Folder *folder = folders.at(i); ++ if (!folder) { ++ continue; ++ } + int numOutStreams = folder->getNumOutStreams(); + for (int j = 0; j < numOutStreams; ++j) { + folder->unpackSizes.append(readNumber()); +-- +GitLab + diff -Nru kf6-karchive-6.13.0/debian/patches/upstream_28b763d7_Avoid-searching-uninitialized-bytes-in-header-indexOf-PK-.patch kf6-karchive-6.13.0/debian/patches/upstream_28b763d7_Avoid-searching-uninitialized-bytes-in-header-indexOf-PK-.patch --- kf6-karchive-6.13.0/debian/patches/upstream_28b763d7_Avoid-searching-uninitialized-bytes-in-header-indexOf-PK-.patch 1970-01-01 01:00:00.000000000 +0100 +++ kf6-karchive-6.13.0/debian/patches/upstream_28b763d7_Avoid-searching-uninitialized-bytes-in-header-indexOf-PK-.patch 2025-05-16 23:32:13.000000000 +0200 @@ -0,0 +1,34 @@ +From 28b763d70cfae79bd51a08e8b85113ca6fba11dc Mon Sep 17 00:00:00 2001 +From: Azhar Momin <azhar-mo...@outlook.com> +Date: Wed, 9 Apr 2025 12:16:16 +0530 +Subject: [PATCH] Avoid searching uninitialized bytes in `header.indexOf("PK")` +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +`QByteArray::resize()` does not initialize newly allocated memory and +`QIODevice::read()` may do a short read. Using +`QByteArrayView(header.data(), n)` ensures that `indexOf("PK")` scans +only the valid portion of the buffer. + +Co-authored-by: Stefan Brüns <stefan.bru...@rwth-aachen.de> +--- + src/kzip.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kzip.cpp b/src/kzip.cpp +index d823a08d..0f048410 100644 +--- a/src/kzip.cpp ++++ b/src/kzip.cpp +@@ -319,7 +319,7 @@ static bool seekAnyHeader(QIODevice *dev, QByteArray &header, qsizetype minSize) + int n = dev->peek(header.data(), header.size()); + + while (n >= minSize) { +- if (auto i = header.indexOf("PK"); i >= 0) { ++ if (auto i = QByteArrayView(header.data(), n).indexOf("PK"); i >= 0) { + dev->seek(dev->pos() + i); + if ((i + minSize) < n) { + header.remove(0, i); +-- +GitLab + diff -Nru kf6-karchive-6.13.0/debian/patches/upstream_3ac1505b_Fix-heap-buffer-overflow.patch kf6-karchive-6.13.0/debian/patches/upstream_3ac1505b_Fix-heap-buffer-overflow.patch --- kf6-karchive-6.13.0/debian/patches/upstream_3ac1505b_Fix-heap-buffer-overflow.patch 1970-01-01 01:00:00.000000000 +0100 +++ kf6-karchive-6.13.0/debian/patches/upstream_3ac1505b_Fix-heap-buffer-overflow.patch 2025-05-16 23:35:32.000000000 +0200 @@ -0,0 +1,26 @@ +From 3ac1505b71e4d8a895dff640525a58b9749c7fe1 Mon Sep 17 00:00:00 2001 +From: Albert Astals Cid <aa...@kde.org> +Date: Tue, 15 Apr 2025 00:57:19 +0200 +Subject: [PATCH] Fix heap buffer overflow + +--- + src/k7zip.cpp | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/src/k7zip.cpp b/src/k7zip.cpp +index 5ae9c7ab..e04814c1 100644 +--- a/src/k7zip.cpp ++++ b/src/k7zip.cpp +@@ -761,8 +761,7 @@ void K7Zip::K7ZipPrivate::readHashDigests(int numItems, QList<bool> &digestsDefi + for (int i = 0; i < numItems; i++) { + quint32 crc = 0; + if (digestsDefined[i]) { +- crc = GetUi32(buffer + pos); +- pos += 4; ++ crc = readUInt32(); + } + digests.append(crc); + } +-- +GitLab + diff -Nru kf6-karchive-6.13.0/debian/patches/upstream_736eae2b_kzip-Fix-crash-on-malformed-files.patch kf6-karchive-6.13.0/debian/patches/upstream_736eae2b_kzip-Fix-crash-on-malformed-files.patch --- kf6-karchive-6.13.0/debian/patches/upstream_736eae2b_kzip-Fix-crash-on-malformed-files.patch 1970-01-01 01:00:00.000000000 +0100 +++ kf6-karchive-6.13.0/debian/patches/upstream_736eae2b_kzip-Fix-crash-on-malformed-files.patch 2025-05-17 00:45:55.000000000 +0200 @@ -0,0 +1,41 @@ +From 736eae2ba63645b49b8faa37d94b4329acf0d0af Mon Sep 17 00:00:00 2001 +From: Albert Astals Cid <aa...@kde.org> +Date: Tue, 6 May 2025 15:53:59 +0000 +Subject: [PATCH] kzip: Fix crash on malformed files + +--- + src/kzip.cpp | 11 +++++++---- + 1 file changed, 7 insertions(+), 4 deletions(-) + +diff --git a/src/kzip.cpp b/src/kzip.cpp +index ce240ea6..32460b5e 100644 +--- a/src/kzip.cpp ++++ b/src/kzip.cpp +@@ -669,7 +669,13 @@ bool KZip::openArchive(QIODevice::OpenMode mode) + setErrorString(tr("Invalid ZIP file, file path name length is zero")); + return false; + } +- QByteArray varData = dev->read(namelen + extralen); ++ const int varDataDesiredLength = namelen + extralen; ++ const QByteArray varData = dev->read(varDataDesiredLength); ++ if (varData.length() < varDataDesiredLength) { ++ setErrorString(tr("Invalid ZIP file, unable to read %1 + %2 bytes for filename and extra data at offset %3") ++ .arg(namelen, extralen, dev->pos() - varData.size())); ++ return false; ++ } + + ParseFileInfo extrafi; + if (extralen) { +@@ -677,9 +683,6 @@ bool KZip::openArchive(QIODevice::OpenMode mode) + } + + QByteArray bufferName(varData.constData(), namelen); +- if (bufferName.size() < namelen) { +- // qCWarning(KArchiveLog) << "Invalid ZIP file. Name not completely read"; +- } + + ParseFileInfo pfi = pfi_map.value(bufferName, ParseFileInfo()); + +-- +GitLab + diff -Nru kf6-karchive-6.13.0/debian/patches/upstream_7ef21db4_k7zip-Fix-infinite-loop-on-malformed-file.patch kf6-karchive-6.13.0/debian/patches/upstream_7ef21db4_k7zip-Fix-infinite-loop-on-malformed-file.patch --- kf6-karchive-6.13.0/debian/patches/upstream_7ef21db4_k7zip-Fix-infinite-loop-on-malformed-file.patch 1970-01-01 01:00:00.000000000 +0100 +++ kf6-karchive-6.13.0/debian/patches/upstream_7ef21db4_k7zip-Fix-infinite-loop-on-malformed-file.patch 2025-05-17 00:45:55.000000000 +0200 @@ -0,0 +1,28 @@ +From 7ef21db48ecf58edb54479971701be48208dc34f Mon Sep 17 00:00:00 2001 +From: Albert Astals Cid <aa...@kde.org> +Date: Tue, 27 May 2025 00:44:25 +0200 +Subject: [PATCH] k7zip: Fix infinite loop on malformed file + +Same file from https://issues.oss-fuzz.com/issues/410420649 +--- + src/k7zip.cpp | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/src/k7zip.cpp b/src/k7zip.cpp +index b689eb9..fe78bc2 100644 +--- a/src/k7zip.cpp ++++ b/src/k7zip.cpp +@@ -988,6 +988,10 @@ bool K7Zip::K7ZipPrivate::readUnpackInfo() + } + + for (;;) { ++ if (pos >= end) { ++ return false; ++ } ++ + int type = readByte(); + if (type == kEnd) { + break; +-- +GitLab + diff -Nru kf6-karchive-6.13.0/debian/patches/upstream_c33e581b_Skip-two-bytes-on-invalid-header-signature-in-seekToNextHeaderToken-.patch kf6-karchive-6.13.0/debian/patches/upstream_c33e581b_Skip-two-bytes-on-invalid-header-signature-in-seekToNextHeaderToken-.patch --- kf6-karchive-6.13.0/debian/patches/upstream_c33e581b_Skip-two-bytes-on-invalid-header-signature-in-seekToNextHeaderToken-.patch 1970-01-01 01:00:00.000000000 +0100 +++ kf6-karchive-6.13.0/debian/patches/upstream_c33e581b_Skip-two-bytes-on-invalid-header-signature-in-seekToNextHeaderToken-.patch 2025-05-16 23:34:57.000000000 +0200 @@ -0,0 +1,32 @@ +From c33e581b5ca6e049f4443074394f47ed54ba680d Mon Sep 17 00:00:00 2001 +From: Azhar Momin <azhar-mo...@outlook.com> +Date: Wed, 9 Apr 2025 22:08:08 +0530 +Subject: [PATCH] Skip two bytes on invalid header signature in + `seekToNextHeaderToken()` + +`seekAnyHeader()` advances the stream to the start of next potential +header but may not move if the stream is already positioned at a header +candidate. When encountering a token that does not match an expected +signature (i.e, PK\x03\x04 or PK\x01\x02) in `seekToNextHeaderToken()`, +manually advance the stream by two bytes to avoid re-detecting the same +invalid token in the next iteration. +--- + src/kzip.cpp | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/kzip.cpp b/src/kzip.cpp +index 0f048410..439d0bdf 100644 +--- a/src/kzip.cpp ++++ b/src/kzip.cpp +@@ -387,6 +387,8 @@ static bool seekToNextHeaderToken(QIODevice *dev) + // PK12 for the central header in case there is no data descriptor + if (header.startsWith("PK\x03\x04") || header.startsWith("PK\x01\x02")) { + return true; ++ } else { ++ dev->seek(dev->pos() + 2); // Skip found 'PK' + } + } + return false; +-- +GitLab + diff -Nru kf6-karchive-6.13.0/debian/patches/upstream_e20f8694_kzip-Fix-position-calculation-for-archives-with-prepended-arbitrary-data.patch kf6-karchive-6.13.0/debian/patches/upstream_e20f8694_kzip-Fix-position-calculation-for-archives-with-prepended-arbitrary-data.patch --- kf6-karchive-6.13.0/debian/patches/upstream_e20f8694_kzip-Fix-position-calculation-for-archives-with-prepended-arbitrary-data.patch 1970-01-01 01:00:00.000000000 +0100 +++ kf6-karchive-6.13.0/debian/patches/upstream_e20f8694_kzip-Fix-position-calculation-for-archives-with-prepended-arbitrary-data.patch 2025-05-16 23:35:03.000000000 +0200 @@ -0,0 +1,184 @@ +From e20f869437fc4dd979e36e8f18403a03920b148e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Stefan=20Br=C3=BCns?= <stefan.bru...@rwth-aachen.de> +Date: Tue, 1 Apr 2025 22:41:11 +0200 +Subject: [PATCH] kzip: Fix position calculation for archives with prepended + arbitrary data + +There are two variants of non-ZIP data before the first Local File Header. +Either with offset in the Central Directory reflecting the actual +position, i.e. the first entry offset in the CD will point just after the +additional non-ZIP header instead of 0. This is the case for e.g. +self-extracting archives from WinZIP. + +The other is just a concatenation of non-ZIP data and a regular ZIP file, +i.e. the first entry in the CD will contain an offset value of 0. + +unzip (Info-ZIP) and bsdunzip (libarchive) accept both variants (unzip -v +provides a warning - "warning [xxx.zip]: 61 extra bytes at beginning or +within zipfile"), libzip requires an explicit offset (-o 61) for the +latter. + +Verify if the (adjusted) local file header offset in the Central +Directory match the seen positions, bail out otherwise. Provide a +warning similar to unzip in case there is extra data. + +Limit the initial header search to the first 4 MByte (previously +unlimited). Also provide a more specific error message. + +BUG: 329579 +--- + autotests/karchivetest.cpp | 26 ++++++++++++++++++++++++++ + autotests/karchivetest.h | 1 + + src/kzip.cpp | 31 +++++++++++++++++++++++++++---- + 3 files changed, 54 insertions(+), 4 deletions(-) + +diff --git a/autotests/karchivetest.cpp b/autotests/karchivetest.cpp +index 86b84b96..1fae4ba8 100644 +--- a/autotests/karchivetest.cpp ++++ b/autotests/karchivetest.cpp +@@ -1276,6 +1276,31 @@ void KArchiveTest::testZipWithinZip() + QCOMPARE(listing[1], QString::fromUtf8("mode=100644 path=test.epub type=file size=525154")); + } + ++void KArchiveTest::testZipPrependedData() ++{ ++ const QString fileName = QFINDTESTDATA("data/zip64_datadescriptor.zip"); ++ QVERIFY(!fileName.isEmpty()); ++ ++ QFile origFile(fileName); ++ QVERIFY2(origFile.open(QIODevice::ReadOnly), qPrintable(origFile.errorString())); ++ auto zipData = origFile.readAll(); ++ QVERIFY(zipData.startsWith("PK")); ++ ++ zipData.insert(0, QByteArrayLiteral("PrependedPK\x01\x02Garbage")); ++ QBuffer zipBuffer(&zipData); ++ ++ KZip zip(&zipBuffer); ++ QVERIFY2(zip.open(QIODevice::ReadOnly), qPrintable(zip.errorString())); ++ ++ QCOMPARE(zip.directory()->entries().size(), 1); ++ QCOMPARE(zip.directory()->entries(), QList{QStringLiteral("-")}); ++ ++ auto entry = zip.directory()->file(QStringLiteral("-")); ++ QVERIFY(entry); ++ QCOMPARE(entry->size(), 4); ++ QCOMPARE(entry->data(), "abcd"); ++} ++ + void KArchiveTest::testZipUnusualButValid() + { + const QString fileName = QFINDTESTDATA("data/unusual_but_valid_364071.zip"); +@@ -1429,6 +1454,7 @@ void KArchiveTest::testZip64DataDescriptor() + auto entry = zip.directory()->file(QStringLiteral("-")); + QVERIFY(entry); + QCOMPARE(entry->size(), 4); ++ QCOMPARE(entry->data(), "abcd"); + + QVERIFY(zip.close()); + } +diff --git a/autotests/karchivetest.h b/autotests/karchivetest.h +index f898b9ec..b1a53c18 100644 +--- a/autotests/karchivetest.h ++++ b/autotests/karchivetest.h +@@ -84,6 +84,7 @@ private Q_SLOTS: + void testZipUnusualButValid(); + void testZipDuplicateNames(); + void testZipWithinZip(); ++ void testZipPrependedData(); + void testZip64(); + void testZipReopenWithoutDoubleDeletion(); + void testZip64NestedStored(); +diff --git a/src/kzip.cpp b/src/kzip.cpp +index 439d0bdf..ce240ea6 100644 +--- a/src/kzip.cpp ++++ b/src/kzip.cpp +@@ -104,9 +104,11 @@ struct ParseFileInfo { + bool newinfounix_seen; // true if Info-ZIP Unix New extra field has + // been parsed + +- // file sizes and header position from a ZIP64 extra field ++ // file sizes from a ZIP64 extra field + quint64 uncompressedSize = 0; + quint64 compressedSize = 0; ++ // position of the Local File Header itself, or from the ++ // ZIP64 extra field in the Central Directory + quint64 localheaderoffset = 0; + + ParseFileInfo() +@@ -422,6 +424,8 @@ public: + // writeonly mode, or it points to the beginning of the central directory. + // each call to writefile updates this value. + quint64 m_offset; ++ // Position of the first Local File Header ++ quint64 m_startPos = 0; + }; + + KZip::KZip(const QString &fileName) +@@ -467,6 +471,8 @@ bool KZip::openArchive(QIODevice::OpenMode mode) + // We set a bool for knowing if we are allowed to skip the start of the file + bool startOfFile = true; + ++ qint64 expectedStartPos = 0; ++ + for (;;) { // repeat until 'end of entries' signature is reached + // qCDebug(KArchiveLog) << "loop starts"; + // qCDebug(KArchiveLog) << "dev->pos() now : " << dev->pos(); +@@ -492,6 +498,9 @@ bool KZip::openArchive(QIODevice::OpenMode mode) + // qCDebug(KArchiveLog) << "PK34 found local file header at offset" << dev->pos(); + startOfFile = false; + ++ ParseFileInfo pfi; ++ pfi.localheaderoffset = dev->pos() - 4; ++ + // read static header stuff + n = dev->read(buffer, 26); + if (n < 26) { +@@ -530,7 +539,6 @@ bool KZip::openArchive(QIODevice::OpenMode mode) + return false; + } + +- ParseFileInfo pfi; + pfi.mtime = mtime; + pfi.dataoffset = 30 + extralen + namelen; + +@@ -705,7 +713,17 @@ bool KZip::openArchive(QIODevice::OpenMode mode) + // qCDebug(KArchiveLog) << "localheader dataoffset: " << pfi.dataoffset; + + // offset, where the real data for uncompression starts +- qint64 dataoffset = localheaderoffset + pfi.dataoffset; // comment only in central header ++ qint64 dataoffset = localheaderoffset + pfi.dataoffset; ++ if (pfi.localheaderoffset != expectedStartPos + localheaderoffset) { ++ if (pfi.localheaderoffset == d->m_startPos + localheaderoffset) { ++ qCDebug(KArchiveLog) << "warning:" << d->m_startPos << "extra bytes at beginning of zipfile"; ++ expectedStartPos = d->m_startPos; ++ } else { ++ setErrorString(tr("Invalid ZIP file, inconsistent Local Header Offset in Central Directory at %1").arg(offset)); ++ return false; ++ } ++ dataoffset = localheaderoffset + pfi.dataoffset + d->m_startPos; ++ } + + // qCDebug(KArchiveLog) << "csize: " << csize; + +@@ -808,12 +826,17 @@ bool KZip::openArchive(QIODevice::OpenMode mode) + */ + if (header.startsWith("PK\x03\x04")) { + foundSignature = true; ++ d->m_startPos = dev->pos(); ++ break; ++ } ++ if (dev->pos() > 4 * 1024 * 1024) { + break; + } ++ dev->seek(dev->pos() + 2); // Skip found 'PK' + } + + if (!foundSignature) { +- setErrorString(tr("Invalid ZIP file. Unexpected end of file.")); ++ setErrorString(tr("Not a ZIP file, no Local File Header found.")); + return false; + } + } else { +-- +GitLab +