From 80bb89c182f4f5ee38ac380942fb2713c97d101f Mon Sep 17 00:00:00 2001
From: Rym Bouabid <rym.bouabid@qt.io>
Date: Tue, 09 Jun 2026 14:21:42 +0200
Subject: [PATCH] QNdefNfcTextRecord: fix out-of-bounds read in locale() and text()

Return empty qbytearray for malformed or corrupted payloads, as there
is no way to signal that the returned data is incomplete or invalid.

Add autotest. All four cases trip ASAN heap-buffer-overflow without the
fix, and pass cleanly with it.

Fixes: QTBUG-147372
Pick-to: 6.5 5.15
Change-Id: Iac519ff1ee1684bf8eac8a2f35396ba4eefaf6aa
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
(cherry picked from commit dfa2daa267b6f061820ae6f128568d2558e13a0c)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 981de204aa28cd3c11bec0314c343bc55b9a976d)
(cherry picked from commit 30e7a3851fe5321547decce144e9d5537dc9c90f)
---

diff --git a/src/nfc/qndefnfctextrecord.cpp b/src/nfc/qndefnfctextrecord.cpp
index a45c2ff..e1a1d22 100644
--- a/src/nfc/qndefnfctextrecord.cpp
+++ b/src/nfc/qndefnfctextrecord.cpp
@@ -54,6 +54,9 @@
 
     quint8 codeLength = status & 0x3f;
 
+    if (p.size() < 1 + codeLength)
+        return QString();
+
     return QString::fromLatin1(p.constData() + 1, codeLength);
 }
 
@@ -90,6 +93,9 @@
     bool utf16 = status & 0x80;
     quint8 codeLength = status & 0x3f;
 
+    if (p.size() < 1 + codeLength)
+        return QString();
+
     auto toUnicode = QStringDecoder(
         utf16 ? QStringDecoder::Encoding::Utf16BE : QStringDecoder::Encoding::Utf8,
         QStringDecoder::Flag::Stateless);
diff --git a/tests/auto/qndefrecord/tst_qndefrecord.cpp b/tests/auto/qndefrecord/tst_qndefrecord.cpp
index d667129..c6ec6d6 100644
--- a/tests/auto/qndefrecord/tst_qndefrecord.cpp
+++ b/tests/auto/qndefrecord/tst_qndefrecord.cpp
@@ -28,6 +28,9 @@
     void tst_textRecord_data();
     void tst_textRecord();
 
+    void tst_textRecord_malformedPayload_data();
+    void tst_textRecord_malformedPayload();
+
     void tst_uriRecord_data();
     void tst_uriRecord();
 
@@ -340,6 +343,43 @@
     }
 }
 
+void tst_QNdefRecord::tst_textRecord_malformedPayload_data()
+{
+    QTest::addColumn<QByteArray>("payload");
+
+    // The payload layout for an nfc text record is:
+    // [status: 1 byte][language code: codeLength bytes][text: remaining bytes]
+
+    // status byte claims codeLength=2, so payload should have 3 bytes or more
+    QTest::newRow("codeLength=2-payload=1byte")
+            << QByteArray::fromHex("02");
+
+    // status byte claims codeLength=5, but only 2 locale bytes follow
+    QTest::newRow("codeLength=5-payload=3bytes")
+            << QByteArray::fromHex("05656e");   // 0x05 + "en"
+
+    // status byte claims codeLength=63 (maximum), but payload is only 1 byte
+    QTest::newRow("codeLength=63-payload=1byte")
+        << QByteArray::fromHex("3f");
+
+    // utf16 flag set, codeLength=2, payload is only 2 bytes
+    QTest::newRow("utf16-codeLength=2-payload=2bytes")
+        << QByteArray::fromHex("8265");   // 0x82 + 'e'
+}
+
+void tst_QNdefRecord::tst_textRecord_malformedPayload()
+{
+    QFETCH(QByteArray, payload);
+
+    QNdefNfcTextRecord record;
+    record.setPayload(payload);
+
+    // Before the fix for QTBUG-147372, locale() and text() read past the end of
+    // the payload when codeLength > p.size()-1. ASAN would catch both calls here.
+    QCOMPARE(record.locale(), QString());
+    QCOMPARE(record.text(), QString());
+}
+
 void tst_QNdefRecord::tst_uriRecord_data()
 {
     QTest::addColumn<QString>("url");
