https://bugs.gentoo.org/986097
--- a/src/network/access/qhttpheaderparser_p.h
+++ b/src/network/access/qhttpheaderparser_p.h
@@ -90,4 +90,12 @@
     qsizetype maxTotalHeaderSize() const { return maxTotalSize; }
 
+    qsizetype maxRawHeaderSize() const {
+        constexpr qsizetype TrailingLineEndings = 4;
+        qsizetype result;
+        if (qAddOverflow(maxTotalSize, TrailingLineEndings, &result))
+            return (std::numeric_limits<qsizetype>::max)();
+        return result;
+    }
+
     void setMaxHeaderFields(qsizetype size) {
         Q_ASSERT(size > 0);
--- a/src/network/access/qhttpnetworkreply.cpp
+++ b/src/network/access/qhttpnetworkreply.cpp
@@ -424,4 +424,12 @@
             return -1;
         }
+
+        // parseStatus() rejects reason phrases longer than MAX_REASON_PHRASE_SIZE anyway,
+        // so anything beyond that plus a generous allowance for the rest of the line
+        // can never be valid.
+        if (fragment.size() > HeaderConstants::MAX_REASON_PHRASE_SIZE + 64) {
+            fragment.clear();
+            return -1;
+        }
     } while (haveRead == 1);
 
@@ -459,4 +467,10 @@
             bytes++;
 
+            // Enforce the total header size limit while accumulating.
+            if (fragment.size() > parser.maxRawHeaderSize()) {
+                fragment.clear();
+                return -1;
+            }
+
             if (c == '\n') {
                 // check for possible header endings. As per HTTP rfc,
--- a/tests/auto/network/access/qhttpnetworkreply/tst_qhttpnetworkreply.cpp
+++ b/tests/auto/network/access/qhttpnetworkreply/tst_qhttpnetworkreply.cpp
@@ -22,4 +22,9 @@
     void parseEndOfHeader_data();
     void parseEndOfHeader();
+
+    void headerSizeLimit_data();
+    void headerSizeLimit();
+
+    void statusLineSizeLimit();
 };
 
@@ -233,4 +238,70 @@
 }
 
+void tst_QHttpNetworkReply::headerSizeLimit_data()
+{
+    QTest::addColumn<QByteArray>("headers");
+    QTest::addColumn<bool>("accepted");
+
+    // Limit used below is 64 bytes of header content.
+    const QByteArray field = "X-Field: " + QByteArray(40, 'a'); // 49 bytes
+
+    QTest::newRow("empty") << QByteArray("\r\n") << true;
+    QTest::newRow("within-limit") << QByteArray(field + "\r\n\r\n") << true;
+    QTest::newRow("within-limit-LFLF") << QByteArray(field + "\n\n") << true;
+    QTest::newRow("at-limit") << QByteArray("X-Field: " + QByteArray(55, 'a') + "\r\n\r\n")
+                              << true;
+    QTest::newRow("over-limit-terminated")
+            << QByteArray(field + "\r\n" + field + "\r\n\r\n") << false;
+    QTest::newRow("over-limit-unterminated") << QByteArray("X-Field: " + QByteArray(4096, 'a'))
+                                             << false;
+    QTest::newRow("over-limit-unterminated-lines")
+            << QByteArray(field + "\r\n").repeated(100) << false;
+}
+
+void tst_QHttpNetworkReply::headerSizeLimit()
+{
+    QFETCH(QByteArray, headers);
+    QFETCH(bool, accepted);
+
+    TestHeaderSocket socket(headers);
+    TestHeaderReply reply;
+    QHttpNetworkReplyPrivate *replyPrivate = reply.replyPrivate();
+    replyPrivate->parser.setMaxTotalHeaderSize(64);
+
+    const qint64 headerBytes = replyPrivate->readHeader(&socket);
+    if (accepted) {
+        QCOMPARE(headerBytes, qint64(headers.size()));
+        QCOMPARE(replyPrivate->state, QHttpNetworkReplyPrivate::ReadingDataState);
+    } else {
+        QCOMPARE(headerBytes, qint64(-1));
+        QCOMPARE_NE(replyPrivate->state, QHttpNetworkReplyPrivate::ReadingDataState);
+        // Nothing may be retained from the rejected block.
+        QVERIFY(replyPrivate->fragment.isEmpty());
+        // Must stop reading as soon as the limit is exceeded, not consume everything.
+        QCOMPARE_LE(socket.inputBuffer.pos(), replyPrivate->parser.maxRawHeaderSize() + 1);
+    }
+}
+
+void tst_QHttpNetworkReply::statusLineSizeLimit()
+{
+    // A status line that is never terminated must be rejected once it can
+    // no longer be valid, instead of being buffered indefinitely.
+    const QByteArray status = "HTTP/1.1 200 " + QByteArray(1024 * 1024, 'a');
+    TestHeaderSocket socket(status);
+    TestHeaderReply reply;
+    QHttpNetworkReplyPrivate *replyPrivate = reply.replyPrivate();
+
+    QCOMPARE(replyPrivate->readStatus(&socket), qint64(-1));
+    QVERIFY(replyPrivate->fragment.isEmpty());
+    QCOMPARE_LT(socket.inputBuffer.pos(), 4096);
+
+    // The longest acceptable reason phrase still parses.
+    const QByteArray okStatus = "HTTP/1.1 200 " + QByteArray(1024, 'a') + "\r\n";
+    TestHeaderSocket okSocket(okStatus);
+    TestHeaderReply okReply;
+    QCOMPARE(okReply.replyPrivate()->readStatus(&okSocket), qint64(okStatus.size()));
+    QCOMPARE(okReply.statusCode(), 200);
+}
+
 QTEST_MAIN(tst_QHttpNetworkReply)
 #include "tst_qhttpnetworkreply.moc"
