summaryrefslogtreecommitdiffstats
path: root/src/network/access
diff options
context:
space:
mode:
authorMårten Nordheim <marten.nordheim@qt.io>2021-05-20 14:12:39 +0200
committerMårten Nordheim <marten.nordheim@qt.io>2021-05-31 17:25:20 +0200
commit69982182a394618d4f121d2938d7d76196fe78f6 (patch)
tree32d98bc55cf0fed28d3b23ed15c7d39ee3bc29db /src/network/access
parent347310eb21facbd03d2168d67d83fdbfd6f6888c (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.cpp28
-rw-r--r--src/network/access/qdecompresshelper_p.h2
-rw-r--r--src/network/access/qhttp2protocolhandler.cpp4
-rw-r--r--src/network/access/qhttpnetworkreply.cpp3
-rw-r--r--src/network/access/qhttpnetworkrequest.cpp13
-rw-r--r--src/network/access/qhttpnetworkrequest_p.h7
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp9
-rw-r--r--src/network/access/qnetworkrequest.cpp46
-rw-r--r--src/network/access/qnetworkrequest.h4
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);