Source: qtbase-opensource-src
Version: 5.15.19+dfsg-7
Severity: important
Tags: security upstream patch

Hi,

Two CVEs fixed upstream only in Qt 6 code are still open against Qt 5 in
all suites, and I found no open bug for them. Backports are attached, in
case they save you some work.

CVE-2026-107472 (TSCII encoder reads past the input)
Backport of qt5compat 7b8ed3e3. Upstream's own test cases, compiled with
ASan/UBSan: unpatched fails last-char and second-to-last-char, patched
passes 4 of 4. The same patch applies to 5.15.19+dfsg-7,
5.15.15+dfsg-6+deb13u1 and 5.15.8+dfsg-11+deb12u3.

CVE-2026-105946 (QAuthenticator NTLM and Digest parsing)
Backport of qtbase eb91758e, 3843303b and 8560fb92. Qt 5 has no
qAddOverflow() or QByteArray::assign(), so it uses quint64 arithmetic and
the QByteArray(ptr, size) constructor. There are two versions: one for sid
and trixie, and one for bookworm.

Please weigh the evidence for this one accordingly: upstream's
malformed-input cases pass both before and after the patch, and neither
side trips ASan/UBSan, so they do not prove the fix. The one visible change
is that a timestamp attribute with the wrong length is now rejected and
local time is used instead (phase 3 goes from 172 to 176 bytes). The 32-bit
wrap in qNtlmDecodePhase2 was only read in the source, and I did not assess
its memory impact.

The patches apply after the full Debian series on each version, and the
patched sources compile against each version's headers for trixie and
bookworm, and against 5.15.19 headers for sid. Not tested: full package
builds and Qt's own test binaries. The test harnesses linked the host's
libQt5 5.15.19, so nothing ran against the trixie or bookworm libraries.

Two related entries, for completeness:

CVE-2026-107590 (iconv codec) does not appear to affect Debian's Qt 5. All
three versions are configured with -icu, codecs.pri then builds the ICU
codec and not qiconvcodec.cpp, and the libQt5Core 5.15.19 binary I
inspected has no QIconvCodec and uses ucnv_open. I did not inspect the
trixie or bookworm binaries.

CVE-2026-107479 (integer truncation) looks low value on Qt 5. QString and
QByteArray sizes are int there, so only the QStringView and const char *
overloads can truncate, and they need a single input over 2 GiB. I did not
prepare a patch or test this.

Regards,
Sumit Verma
Description: QTextCodec: fix out-of-bounds read in the TSCII encoder
 QTsciiCodec::convertFromUnicode() looks ahead at uc[i + 1] and uc[i + 2]
 without checking them against the input length.
