diff options
author | Mårten Nordheim <marten.nordheim@qt.io> | 2021-05-20 14:12:39 +0200 |
---|---|---|
committer | Mårten Nordheim <marten.nordheim@qt.io> | 2021-05-31 17:25:20 +0200 |
commit | 69982182a394618d4f121d2938d7d76196fe78f6 (patch) | |
tree | 32d98bc55cf0fed28d3b23ed15c7d39ee3bc29db /src/network/access | |
parent | 347310eb21facbd03d2168d67d83fdbfd6f6888c (diff) |
QNetworkRequest: Add API to set a minimum archive bomb size
Fixes: QTBUG-91870
Change-Id: Ia23e8b8bcfdf65a91fe57e739242a355c681c9e6
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Diffstat (limited to 'src/network/access')
-rw-r--r-- | src/network/access/qdecompresshelper.cpp | 28 | ||||
-rw-r--r-- | src/network/access/qdecompresshelper_p.h | 2 | ||||
-rw-r--r-- | src/network/access/qhttp2protocolhandler.cpp | 4 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkreply.cpp | 3 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkrequest.cpp | 13 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkrequest_p.h | 7 | ||||
-rw-r--r-- | src/network/access/qnetworkreplyhttpimpl.cpp | 9 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.cpp | 46 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.h | 4 |
9 files changed, 77 insertions, 39 deletions
diff --git a/src/network/access/qdecompresshelper.cpp b/src/network/access/qdecompresshelper.cpp index d0e75ef0dc..d51f9a290e 100644 --- a/src/network/access/qdecompresshelper.cpp +++ b/src/network/access/qdecompresshelper.cpp @@ -42,6 +42,7 @@ #include <QtCore/private/qbytearray_p.h> #include <QtCore/qiodevice.h> +#include <limits> #include <zlib.h> #if QT_CONFIG(brotli) @@ -328,7 +329,7 @@ bool QDecompressHelper::countInternal(const QByteArray &data) if (countDecompressed) { if (!countHelper) { countHelper = std::make_unique<QDecompressHelper>(); - countHelper->setArchiveBombDetectionEnabled(archiveBombDetectionEnabled); + countHelper->setMinimumArchiveBombSize(minimumArchiveBombSize); countHelper->setEncoding(contentEncoding); } countHelper->feed(data); @@ -346,7 +347,7 @@ bool QDecompressHelper::countInternal(const QByteDataBuffer &buffer) if (countDecompressed) { if (!countHelper) { countHelper = std::make_unique<QDecompressHelper>(); - countHelper->setArchiveBombDetectionEnabled(archiveBombDetectionEnabled); + countHelper->setMinimumArchiveBombSize(minimumArchiveBombSize); countHelper->setEncoding(contentEncoding); } countHelper->feed(buffer); @@ -393,28 +394,19 @@ qsizetype QDecompressHelper::read(char *data, qsizetype maxSize) /*! \internal - Disables or enables checking the decompression ratio of archives - according to the value of \a enable. - Only for enabling us to test handling of large decompressed files - without needing to bundle large compressed files. + Set the \a threshold required before the archive bomb detection kicks in. + By default this is 10MB. Setting it to -1 is treated as disabling the + feature. */ -void QDecompressHelper::setArchiveBombDetectionEnabled(bool enable) -{ - archiveBombDetectionEnabled = enable; - if (countHelper) - countHelper->setArchiveBombDetectionEnabled(enable); -} - void QDecompressHelper::setMinimumArchiveBombSize(qint64 threshold) { + if (threshold == -1) + threshold = std::numeric_limits<qint64>::max(); minimumArchiveBombSize = threshold; } bool QDecompressHelper::isPotentialArchiveBomb() const { - if (!archiveBombDetectionEnabled) - return false; - if (totalCompressedBytes == 0) return false; @@ -430,12 +422,16 @@ bool QDecompressHelper::isPotentialArchiveBomb() const break; case Deflate: case GZip: + // This value is mentioned in docs for + // QNetworkRequest::setMinimumArchiveBombSize, keep synchronized if (ratio > 40) { return true; } break; case Brotli: case Zstandard: + // This value is mentioned in docs for + // QNetworkRequest::setMinimumArchiveBombSize, keep synchronized if (ratio > 100) { return true; } diff --git a/src/network/access/qdecompresshelper_p.h b/src/network/access/qdecompresshelper_p.h index 6a77775790..96199a91f8 100644 --- a/src/network/access/qdecompresshelper_p.h +++ b/src/network/access/qdecompresshelper_p.h @@ -91,7 +91,6 @@ public: void clear(); - void setArchiveBombDetectionEnabled(bool enable); void setMinimumArchiveBombSize(qint64 threshold); static bool isSupportedEncoding(const QByteArray &encoding); @@ -119,7 +118,6 @@ private: qint64 uncompressedBytes = 0; // Used for calculating the ratio - bool archiveBombDetectionEnabled = true; qint64 minimumArchiveBombSize = 10 * 1024 * 1024; qint64 totalUncompressedBytes = 0; qint64 totalCompressedBytes = 0; diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp index 8f2ad8391b..aaa7b58ac4 100644 --- a/src/network/access/qhttp2protocolhandler.cpp +++ b/src/network/access/qhttp2protocolhandler.cpp @@ -1237,8 +1237,8 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader httpReplyPrivate->removeAutoDecompressHeader(); httpReplyPrivate->decompressHelper.setEncoding( httpReplyPrivate->headerField("content-encoding")); - if (httpReplyPrivate->request.ignoreDecompressionRatio()) - httpReplyPrivate->decompressHelper.setArchiveBombDetectionEnabled(false); + httpReplyPrivate->decompressHelper.setMinimumArchiveBombSize( + httpReplyPrivate->request.minimumArchiveBombSize()); } if (QHttpNetworkReply::isHttpRedirect(statusCode)) { diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp index 76c16335ca..a9579d71bc 100644 --- a/src/network/access/qhttpnetworkreply.cpp +++ b/src/network/access/qhttpnetworkreply.cpp @@ -557,8 +557,7 @@ qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket) if (autoDecompress && isCompressed()) { if (!decompressHelper.setEncoding(headerField("content-encoding"))) return -1; // Either the encoding was unsupported or the decoder could not be set up - if (request.ignoreDecompressionRatio()) - decompressHelper.setArchiveBombDetectionEnabled(false); + decompressHelper.setMinimumArchiveBombSize(request.minimumArchiveBombSize()); } } return bytes; diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp index 3518dba9ed..c1b859958a 100644 --- a/src/network/access/qhttpnetworkrequest.cpp +++ b/src/network/access/qhttpnetworkrequest.cpp @@ -57,6 +57,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest customVerb(other.customVerb), priority(other.priority), uploadByteDevice(other.uploadByteDevice), + minimumArchiveBombSize(other.minimumArchiveBombSize), autoDecompress(other.autoDecompress), pipeliningAllowed(other.pipeliningAllowed), http2Allowed(other.http2Allowed), @@ -64,7 +65,6 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest withCredentials(other.withCredentials), ssl(other.ssl), preConnect(other.preConnect), - ignoreDecompressionRatio(other.ignoreDecompressionRatio), needResendWithCredentials(other.needResendWithCredentials), redirectCount(other.redirectCount), redirectPolicy(other.redirectPolicy), @@ -93,7 +93,8 @@ bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &ot && (preConnect == other.preConnect) && (redirectPolicy == other.redirectPolicy) && (peerVerifyName == other.peerVerifyName) - && (needResendWithCredentials == other.needResendWithCredentials); + && (needResendWithCredentials == other.needResendWithCredentials) + && (minimumArchiveBombSize == other.minimumArchiveBombSize); } QByteArray QHttpNetworkRequest::methodName() const @@ -405,14 +406,14 @@ void QHttpNetworkRequest::setPeerVerifyName(const QString &peerName) d->peerVerifyName = peerName; } -bool QHttpNetworkRequest::ignoreDecompressionRatio() +qint64 QHttpNetworkRequest::minimumArchiveBombSize() const { - return d->ignoreDecompressionRatio; + return d->minimumArchiveBombSize; } -void QHttpNetworkRequest::setIgnoreDecompressionRatio(bool enabled) +void QHttpNetworkRequest::setMinimumArchiveBombSize(qint64 threshold) { - d->ignoreDecompressionRatio = enabled; + d->minimumArchiveBombSize = threshold; } QT_END_NAMESPACE diff --git a/src/network/access/qhttpnetworkrequest_p.h b/src/network/access/qhttpnetworkrequest_p.h index f18ab1c877..bcb9576474 100644 --- a/src/network/access/qhttpnetworkrequest_p.h +++ b/src/network/access/qhttpnetworkrequest_p.h @@ -150,8 +150,9 @@ public: QString peerVerifyName() const; void setPeerVerifyName(const QString &peerName); - bool ignoreDecompressionRatio(); - void setIgnoreDecompressionRatio(bool enabled); + qint64 minimumArchiveBombSize() const; + void setMinimumArchiveBombSize(qint64 threshold); + private: QSharedDataPointer<QHttpNetworkRequestPrivate> d; friend class QHttpNetworkRequestPrivate; @@ -177,6 +178,7 @@ public: QByteArray customVerb; QHttpNetworkRequest::Priority priority; mutable QNonContiguousByteDevice* uploadByteDevice; + qint64 minimumArchiveBombSize = 0; bool autoDecompress; bool pipeliningAllowed; bool http2Allowed; @@ -184,7 +186,6 @@ public: bool withCredentials; bool ssl; bool preConnect; - bool ignoreDecompressionRatio = false; bool needResendWithCredentials = false; int redirectCount; QNetworkRequest::RedirectPolicy redirectPolicy; diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index 555b609433..88f8e62df1 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -774,14 +774,7 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq if (request.attribute(QNetworkRequest::EmitAllUploadProgressSignalsAttribute).toBool()) emitAllUploadProgressSignals = true; - // For internal use/testing - auto ignoreDownloadRatio = - request.attribute(QNetworkRequest::Attribute(QNetworkRequest::User - 1)); - if (!ignoreDownloadRatio.isNull() && ignoreDownloadRatio.canConvert<QByteArray>() - && ignoreDownloadRatio.toByteArray() == "__qdecompresshelper_ignore_download_ratio") { - httpRequest.setIgnoreDecompressionRatio(true); - } - + httpRequest.setMinimumArchiveBombSize(newHttpRequest.minimumArchiveBombSize()); httpRequest.setPeerVerifyName(newHttpRequest.peerVerifyName()); // Create the HTTP thread delegate diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index ee2b9c67c3..29eefb4a70 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -441,6 +441,7 @@ public: peerVerifyName = other.peerVerifyName; #if QT_CONFIG(http) h2Configuration = other.h2Configuration; + minimumArchiveBombSize = other.minimumArchiveBombSize; #endif transferTimeout = other.transferTimeout; } @@ -455,6 +456,7 @@ public: peerVerifyName == other.peerVerifyName #if QT_CONFIG(http) && h2Configuration == other.h2Configuration + && minimumArchiveBombSize == other.minimumArchiveBombSize #endif && transferTimeout == other.transferTimeout ; @@ -470,6 +472,7 @@ public: QString peerVerifyName; #if QT_CONFIG(http) QHttp2Configuration h2Configuration; + qint64 minimumArchiveBombSize = 10ll * 1024ll * 1024ll; #endif int transferTimeout; }; @@ -896,7 +899,50 @@ void QNetworkRequest::setHttp2Configuration(const QHttp2Configuration &configura { d->h2Configuration = configuration; } + +/*! + \since 6.2 + + Returns the threshold for archive bomb checks. + + If the decompressed size of a reply is smaller than this, Qt will simply + decompress it, without further checking. + + \sa setMinimumArchiveBombSize() +*/ +qint64 QNetworkRequest::minimumArchiveBombSize() const +{ + return d->minimumArchiveBombSize; +} + +/*! + \since 6.2 + + Sets the \a threshold for archive bomb checks. + + Some supported compression algorithms can, in a tiny compressed file, encode + a spectacularly huge decompressed file. This is only possible if the + decompressed content is extremely monotonous, which is seldom the case for + real files being transmitted in good faith: files exercising such insanely + high compression ratios are typically payloads of buffer-overrun attacks, or + denial-of-service (by using up too much memory) attacks. Consequently, files + that decompress to huge sizes, particularly from tiny compressed forms, are + best rejected as suspected malware. + + If a reply's decompressed size is bigger than this threshold (by default, + 10 MiB, i.e. 10 * 1024 * 1024), Qt will check the compression ratio: if that + is unreasonably large (40:1 for GZip and Deflate, or 100:1 for Brotli and + ZStandard), the reply will be treated as an error. Setting the threshold + to \c{-1} disables this check. + + \sa minimumArchiveBombSize() +*/ +void QNetworkRequest::setMinimumArchiveBombSize(qint64 threshold) +{ + d->minimumArchiveBombSize = threshold; +} #endif // QT_CONFIG(http) || defined(Q_CLANG_QDOC) + #if QT_CONFIG(http) || defined(Q_CLANG_QDOC) || defined (Q_OS_WASM) /*! \since 5.15 diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index 655c70ad52..fe18115f75 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -179,7 +179,11 @@ public: #if QT_CONFIG(http) || defined(Q_CLANG_QDOC) QHttp2Configuration http2Configuration() const; void setHttp2Configuration(const QHttp2Configuration &configuration); + + qint64 minimumArchiveBombSize() const; + void setMinimumArchiveBombSize(qint64 threshold); #endif // QT_CONFIG(http) || defined(Q_CLANG_QDOC) + #if QT_CONFIG(http) || defined(Q_CLANG_QDOC) || defined (Q_OS_WASM) int transferTimeout() const; void setTransferTimeout(int timeout = DefaultTransferTimeoutConstant); |