Bug#1107339: unblock: kf6-karchive/6.13.0-2
Package: release.debian.org
Severity: normal
X-Debbugs-Cc: kf6-karchive@packages.debian.org, Debian Qt/KDE Maintainers <debian-qt-kde@lists.debian.org>
Control: affects -1 + src:kf6-karchive
User: release.debian.org@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 <coucouf@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 <aacid@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-momin@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.bruens@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 <aacid@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 <aacid@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 <aacid@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-momin@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.bruens@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
+
Reply to: