From 34f98ff330e49125ebf00870e7d841368fb747e7 Mon Sep 17 00:00:00 2001 From: Mate Barany Date: Fri, 14 Aug 2026 17:39:28 +0200 Subject: [PATCH] Reject CoAP frames shorter than the fixed header QCoapInternalReply::createFromFrame() dereferenced the first four header bytes (version/type/token length, response code and message ID) without first checking the frame length. A UDP datagram shorter than the 4-byte CoAP header therefore caused an out-of-bounds read while populating the header fields. Reject frames shorter than 4 bytes in createFromFrame() by returning nullptr, and handle that in the callers: decode() propagates the nullptr, and onFrameReceived() drops the datagram. Task-number: QTBUG-149345 Pick-to: 6.8 6.5 5.15 Change-Id: Id4183e074bd2b32e96553e60461ac62aab4bbdf5 Reviewed-by: Ivan Solovev Reviewed-by: MÃ¥rten Nordheim (cherry picked from commit 86dd4196b7324535dc7cc35e86de78e429c41470) Reviewed-by: Qt Cherry-pick Bot (cherry picked from commit 4e5cf210d5beed0d50baa9bb7555cbbbe8cbaf83) --- diff --git a/src/coap/qcoapinternalreply.cpp b/src/coap/qcoapinternalreply.cpp index f5b584a..a1d67af 100644 --- a/src/coap/qcoapinternalreply.cpp +++ b/src/coap/qcoapinternalreply.cpp @@ -51,6 +51,10 @@ //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ QCoapInternalReply *QCoapInternalReply::createFromFrame(const QByteArray &reply, QObject *parent) { + // A CoAP message is at least 4 bytes long + if (reply.size() < 4) + return nullptr; + QCoapInternalReply *internalReply = new QCoapInternalReply(parent); QCoapInternalReplyPrivate *d = internalReply->d_func(); diff --git a/src/coap/qcoapprotocol.cpp b/src/coap/qcoapprotocol.cpp index f12450f..9b4ec35 100644 --- a/src/coap/qcoapprotocol.cpp +++ b/src/coap/qcoapprotocol.cpp @@ -324,6 +324,13 @@ Q_ASSERT(QThread::currentThread() == q->thread()); QSharedPointer reply(decode(data, sender)); + if (!reply) { + qCDebug(lcCoapProtocol).nospace() << "QtCoap: Discarding malformed frame from " + << sender << " (" << data.size() + << " bytes): not a valid CoAP message"; + return; + } + const QCoapMessage *messageReceived = reply->message(); QCoapInternalRequest *request = nullptr; @@ -691,6 +698,9 @@ { Q_Q(QCoapProtocol); QCoapInternalReply *reply = QCoapInternalReply::createFromFrame(data, q); + if (!reply) + return nullptr; + reply->setSenderAddress(sender); return reply; diff --git a/tests/auto/qcoapinternalreply/tst_qcoapinternalreply.cpp b/tests/auto/qcoapinternalreply/tst_qcoapinternalreply.cpp index 8faec9a..cab3402 100644 --- a/tests/auto/qcoapinternalreply/tst_qcoapinternalreply.cpp +++ b/tests/auto/qcoapinternalreply/tst_qcoapinternalreply.cpp @@ -15,6 +15,8 @@ private Q_SLOTS: void parseReplyPdu_data(); void parseReplyPdu(); + void parseInvalidReplyPdu_data(); + void parseInvalidReplyPdu(); void updateReply_data(); void updateReply(); }; @@ -137,6 +139,29 @@ QCOMPARE(reply->message()->payload(), payload.toUtf8()); } +void tst_QCoapInternalReply::parseInvalidReplyPdu_data() +{ + QTest::addColumn("frame"); + + // A CoAP message is at least 4 bytes long (the fixed header). Frames shorter + // than that must be rejected before the header is dereferenced; + // createFromFrame() signals rejection by returning nullptr. + QTest::newRow("empty") << QByteArray(); + QTest::newRow("one_byte") << QByteArray::fromHex("50"); + QTest::newRow("two_bytes") << QByteArray::fromHex("5045"); + QTest::newRow("three_bytes") << QByteArray::fromHex("5045fb"); +} + +void tst_QCoapInternalReply::parseInvalidReplyPdu() +{ + QFETCH(QByteArray, frame); + + QScopedPointer reply(QCoapInternalReply::createFromFrame(frame)); + QVERIFY2(reply.isNull(), + "createFromFrame() must reject a malformed frame by returning nullptr"); +} + + void tst_QCoapInternalReply::updateReply_data() { QTest::addColumn("data");