Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package qt6-coap for openSUSE:Factory checked in at 2026-10-18 19:22:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/qt6-coap (Old) and /work/SRC/openSUSE:Factory/.qt6-coap.new.3698736 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "qt6-coap" Sun Oct 18 19:22:40 2026 rev:45 rq:1384387 version:6.12.0 Changes: -------- --- /work/SRC/openSUSE:Factory/qt6-coap/qt6-coap.changes 2026-08-21 16:54:03.201521533 +0200 +++ /work/SRC/openSUSE:Factory/.qt6-coap.new.3698736/qt6-coap.changes 2026-10-18 19:23:00.394969714 +0200 @@ -1,0 +2,6 @@ +Wed Sep 30 16:08:55 UTC 2026 - Christophe Marin <[email protected]> + +- Update to 6.12.0 + https://www.qt.io/blog/qt-6.12-released + +------------------------------------------------------------------- Old: ---- qtcoap-everywhere-src-6.11.2.tar.xz New: ---- qtcoap-everywhere-src-6.12.0.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ qt6-coap.spec ++++++ --- /var/tmp/diff_new_pack.wDoo1H/_old 2026-10-18 19:23:01.068997626 +0200 +++ /var/tmp/diff_new_pack.wDoo1H/_new 2026-10-18 19:23:01.069997667 +0200 @@ -16,8 +16,8 @@ # -%define real_version 6.11.2 -%define short_version 6.11 +%define real_version 6.12.0 +%define short_version 6.12 %define tar_name qtcoap-everywhere-src %define tar_suffix %{nil} # @@ -27,7 +27,7 @@ %endif # Name: qt6-coap%{?pkg_suffix} -Version: 6.11.2 +Version: 6.12.0 Release: 0 Summary: Qt 6 CoAP client module License: GPL-3.0-or-later ++++++ _service ++++++ --- /var/tmp/diff_new_pack.wDoo1H/_old 2026-10-18 19:23:01.113999489 +0200 +++ /var/tmp/diff_new_pack.wDoo1H/_new 2026-10-18 19:23:01.119999737 +0200 @@ -1,9 +1,9 @@ <services> <service name="tar_scm" mode="disabled"> <param name="changesgenerate">disable</param> - <param name="version">6.11.2</param> + <param name="version">6.12.0</param> <param name="url">git://code.qt.io/qt/qtcoap.git</param> - <param name="revision">v6.11.2</param> + <param name="revision">v6.12.0</param> <param name="scm">git</param> <param name="filename">qtcoap-everywhere-src</param> </service> ++++++ qtcoap-everywhere-src-6.11.2.tar.xz -> qtcoap-everywhere-src-6.12.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/.cmake.conf new/qtcoap-everywhere-src-6.12.0/.cmake.conf --- old/qtcoap-everywhere-src-6.11.2/.cmake.conf 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/.cmake.conf 2026-09-26 12:11:53.000000000 +0200 @@ -1,4 +1,4 @@ -set(QT_REPO_MODULE_VERSION "6.11.2") +set(QT_REPO_MODULE_VERSION "6.12.0") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") set(QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_NO_CONTEXTLESS_CONNECT=1" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/CONTRIBUTING.md new/qtcoap-everywhere-src-6.12.0/CONTRIBUTING.md --- old/qtcoap-everywhere-src-6.11.2/CONTRIBUTING.md 1970-01-01 01:00:00.000000000 +0100 +++ new/qtcoap-everywhere-src-6.12.0/CONTRIBUTING.md 2026-09-26 12:11:53.000000000 +0200 @@ -0,0 +1,11 @@ +## Qt contribution guidelines + +We welcome contributions to Qt! + +Note that we cannot accept pull requests on GitHub. All contributions +to the Qt project are exclusively handled through the +[Gerrit code review system](https://codereview.qt-project.org). + +See +[contribute.qt-project.org](https://contribute.qt-project.org/) to learn +more. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/REUSE.toml new/qtcoap-everywhere-src-6.12.0/REUSE.toml --- old/qtcoap-everywhere-src-6.11.2/REUSE.toml 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/REUSE.toml 2026-09-26 12:11:53.000000000 +0200 @@ -21,7 +21,7 @@ SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR BSD-3-Clause" [[annotations]] -path = ["**/README*", "**.qdocconf", "src/coap/doc/style/style.css"] +path = ["**.qdocconf", "src/coap/doc/style/style.css", "**/README*", "CONTRIBUTING.md"] comment = "documentation" precedence = "closest" SPDX-FileCopyrightText = "Copyright (C) The Qt Company Ltd." diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/dependencies.yaml new/qtcoap-everywhere-src-6.12.0/dependencies.yaml --- old/qtcoap-everywhere-src-6.11.2/dependencies.yaml 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/dependencies.yaml 2026-09-26 12:11:53.000000000 +0200 @@ -1,4 +1,4 @@ dependencies: ../qtbase: - ref: ef55f427f2c8b410d34f8a7681020a3000cf6866 + ref: 025bdad181de241e81bf853c8a2d7bf3d19261a9 required: true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/licenseRule.json new/qtcoap-everywhere-src-6.12.0/licenseRule.json --- old/qtcoap-everywhere-src-6.11.2/licenseRule.json 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/licenseRule.json 2026-09-26 12:11:53.000000000 +0200 @@ -58,8 +58,9 @@ }, { "comment": "Files with the following endings are Documentation licensed.", - "file_pattern_ending": [".qdoc", ".qdocinc" , ".qdocconf", "README", "qt_attribution.json", - "README.md", ".css"], + "file_pattern_ending": [".qdoc", ".qdocinc" , ".qdocconf", ".css", + "README.md", "CONTRIBUTING.md", + "qt_attribution.json"], "location":{ "": { "comment": "", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/src/coap/CMakeLists.txt new/qtcoap-everywhere-src-6.12.0/src/coap/CMakeLists.txt --- old/qtcoap-everywhere-src-6.11.2/src/coap/CMakeLists.txt 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/src/coap/CMakeLists.txt 2026-09-26 12:11:53.000000000 +0200 @@ -28,6 +28,7 @@ Qt::NetworkPrivate PUBLIC_LIBRARIES Qt::Core + Qt::Network PRIVATE_MODULE_INTERFACE Qt::CorePrivate Qt::NetworkPrivate diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/src/coap/doc/src/qtcoap-index.qdoc new/qtcoap-everywhere-src-6.12.0/src/coap/doc/src/qtcoap-index.qdoc --- old/qtcoap-everywhere-src-6.11.2/src/coap/doc/src/qtcoap-index.qdoc 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/src/coap/doc/src/qtcoap-index.qdoc 2026-09-26 12:11:53.000000000 +0200 @@ -28,6 +28,10 @@ \li Resource discovery (multicast and single server) \endlist + \note Security is opt-in: QCoapClient uses QtCoap::NoSecurity by default and must + be constructed with a secure mode to enable DTLS. + See \l{Qt CoAP Overview#security}{Security}. + \include module-use.qdocinc using qt module \code diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/src/coap/doc/src/qtcoap-overview.qdoc new/qtcoap-everywhere-src-6.12.0/src/coap/doc/src/qtcoap-overview.qdoc --- old/qtcoap-everywhere-src-6.11.2/src/coap/doc/src/qtcoap-overview.qdoc 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/src/coap/doc/src/qtcoap-overview.qdoc 2026-09-26 12:11:53.000000000 +0200 @@ -17,9 +17,10 @@ for addressing. At the same time it provides lightweight reliability mechanisms and security. - Qt CoAP implements the client side of CoAP. By default, the transport layer uses - QUdpSocket and QDtls for security. Alternative transports can be used by implementing - the QCoapConnection interface. + Qt CoAP implements the client side of CoAP. The default transport is built on + QUdpSocket, with QDtls providing security only when a secure mode is selected; + no security mode is enabled by default (see \l{#security}{Security}). Alternative + transports can be used by implementing the QCoapConnection interface. \section1 Messaging Model @@ -141,6 +142,17 @@ based on Datagram TLS (DTLS) over UDP. However, as mentioned above, it is possible to provide a custom transport with another security type. + \note When you provide a custom transport, the DTLS protection described here no longer + applies. Securing the exchange then becomes the responsibility of that transport, which + must provide encryption, peer authentication, and message framing appropriate to the + protocol it is built on. A transport that omits any of these leaves requests and + responses unprotected, and the loss is not visible to the rest of the application. + + \warning By default Qt CoAP operates without transport security. Unless a secure + mode is selected, requests and responses are sent as plain UDP datagrams. Use the + default (\c{QtCoap::NoSecurity}) only on fully trusted networks; on any untrusted + network construct QCoapClient with a secure mode. + For securing the CoAP client, one of the supported security modes should be specified when creating the client: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapclient.cpp new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapclient.cpp --- old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapclient.cpp 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapclient.cpp 2026-09-26 12:11:53.000000000 +0200 @@ -33,11 +33,14 @@ QCoapClientPrivate::~QCoapClientPrivate() { + // protocol and connection (and their children, such as the retransmission + // timers and the UDP socket) live in workerThread. They must be destroyed in + // that thread, not here. + protocol->deleteLater(); + connection->deleteLater(); workerThread->quit(); workerThread->wait(); delete workerThread; - delete protocol; - delete connection; } /*! @@ -88,6 +91,32 @@ It can be used the same way as a QCoapReply but contains also a list of resources. + \section1 Security Considerations + + During a block-wise transfer, the response payload is accumulated + in memory until the transfer completes, and QtCoap does not impose an upper + bound on the accumulated size. A malicious or malfunctioning server that + keeps sending blocks without ever signalling the final one can therefore + drive the client to exhaust memory. The accumulated size is not observable while + the transfer is in progress, so an application cannot bound it directly. Perform + block-wise exchanges only with trusted servers, and preferably on a trusted or + isolated network. + + \section2 Multicast + + DTLS protects a connection between two endpoints and therefore cannot apply to + multicast, where more than 2 endpoints may be present. Because of this, a + multicast request and its replies are neither encrypted nor authenticated. + + Every host in the group may answer a multicast request. In CoAP these hosts + are all legitimate owners of the resource and are expected to serve + equivalent replies. When a reply is sent block by block, the remaining + blocks are retrieved by unicast from whichever host answered. + + CoAP multicast is designed with the assumption that the network is secured + and every node in the group is trusted. Use multicast only in scenarios that + fulfill this assumption. + \sa QCoapRequest, QCoapReply, QCoapResourceDiscoveryReply */ @@ -112,6 +141,9 @@ been received, \a message contains the payload and the message details, and \a sender contains the sender address. + \note Multicast requests are unauthenticated; see \l {Multicast} + {multicast security considerations} for the trust model this relies on. + \sa error(), QCoapReply::finished(), QCoapReply::error() */ @@ -181,7 +213,11 @@ { Q_Q(QCoapClient); - delete connection; + if (connection) { + // The connection lives in workerThread, let that thread delete it. + connection->disconnect(); + connection->deleteLater(); + } connection = customConnection; q->connect(connection, &QCoapConnection::readyRead, protocol, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapconnection.cpp new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapconnection.cpp --- old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapconnection.cpp 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapconnection.cpp 2026-09-26 12:11:53.000000000 +0200 @@ -21,6 +21,13 @@ It isolates CoAP clients from the transport in use, so that any client can be used with any supported transport. + + \note A subclass that implements a transport is responsible for the security + of the connection it establishes. Unless the transport itself provides + encryption, peer authentication, and correct message framing, the + confidentiality, integrity, and authenticity guarantees that DTLS provides + over the default transport no longer hold. See the security discussion in the + \l(Qt CoAP Overview#Security){Qt CoAP Overview}. */ /*! diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapinternalmessage.cpp new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapinternalmessage.cpp --- old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapinternalmessage.cpp 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapinternalmessage.cpp 2026-09-26 12:11:53.000000000 +0200 @@ -90,7 +90,11 @@ const auto value = option.opaqueValue(); const quint8 *optionData = reinterpret_cast<const quint8 *>(value.data()); - const quint8 lastByte = optionData[option.length() - 1]; + + // A zero-length block option is valid and encodes NUM=0, M=0, SZX=0 + // (RFC 7959 §2.2). Treat the absent last byte as 0 instead of reading + // optionData[-1]; the loop below is then skipped and every field resolves to 0. + const quint8 lastByte = option.length() > 0 ? optionData[option.length() - 1] : 0; quint32 blockNumber = 0; for (int i = 0; i < option.length() - 1; ++i) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapinternalreply.cpp new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapinternalreply.cpp --- old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapinternalreply.cpp 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapinternalreply.cpp 2026-09-26 12:11:53.000000000 +0200 @@ -4,8 +4,11 @@ // Qt-Security score:critical reason:network-protocol #include "qcoapinternalreply_p.h" +#include <QtCore/qendian.h> #include <QtCore/qmath.h> +#include <memory> + QT_BEGIN_NAMESPACE /*! @@ -51,7 +54,11 @@ //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ QCoapInternalReply *QCoapInternalReply::createFromFrame(const QByteArray &reply, QObject *parent) { - QCoapInternalReply *internalReply = new QCoapInternalReply(parent); + // A CoAP message is at least 4 bytes long + if (reply.size() < 4) + return nullptr; + + auto internalReply = std::make_unique<QCoapInternalReply>(parent); QCoapInternalReplyPrivate *d = internalReply->d_func(); const quint8 *pduData = reinterpret_cast<const quint8 *>(reply.data()); @@ -60,39 +67,81 @@ d->message.setVersion((pduData[0] >> 6) & 0x03); d->message.setType(QCoapMessage::Type((pduData[0] >> 4) & 0x03)); quint8 tokenLength = (pduData[0]) & 0x0F; + + // RFC 7252 §3: token lengths 9-15 are reserved and must be treated as a + // message format error. Also reject a token whose bytes are not all present. + if (tokenLength > 8 || 4 + tokenLength > reply.size()) + return nullptr; + d->responseCode = static_cast<QtCoap::ResponseCode>(pduData[1]); d->message.setMessageId(static_cast<quint16>((static_cast<quint16>(pduData[2]) << 8) | static_cast<quint16>(pduData[3]))); d->message.setToken(reply.mid(4, tokenLength)); // Parse Options - int i = 4 + tokenLength; - quint16 lastOptionNumber = 0; - while (i != reply.size() && pduData[i] != 0xFF) { - quint16 optionDelta = ((pduData[i] >> 4) & 0x0F); - quint16 optionLength = (pduData[i] & 0x0F); + qsizetype i = 4 + tokenLength; + quint32 lastOptionNumber = 0; + while (i < reply.size() && pduData[i] != 0xFF) { + quint32 optionDelta = ((pduData[i] >> 4) & 0x0F); + quint32 optionLength = (pduData[i] & 0x0F); // Delta value > 12 : special values if (optionDelta == 13) { - ++i; + if (++i >= reply.size()) + return nullptr; optionDelta = pduData[i] + 13; } else if (optionDelta == 14) { - ++i; - optionDelta = pduData[i] + 269; + // RFC 7252 §3.1: nibble 14 is a 16-bit unsigned integer in network + // byte order, giving the value minus 269. + if (i >= reply.size() - 2) + return nullptr; + optionDelta = qFromBigEndian<quint16>(pduData + i + 1) + 269; + i += 2; } // Delta length > 12 : special values if (optionLength == 13) { - ++i; + if (++i >= reply.size()) + return nullptr; optionLength = pduData[i] + 13; } else if (optionLength == 14) { - ++i; - optionLength = pduData[i] + 269; + // RFC 7252 §3.1: nibble 14 is a 16-bit unsigned integer in network + // byte order, giving the value minus 269. + if (i >= reply.size() - 2) + return nullptr; + optionLength = qFromBigEndian<quint16>(pduData + i + 1) + 269; + i += 2; + } + + // The option value is optionLength bytes following the current byte. + // Reject the frame if it would run past the end of the buffer. + if (i >= reply.size() - static_cast<qsizetype>(optionLength)) + return nullptr; + + quint32 optionNumber = lastOptionNumber + optionDelta; + // RFC 7252: option numbers do not exceed 65535. A larger cumulative + // number means a malformed frame; every following option would be out of + // range too, so reject the whole reply. + if (optionNumber > 65535) + return nullptr; + + const QByteArray optionValue = reply.sliced(i + 1, optionLength); + + // Validate block options (RFC 7959 §2.2) before they are interpreted. + if (optionNumber == QCoapOption::Block1 || optionNumber == QCoapOption::Block2) { + // A block option value is 0-3 bytes. A longer value yields a block + // number that overflows the 20-bit NUM field. + if (optionValue.size() > 3) + return nullptr; + // SZX value 7 (block size 2048) is reserved; it is the low 3 bits of + // the last byte. Accepting it yields a 2048-byte block size, which is illegal + if (!optionValue.isEmpty() + && (static_cast<quint8>(optionValue.back()) & 0x7) == 0x7) { + return nullptr; + } } - quint16 optionNumber = lastOptionNumber + optionDelta; - internalReply->addOption(QCoapOption::OptionName(optionNumber), - reply.mid(i + 1, optionLength)); + internalReply->addOption(QCoapOption::OptionName(optionNumber), optionValue); lastOptionNumber = optionNumber; i += 1 + optionLength; } @@ -104,7 +153,7 @@ d->message.setPayload(d->message.payload().append(currentPayload)); } - return internalReply; + return internalReply.release(); } /*! @@ -156,7 +205,8 @@ const auto value = option.opaqueValue(); const quint8 *optionData = reinterpret_cast<const quint8 *>(value.data()); - const quint8 lastByte = optionData[option.length() - 1]; + // Zero-length block option => M=0 (no further block); avoid optionData[-1]. + const quint8 lastByte = option.length() > 0 ? optionData[option.length() - 1] : 0; // M field bool hasNextBlock = ((lastByte & 0x8) == 0x8); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapoption.cpp new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapoption.cpp --- old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapoption.cpp 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapoption.cpp 2026-09-26 12:11:53.000000000 +0200 @@ -172,14 +172,21 @@ /*! Returns the integer value of the option. + + \note A uint option holds at most 4 bytes. If the option value is longer, + only its low 4 bytes are used to compute the result; use \l length() to + detect an over-long value. */ quint32 QCoapOption::uintValue() const { Q_D(const QCoapOption); quint32 intValue = 0; - for (int i = 0; i < d->value.size(); i++) - intValue |= static_cast<quint8>(d->value.at(i)) << (8 * i); + // A uint option holds at most 4 bytes (32 bits). Stop at 4 so the shift can + // never reach 32 bits (undefined behaviour), and cast to the result type + // before shifting to avoid signed overflow on the top byte. + for (int i = 0; i < d->value.size() && i < 4; ++i) + intValue |= static_cast<quint32>(static_cast<quint8>(d->value.at(i))) << (8 * i); return intValue; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapprotocol.cpp new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapprotocol.cpp --- old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapprotocol.cpp 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapprotocol.cpp 2026-09-26 12:11:53.000000000 +0200 @@ -324,6 +324,13 @@ Q_ASSERT(QThread::currentThread() == q->thread()); QSharedPointer<QCoapInternalReply> 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapreply.cpp new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapreply.cpp --- old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapreply.cpp 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapreply.cpp 2026-09-26 12:11:53.000000000 +0200 @@ -186,7 +186,7 @@ message details. The \a reply parameter is the QCoapReply itself for convenience. - \sa QCoapClient::finished(), isFinished(), finished(), notified() + \sa QCoapClient::finished(), isFinished(), finished(), aborted() */ /*! diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapresourcediscoveryreply.cpp new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapresourcediscoveryreply.cpp --- old/qtcoap-everywhere-src-6.11.2/src/coap/qcoapresourcediscoveryreply.cpp 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/src/coap/qcoapresourcediscoveryreply.cpp 2026-09-26 12:11:53.000000000 +0200 @@ -117,7 +117,7 @@ for (const QByteArray ¶meter : parameterList) { QString parameterString = QString::fromUtf8(parameter); const qsizetype length = parameterString.size(); - if (parameter.startsWith('<')) + if (parameter.startsWith('<') && parameter.endsWith('>')) resource.setPath(parameterString.mid(1, length - 2)); else if (parameter.startsWith("title=")) resource.setTitle(parameterString.mid(6).remove(quote)); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/tests/auto/qcoapinternalreply/tst_qcoapinternalreply.cpp new/qtcoap-everywhere-src-6.12.0/tests/auto/qcoapinternalreply/tst_qcoapinternalreply.cpp --- old/qtcoap-everywhere-src-6.11.2/tests/auto/qcoapinternalreply/tst_qcoapinternalreply.cpp 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/tests/auto/qcoapinternalreply/tst_qcoapinternalreply.cpp 2026-09-26 12:11:53.000000000 +0200 @@ -15,6 +15,11 @@ private Q_SLOTS: void parseReplyPdu_data(); void parseReplyPdu(); + void parseInvalidReplyPdu_data(); + void parseInvalidReplyPdu(); + void parseExtendedOption(); + void parseEmptyBlockOption(); + void nextBlockToSendWithEmptyBlock1(); void updateReply_data(); void updateReply(); }; @@ -137,6 +142,121 @@ QCOMPARE(reply->message()->payload(), payload.toUtf8()); } +void tst_QCoapInternalReply::parseInvalidReplyPdu_data() +{ + QTest::addColumn<QByteArray>("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"); + + // Option-parser cases: each frame has a valid 4-byte header (no token) + // followed by a single option byte whose encoding demands bytes that are + // not present. createFromFrame() must reject the frame rather than read + // past the buffer or deliver a silently truncated option. + + // 0x09: option value length 9, but no value bytes follow it. + QTest::newRow("option_value_overruns_buffer") << QByteArray::fromHex("50450001" "09"); + // 0xd0: delta nibble 13 promises one extended delta byte that is missing. + QTest::newRow("missing_option_delta_ext_byte") << QByteArray::fromHex("50450001" "d0"); + // 0x0d: length nibble 13 promises one extended length byte that is missing. + QTest::newRow("missing_option_length_ext_byte") << QByteArray::fromHex("50450001" "0d"); + // 0x0d + 0xff: extended length yields optionLength 268, far past the buffer. + QTest::newRow("extended_option_length_overruns") << QByteArray::fromHex("50450001" "0d" "ff"); + // 0xe0 + 0xffff: extended delta 0xffff -> option number 65535 + 269 = 65804, + // which exceeds the 65535 maximum, so the frame is rejected. + QTest::newRow("option_number_exceeds_max") << QByteArray::fromHex("50450001" "e0" "ffff"); + + // Token-length cases. + // TKL 9 is reserved (RFC 7252 §3) and must be rejected even when the 9 token + // bytes are present, so this frame includes them. + QTest::newRow("reserved_token_length") << QByteArray::fromHex("59450001" "aabbccddeeff001122"); + // TKL 4 is valid, but no token bytes follow the 4-byte header. + QTest::newRow("truncated_token") << QByteArray::fromHex("54450001"); + + // RFC 7959 §2.2: SZX value 7 (block size 2048) is reserved. Reject a block + // option whose last byte has SZX 7 (low 3 bits set). Value byte 0x07 encodes + // NUM=0, M=0, SZX=7. Option header 0xd1 = delta 13 (extended), length 1. + // Block2 (number 23): extended delta byte 0x0a -> 10 + 13 = 23. + QTest::newRow("reserved_block2_szx7") << QByteArray::fromHex("50450001" "d10a" "07"); + // Block1 (number 27): extended delta byte 0x0e -> 14 + 13 = 27. + QTest::newRow("reserved_block1_szx7") << QByteArray::fromHex("50450001" "d10e" "07"); + + // RFC 7959 §2.2: a block option value is 0-3 bytes; a 4-byte value is rejected + // (it would overflow the 20-bit block number). Option header 0xd4 = delta 13 + // (extended), length 4; 0x0a -> Block2 (23); last byte 0x04 has a valid SZX, so + // this exercises the length check rather than the SZX check. + QTest::newRow("oversized_block2_value") << QByteArray::fromHex("50450001" "d40a" "01020304"); +} + +void tst_QCoapInternalReply::parseInvalidReplyPdu() +{ + QFETCH(QByteArray, frame); + + QScopedPointer<QCoapInternalReply> reply(QCoapInternalReply::createFromFrame(frame)); + QVERIFY2(reply.isNull(), + "createFromFrame() must reject a malformed frame by returning nullptr"); +} + +void tst_QCoapInternalReply::parseExtendedOption() +{ + // RFC 7252 §3.1: a delta/length nibble of 14 is followed by a 16-bit + // unsigned integer in network byte order (the value minus 269). Build one + // option that uses the 16-bit form for BOTH its delta and its length, with + // distinct first/second bytes so the test also verifies the byte order: + // 0xee -> delta nibble 14, length nibble 14 + // 0x01 0x02 -> extended delta = 0x0102 = 258 -> option number 258 + 269 = 527 + // 0x00 0x03 -> extended length = 0x0003 = 3 -> value length 3 + 269 = 272 + // 272 value bytes + // A decoder that read one byte instead of two, or used the wrong byte order, + // would compute different numbers and misalign the rest of the frame. + const QByteArray value(272, 'x'); + const QByteArray frame = QByteArray::fromHex("50450001" "ee" "0102" "0003") + value; + + QScopedPointer<QCoapInternalReply> reply(QCoapInternalReply::createFromFrame(frame)); + + QVERIFY(!reply.isNull()); + QCOMPARE(reply->message()->optionCount(), 1); + const QCoapOption option = reply->message()->options().constFirst(); + QCOMPARE(static_cast<int>(option.name()), 527); + QCOMPARE(option.opaqueValue(), value); +} + +void tst_QCoapInternalReply::parseEmptyBlockOption() +{ + // RFC 7959 §2.2: a zero-length Block option is valid and encodes + // NUM=0, M=0, SZX=0. Frame: 4-byte header (no token), then a Block2 + // option (number 23) carrying an empty value. + // 0xd0 -> option delta nibble 13 (extended), length nibble 0 + // 0x0a -> extended delta byte, giving option number 13 + 10 = 23 (Block2) + const QByteArray frame = QByteArray::fromHex("50450001" "d0" "0a"); + QScopedPointer<QCoapInternalReply> reply(QCoapInternalReply::createFromFrame(frame)); + + QVERIFY(!reply.isNull()); + QCOMPARE(reply->currentBlockNumber(), 0u); + QVERIFY(!reply->hasMoreBlocksToReceive()); + QCOMPARE(reply->blockSize(), 16u); +} + +void tst_QCoapInternalReply::nextBlockToSendWithEmptyBlock1() +{ + // A zero-length Block1 option is valid (RFC 7959 §2.2), encoding M=0, so + // there is no next block to send. nextBlockToSend() must return -1 rather + // than read optionData[-1]. Frame: 4-byte header, then a Block1 option + // (number 27) with an empty value. + // 0xd0 -> option delta nibble 13 (extended), length nibble 0 + // 0x0e -> extended delta byte, giving option number 13 + 14 = 27 (Block1) + const QByteArray frame = QByteArray::fromHex("50450001" "d0" "0e"); + QScopedPointer<QCoapInternalReply> reply(QCoapInternalReply::createFromFrame(frame)); + + QVERIFY(!reply.isNull()); + QCOMPARE(reply->nextBlockToSend(), -1); +} + void tst_QCoapInternalReply::updateReply_data() { QTest::addColumn<QByteArray>("data"); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/tests/auto/qcoapoption/tst_qcoapoption.cpp new/qtcoap-everywhere-src-6.12.0/tests/auto/qcoapoption/tst_qcoapoption.cpp --- old/qtcoap-everywhere-src-6.11.2/tests/auto/qcoapoption/tst_qcoapoption.cpp 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/tests/auto/qcoapoption/tst_qcoapoption.cpp 2026-09-26 12:11:53.000000000 +0200 @@ -15,6 +15,8 @@ void constructWithQByteArray(); void constructWithQString(); void constructWithInteger(); + void uintValueIgnoresExcessBytes(); + void uintValueHandlesHighBitBytes(); void constructWithUtf8Characters(); }; @@ -80,6 +82,30 @@ QCOMPARE(option.uintValue(), value); } +void tst_QCoapOption::uintValueIgnoresExcessBytes() +{ + // A uint option holds at most 4 bytes. uintValue() must not shift past 32 + // bits when the value is longer; only the low 4 bytes are used (little-endian). + // Size2 has no per-option size check in setValue(), so all 6 bytes are stored. + const QByteArray value = QByteArray::fromHex("010203040506"); + QCoapOption option(QCoapOption::Size2, value); + + QCOMPARE(option.uintValue(), 0x04030201u); +} + +void tst_QCoapOption::uintValueHandlesHighBitBytes() +{ + // A byte with the high bit set must be zero-extended, not sign-extended: + QCOMPARE(QCoapOption(QCoapOption::Size2, QByteArray::fromHex("ff")).uintValue(), + 0xffu); + + // All four bytes have the high bit set: the top byte exercises the byte<<24 + // path (signed-overflow avoidance), and the result confirms every byte is + // zero-extended rather than sign-extended. + QCOMPARE(QCoapOption(QCoapOption::Size2, QByteArray::fromHex("deadbeef")).uintValue(), + 0xefbeaddeu); +} + void tst_QCoapOption::constructWithUtf8Characters() { QByteArray ba = "\xc3\xa9~\xce\xbb\xe2\x82\xb2"; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qtcoap-everywhere-src-6.11.2/tests/auto/qcoapresource/tst_qcoapresource.cpp new/qtcoap-everywhere-src-6.12.0/tests/auto/qcoapresource/tst_qcoapresource.cpp --- old/qtcoap-everywhere-src-6.11.2/tests/auto/qcoapresource/tst_qcoapresource.cpp 2026-08-11 19:38:38.000000000 +0200 +++ new/qtcoap-everywhere-src-6.12.0/tests/auto/qcoapresource/tst_qcoapresource.cpp 2026-09-26 12:11:53.000000000 +0200 @@ -104,6 +104,21 @@ << maximumSizeList << observableList << coreLinks; + + // A link that opens with '<' but has no closing '>' is malformed (RFC 6690). + // It must be dropped without disturbing the valid entries around it: here + // "</malformed" is discarded and only "</valid>" yields a resource. + QTest::newRow("malformed_unterminated_link") + << 1 + << QString("10.20.30.40") + << QList<QString>{ QStringLiteral("/valid") } + << QList<QString>{ QString() } + << QList<QString>{ QString() } + << QList<uint>{ 0u } + << QList<QString>{ QString() } + << QList<int>{ -1 } + << QList<bool>{ false } + << QByteArray("</valid>,</malformed"); } void tst_QCoapResource::parseCoreLink()