Origin: backport, 
https://github.com/qt/qt5compat/commit/7b8ed3e3f8dad3c2fe76ebe68b121010c3ca19b6
Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-107472
Forwarded: not-needed
Last-Update: 2026-11-08
---
--- a/src/corelib/codecs/qtsciicodec.cpp
+++ b/src/corelib/codecs/qtsciicodec.cpp
@@ -87,12 +87,14 @@
         if (ch.row() == 0x00 && ch.cell() < 0x80) {
             // ASCII
             j = ch.cell();
-        } else if ((j = qt_UnicodeToTSCII(uc[i].unicode(),
+        } else if ((i < len - 2) &&
+                   (j = qt_UnicodeToTSCII(uc[i].unicode(),
                                           uc[i + 1].unicode(),
                                           uc[i + 2].unicode()))) {
             // We have to check the combined chars first!
             i += 2;
-        } else if ((j = qt_UnicodeToTSCII(uc[i].unicode(),
+        } else if ((i < len - 1) &&
+                   (j = qt_UnicodeToTSCII(uc[i].unicode(),
                                           uc[i + 1].unicode(), 0))) {
             i++;
         } else if ((j = qt_UnicodeToTSCII(uc[i].unicode(), 0, 0))) {
Description: QAuthenticator: harden NTLM and Digest challenge parsing
 Backport of three upstream changes:
  - qExtractServerTime(): walk the AV_PAIR list with explicit bounds and
    accept only an 8-byte timestamp, so a truncated attribute no longer
    leaves uninitialised bytes in the response;
  - qNtlmDecodePhase2(): do the offset + length bound check in 64 bits;
  - parseDigestAuthenticationChallenge(): do not advance past the end.
 Qt 5 has no qAddOverflow() or QByteArray::assign(), so quint64 arithmetic
 and the QByteArray(ptr, size) constructor are used instead.
Origin: backport, 
https://github.com/qt/qtbase/commit/eb91758e006472b0f40304219aae09e69564c0fa
Origin: backport, 
https://github.com/qt/qtbase/commit/3843303b9229f269963e44eb949a9c7dba18db39
Origin: backport, 
https://github.com/qt/qtbase/commit/8560fb92a600a56e77add56eaf9ae3c3e6d9f9fd
Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-105946
Forwarded: not-needed
Last-Update: 2026-11-08
---
--- a/src/network/kernel/qauthenticator.cpp
+++ b/src/network/kernel/qauthenticator.cpp
@@ -658,6 +658,8 @@
         const char *start = d;
         while (d < end && *d != '=')
             ++d;
+        if (d >= end)
+            break;
         QByteArray key = QByteArray(start, d - start);
         ++d;
         if (d >= end)
@@ -689,7 +691,8 @@
         }
         while (d < end && *d != ',')
             ++d;
-        ++d;
+        if (d < end)
+            ++d;
         options[key] = value;
     }
 
@@ -975,6 +978,8 @@
 const int blockSize = 64; //As per RFC2104 Block-size is 512 bits
 const quint8 respversion = 1;
 const quint8 hirespversion = 1;
+// FILETIME: two 32-bit values = 8 bytes (MS-DTYP section 2.3.3)
+static constexpr quint16 NtlmFileTimeSize = 8;
 
 /* usage:
    // fill up ctx with what we know.
@@ -1332,24 +1337,29 @@
 static QByteArray qExtractServerTime(const QByteArray& targetInfoBuff)
 {
     QByteArray timeArray;
-    QDataStream ds(targetInfoBuff);
-    ds.setByteOrder(QDataStream::LittleEndian);
-
+    const char *ptr = targetInfoBuff.constBegin();
+    const char *end = targetInfoBuff.constEnd();
     quint16 avId;
     quint16 avLen;
 
-    ds >> avId;
-    ds >> avLen;
-    while(avId != 0) {
-        if(avId == AVTIMESTAMP) {
-            timeArray.resize(avLen);
-            //avLen size of QByteArray is allocated
-            ds.readRawData(timeArray.data(), avLen);
+    while (end - ptr >= 4) {
+        avId = qFromLittleEndian<quint16>(ptr + 0);
+        avLen = qFromLittleEndian<quint16>(ptr + 2);
+        ptr += 4;
+
+        if (avId == AVTIMESTAMP) {
+            if (avLen != NtlmFileTimeSize)
+                break;
+            if (end - ptr < NtlmFileTimeSize)
+                break;
+
+            timeArray = QByteArray(ptr, NtlmFileTimeSize);
             break;
         }
-        ds.skipRawData(avLen);
-        ds >> avId;
-        ds >> avLen;
+
+        if (avLen > end - ptr)
+            break;
+        ptr += avLen;
     }
     return timeArray;
 }
@@ -1466,14 +1476,14 @@
     ds >> ch.targetInfo;
 
     if (ch.targetName.len > 0) {
-        if (ch.targetName.len + ch.targetName.offset > (unsigned)data.size())
+        if (quint64(ch.targetName.offset) + ch.targetName.len > 
quint64(data.size()))
             return false;
 
         ch.targetNameStr = qStringFromUcs2Le(data.mid(ch.targetName.offset, 
ch.targetName.len));
     }
 
     if (ch.targetInfo.len > 0) {
-        if (ch.targetInfo.len + ch.targetInfo.offset > (unsigned)data.size())
+        if (quint64(ch.targetInfo.offset) + ch.targetInfo.len > 
quint64(data.size()))
             return false;
 
         ch.targetInfoBuff = data.mid(ch.targetInfo.offset, ch.targetInfo.len);
Description: QAuthenticator: harden NTLM and Digest challenge parsing
 Backport of three upstream changes:
  - qExtractServerTime(): walk the AV_PAIR list with explicit bounds and
    accept only an 8-byte timestamp, so a truncated attribute no longer
    leaves uninitialised bytes in the response;
  - qNtlmDecodePhase2(): do the offset + length bound check in 64 bits;
  - parseDigestAuthenticationChallenge(): do not advance past the end.
 Qt 5 has no qAddOverflow() or QByteArray::assign(), so quint64 arithmetic
 and the QByteArray(ptr, size) constructor are used instead.
Origin: backport, 
https://github.com/qt/qtbase/commit/eb91758e006472b0f40304219aae09e69564c0fa
Origin: backport, 
https://github.com/qt/qtbase/commit/3843303b9229f269963e44eb949a9c7dba18db39
Origin: backport, 
https://github.com/qt/qtbase/commit/8560fb92a600a56e77add56eaf9ae3c3e6d9f9fd
Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-105946
Forwarded: not-needed
Last-Update: 2026-11-08
---
--- a/src/network/kernel/qauthenticator.cpp
+++ b/src/network/kernel/qauthenticator.cpp
@@ -655,6 +655,8 @@
         const char *start = d;
         while (d < end && *d != '=')
             ++d;
+        if (d >= end)
+            break;
         QByteArray key = QByteArray(start, d - start);
         ++d;
         if (d >= end)
@@ -686,7 +688,8 @@
         }
         while (d < end && *d != ',')
             ++d;
-        ++d;
+        if (d < end)
+            ++d;
         options[key] = value;
     }
 
@@ -972,6 +975,8 @@
 const int blockSize = 64; //As per RFC2104 Block-size is 512 bits
 const quint8 respversion = 1;
 const quint8 hirespversion = 1;
+// FILETIME: two 32-bit values = 8 bytes (MS-DTYP section 2.3.3)
+static constexpr quint16 NtlmFileTimeSize = 8;
 
 /* usage:
    // fill up ctx with what we know.
@@ -1329,24 +1334,29 @@
 static QByteArray qExtractServerTime(const QByteArray& targetInfoBuff)
 {
     QByteArray timeArray;
-    QDataStream ds(targetInfoBuff);
-    ds.setByteOrder(QDataStream::LittleEndian);
-
+    const char *ptr = targetInfoBuff.constBegin();
+    const char *end = targetInfoBuff.constEnd();
     quint16 avId;
     quint16 avLen;
 
-    ds >> avId;
-    ds >> avLen;
-    while(avId != 0) {
-        if(avId == AVTIMESTAMP) {
-            timeArray.resize(avLen);
-            //avLen size of QByteArray is allocated
-            ds.readRawData(timeArray.data(), avLen);
+    while (end - ptr >= 4) {
+        avId = qFromLittleEndian<quint16>(ptr + 0);
+        avLen = qFromLittleEndian<quint16>(ptr + 2);
+        ptr += 4;
+
+        if (avId == AVTIMESTAMP) {
+            if (avLen != NtlmFileTimeSize)
+                break;
+            if (end - ptr < NtlmFileTimeSize)
+                break;
+
+            timeArray = QByteArray(ptr, NtlmFileTimeSize);
             break;
         }
-        ds.skipRawData(avLen);
-        ds >> avId;
-        ds >> avLen;
+
+        if (avLen > end - ptr)
+            break;
+        ptr += avLen;
     }
     return timeArray;
 }
@@ -1463,14 +1473,14 @@
     ds >> ch.targetInfo;
 
     if (ch.targetName.len > 0) {
-        if (ch.targetName.len + ch.targetName.offset > (unsigned)data.size())
+        if (quint64(ch.targetName.offset) + ch.targetName.len > 
quint64(data.size()))
             return false;
 
         ch.targetNameStr = qStringFromUcs2Le(data.mid(ch.targetName.offset, 
ch.targetName.len));
     }
 
     if (ch.targetInfo.len > 0) {
-        if (ch.targetInfo.len + ch.targetInfo.offset > (unsigned)data.size())
+        if (quint64(ch.targetInfo.offset) + ch.targetInfo.len > 
quint64(data.size()))
             return false;
 
         ch.targetInfoBuff = data.mid(ch.targetInfo.offset, ch.targetInfo.len);

Reply via email to