|
| 1 | +Description: rich text: limit size of text object |
| 2 | + When we draw a text object, we need to store this in RAM |
| 3 | + since the QTextObjectInterface is QPainter-based. This |
| 4 | + could lead to over-allocation if the text object size |
| 5 | + was set to be very large. We use the existing image IO |
| 6 | + infrastructure for making sure allocations are within |
| 7 | + reasonable (and configurable) limits. |
| 8 | +Origin: upstream, https://code.qt.io/cgit/qt/qtdeclarative.git/commit/?id=144ce34e846b3f73 |
| 9 | + Backported to 5.15 by Dmitry Shachnev: validate allocation manually |
| 10 | + instead of using QImageIOHandler::allocateImage(). |
| 11 | +Last-Update: 2025-12-11 |
| 12 | + |
| 13 | +Upstream Patch Reference: https://salsa.debian.org/qt-kde-team/qt/qtdeclarative/-/raw/debian/5.15.17+dfsg-4/debian/patches/CVE-2025-12385-part2.patch?ref_type=tags and https://codereview.qt-project.org/changes/qt%2Fqtdeclarative~687239/revisions/7/patch?download&raw |
| 14 | + |
| 15 | +--- |
| 16 | + src/quick/items/qquicktextdocument.cpp | 4 +- |
| 17 | + src/quick/items/qquicktextnodeengine.cpp | 17 ++-- |
| 18 | + src/quick/util/qquickstyledtext.cpp | 21 +++- |
| 19 | + .../auto/quick/qquicktext/tst_qquicktext.cpp | 97 +++++++++++++++++++ |
| 20 | + 4 files changed, 129 insertions(+), 10 deletions(-) |
| 21 | + |
| 22 | +diff --git a/src/quick/items/qquicktextdocument.cpp b/src/quick/items/qquicktextdocument.cpp |
| 23 | +index 06ac5804..6610d1a6 100644 |
| 24 | +--- a/src/quick/items/qquicktextdocument.cpp |
| 25 | ++++ b/src/quick/items/qquicktextdocument.cpp |
| 26 | +@@ -138,9 +138,9 @@ QSizeF QQuickTextDocumentWithImageResources::intrinsicSize( |
| 27 | + if (format.isImageFormat()) { |
| 28 | + QTextImageFormat imageFormat = format.toImageFormat(); |
| 29 | + |
| 30 | +- const int width = qRound(imageFormat.width()); |
| 31 | ++ const int width = qRound(qBound(qreal(INT_MIN), imageFormat.width(), qreal(INT_MAX))); |
| 32 | + const bool hasWidth = imageFormat.hasProperty(QTextFormat::ImageWidth) && width > 0; |
| 33 | +- const int height = qRound(imageFormat.height()); |
| 34 | ++ const int height = qRound(qBound(qreal(INT_MIN), imageFormat.height(), qreal(INT_MAX))); |
| 35 | + const bool hasHeight = imageFormat.hasProperty(QTextFormat::ImageHeight) && height > 0; |
| 36 | + |
| 37 | + QSizeF size(width, height); |
| 38 | +diff --git a/src/quick/items/qquicktextnodeengine.cpp b/src/quick/items/qquicktextnodeengine.cpp |
| 39 | +index 5a4ef2b6..1bbf3778 100644 |
| 40 | +--- a/src/quick/items/qquicktextnodeengine.cpp |
| 41 | ++++ b/src/quick/items/qquicktextnodeengine.cpp |
| 42 | +@@ -47,6 +47,7 @@ |
| 43 | + #include <QtGui/qtextobject.h> |
| 44 | + #include <QtGui/qtexttable.h> |
| 45 | + #include <QtGui/qtextlist.h> |
| 46 | ++#include <QtGui/private/qimage_p.h> |
| 47 | + |
| 48 | + #include <private/qquicktext_p.h> |
| 49 | + #include <private/qquicktextdocument_p.h> |
| 50 | +@@ -467,12 +468,16 @@ void QQuickTextNodeEngine::addTextObject(const QTextBlock &block, const QPointF |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | +- if (image.isNull()) { |
| 55 | +- image = QImage(size.toSize(), QImage::Format_ARGB32_Premultiplied); |
| 56 | +- image.fill(Qt::transparent); |
| 57 | +- { |
| 58 | +- QPainter painter(&image); |
| 59 | +- handler->drawObject(&painter, image.rect(), textDocument, pos, format); |
| 60 | ++ if (image.isNull() && !size.isEmpty()) { |
| 61 | ++ QImageData::ImageSizeParameters szp = |
| 62 | ++ QImageData::calculateImageParameters(size.width(), size.height(), 32); |
| 63 | ++ if (szp.isValid() && szp.totalSize <= 268435456L /* 256 MB */) { |
| 64 | ++ image = QImage(size.toSize(), QImage::Format_ARGB32_Premultiplied); |
| 65 | ++ image.fill(Qt::transparent); |
| 66 | ++ { |
| 67 | ++ QPainter painter(&image); |
| 68 | ++ handler->drawObject(&painter, image.rect(), textDocument, pos, format); |
| 69 | ++ } |
| 70 | + } |
| 71 | + } |
| 72 | + |
| 73 | +diff --git a/src/quick/util/qquickstyledtext.cpp b/src/quick/util/qquickstyledtext.cpp |
| 74 | +index 5e1aaf12..55c16944 100644 |
| 75 | +--- a/src/quick/util/qquickstyledtext.cpp |
| 76 | ++++ b/src/quick/util/qquickstyledtext.cpp |
| 77 | +@@ -45,6 +45,13 @@ |
| 78 | + #include <qmath.h> |
| 79 | + #include "qquickstyledtext_p.h" |
| 80 | + #include <QQmlContext> |
| 81 | ++#include <QtGui/private/qoutlinemapper_p.h> |
| 82 | ++ |
| 83 | ++#ifndef QQUICKSTYLEDPARSER_COORD_LIMIT |
| 84 | ++# define QQUICKSTYLEDPARSER_COORD_LIMIT QT_RASTER_COORD_LIMIT |
| 85 | ++#endif |
| 86 | ++ |
| 87 | ++Q_LOGGING_CATEGORY(lcStyledText, "qt.quick.styledtext") |
| 88 | + |
| 89 | + /* |
| 90 | + QQuickStyledText supports few tags: |
| 91 | +@@ -688,9 +695,19 @@ void QQuickStyledTextPrivate::parseImageAttributes(const QChar *&ch, const QStri |
| 92 | + if (attr.first == QLatin1String("src")) { |
| 93 | + image->url = QUrl(attr.second.toString()); |
| 94 | + } else if (attr.first == QLatin1String("width")) { |
| 95 | +- image->size.setWidth(attr.second.toString().toInt()); |
| 96 | ++ bool ok; |
| 97 | ++ int v = attr.second.toString().toInt(&ok); |
| 98 | ++ if (ok && v <= QQUICKSTYLEDPARSER_COORD_LIMIT) |
| 99 | ++ image->size.setWidth(v); |
| 100 | ++ else |
| 101 | ++ qCWarning(lcStyledText) << "Invalid width provided for <img>"; |
| 102 | + } else if (attr.first == QLatin1String("height")) { |
| 103 | +- image->size.setHeight(attr.second.toString().toInt()); |
| 104 | ++ bool ok; |
| 105 | ++ int v = attr.second.toString().toInt(&ok); |
| 106 | ++ if (ok && v <= QQUICKSTYLEDPARSER_COORD_LIMIT) |
| 107 | ++ image->size.setHeight(v); |
| 108 | ++ else |
| 109 | ++ qCWarning(lcStyledText) << "Invalid height provided for <img>"; |
| 110 | + } else if (attr.first == QLatin1String("align")) { |
| 111 | + if (attr.second.toString() == QLatin1String("top")) { |
| 112 | + image->align = QQuickStyledTextImgTag::Top; |
| 113 | +diff --git a/tests/auto/quick/qquicktext/tst_qquicktext.cpp b/tests/auto/quick/qquicktext/tst_qquicktext.cpp |
| 114 | +index eafa6cb0..addabadf 100644 |
| 115 | +--- a/tests/auto/quick/qquicktext/tst_qquicktext.cpp |
| 116 | ++++ b/tests/auto/quick/qquicktext/tst_qquicktext.cpp |
| 117 | +@@ -127,6 +127,8 @@ private slots: |
| 118 | + void imgTagsElide(); |
| 119 | + void imgTagsUpdates(); |
| 120 | + void imgTagsError(); |
| 121 | ++ void imgSize_data(); |
| 122 | ++ void imgSize(); |
| 123 | + void fontSizeMode_data(); |
| 124 | + void fontSizeMode(); |
| 125 | + void fontSizeModeMultiline_data(); |
| 126 | +@@ -3126,6 +3128,101 @@ void tst_qquicktext::imgTagsError() |
| 127 | + delete textObject; |
| 128 | + } |
| 129 | + |
| 130 | ++void tst_qquicktext::imgSize_data() |
| 131 | ++{ |
| 132 | ++ QTest::addColumn<QString>("url"); |
| 133 | ++ QTest::addColumn<qint64>("width"); |
| 134 | ++ QTest::addColumn<qint64>("height"); |
| 135 | ++ QTest::addColumn<QQuickText::TextFormat>("format"); |
| 136 | ++ |
| 137 | ++ QTest::newRow("negative (styled text)") << QStringLiteral("images/starfish_2.png") |
| 138 | ++ << qint64(-0x7FFFFF) |
| 139 | ++ << qint64(-0x7FFFFF) |
| 140 | ++ << QQuickText::StyledText; |
| 141 | ++ QTest::newRow("negative (rich text)") << QStringLiteral("images/starfish_2.png") |
| 142 | ++ << qint64(-0x7FFFFF) |
| 143 | ++ << qint64(-0x7FFFFF) |
| 144 | ++ << QQuickText::RichText; |
| 145 | ++ QTest::newRow("large (styled text)") << QStringLiteral("images/starfish_2.png") |
| 146 | ++ << qint64(0x7FFFFF) |
| 147 | ++ << qint64(0x7FFFFF) |
| 148 | ++ << QQuickText::StyledText; |
| 149 | ++ QTest::newRow("large (right text)") << QStringLiteral("images/starfish_2.png") |
| 150 | ++ << qint64(0x7FFFFF) |
| 151 | ++ << qint64(0x7FFFFF) |
| 152 | ++ << QQuickText::RichText; |
| 153 | ++ QTest::newRow("medium (styled text)") << QStringLiteral("images/starfish_2.png") |
| 154 | ++ << qint64(0x10000) |
| 155 | ++ << qint64(0x10000) |
| 156 | ++ << QQuickText::StyledText; |
| 157 | ++ QTest::newRow("medium (right text)") << QStringLiteral("images/starfish_2.png") |
| 158 | ++ << qint64(0x10000) |
| 159 | ++ << qint64(0x10000) |
| 160 | ++ << QQuickText::RichText; |
| 161 | ++ QTest::newRow("out-of-bounds (styled text)") << QStringLiteral("images/starfish_2.png") |
| 162 | ++ << (qint64(INT_MAX) + 1) |
| 163 | ++ << (qint64(INT_MAX) + 1) |
| 164 | ++ << QQuickText::StyledText; |
| 165 | ++ QTest::newRow("out-of-bounds (rich text)") << QStringLiteral("images/starfish_2.png") |
| 166 | ++ << (qint64(INT_MAX) + 1) |
| 167 | ++ << (qint64(INT_MAX) + 1) |
| 168 | ++ << QQuickText::RichText; |
| 169 | ++ QTest::newRow("negative out-of-bounds (styled text)") << QStringLiteral("images/starfish_2.png") |
| 170 | ++ << (qint64(INT_MIN) - 1) |
| 171 | ++ << (qint64(INT_MIN) - 1) |
| 172 | ++ << QQuickText::StyledText; |
| 173 | ++ QTest::newRow("negative out-of-bounds (rich text)") << QStringLiteral("images/starfish_2.png") |
| 174 | ++ << (qint64(INT_MIN) - 1) |
| 175 | ++ << (qint64(INT_MIN) - 1) |
| 176 | ++ << QQuickText::RichText; |
| 177 | ++ QTest::newRow("large non-existent (styled text)") << QStringLiteral("a") |
| 178 | ++ << qint64(0x7FFFFF) |
| 179 | ++ << qint64(0x7FFFFF) |
| 180 | ++ << QQuickText::StyledText; |
| 181 | ++ QTest::newRow("medium non-existent (styled text)") << QStringLiteral("a") |
| 182 | ++ << qint64(0x10000) |
| 183 | ++ << qint64(0x10000) |
| 184 | ++ << QQuickText::StyledText; |
| 185 | ++ QTest::newRow("out-of-bounds non-existent (styled text)") << QStringLiteral("a") |
| 186 | ++ << (qint64(INT_MAX) + 1) |
| 187 | ++ << (qint64(INT_MAX) + 1) |
| 188 | ++ << QQuickText::StyledText; |
| 189 | ++ QTest::newRow("large non-existent (rich text)") << QStringLiteral("a") |
| 190 | ++ << qint64(0x7FFFFF) |
| 191 | ++ << qint64(0x7FFFFF) |
| 192 | ++ << QQuickText::RichText; |
| 193 | ++ QTest::newRow("medium non-existent (rich text)") << QStringLiteral("a") |
| 194 | ++ << qint64(0x10000) |
| 195 | ++ << qint64(0x10000) |
| 196 | ++ << QQuickText::RichText; |
| 197 | ++} |
| 198 | ++ |
| 199 | ++void tst_qquicktext::imgSize() |
| 200 | ++{ |
| 201 | ++ QFETCH(QString, url); |
| 202 | ++ QFETCH(qint64, width); |
| 203 | ++ QFETCH(qint64, height); |
| 204 | ++ QFETCH(QQuickText::TextFormat, format); |
| 205 | ++ |
| 206 | ++ // Reusing imgTagsUpdates.qml here, since it is just an empty Text component |
| 207 | ++ QScopedPointer<QQuickView> window(createView(testFile("imgTagsUpdates.qml"))); |
| 208 | ++ window->show(); |
| 209 | ++ QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
| 210 | ++ |
| 211 | ++ QScopedPointer<QQuickText> myText(window->rootObject()->findChild<QQuickText*>("myText")); |
| 212 | ++ QVERIFY(myText); |
| 213 | ++ |
| 214 | ++ myText->setTextFormat(format); |
| 215 | ++ |
| 216 | ++ QString imgStr = QStringLiteral("<img src=\"%1\" width=\"%2\" height=\"%3\" />") |
| 217 | ++ .arg(url) |
| 218 | ++ .arg(width) |
| 219 | ++ .arg(height); |
| 220 | ++ myText->setText(imgStr); |
| 221 | ++ |
| 222 | ++ QVERIFY(QQuickTest::qWaitForItemPolished(myText.data())); |
| 223 | ++} |
| 224 | ++ |
| 225 | + void tst_qquicktext::fontSizeMode_data() |
| 226 | + { |
| 227 | + QTest::addColumn<QString>("text"); |
| 228 | +-- |
| 229 | +2.45.4 |
| 230 | + |
0 commit comments