diff options
Diffstat (limited to 'src/svg/qsvghandler.cpp')
-rw-r--r-- | src/svg/qsvghandler.cpp | 226 |
1 files changed, 127 insertions, 99 deletions
diff --git a/src/svg/qsvghandler.cpp b/src/svg/qsvghandler.cpp index 7205cda..b2227b6 100644 --- a/src/svg/qsvghandler.cpp +++ b/src/svg/qsvghandler.cpp @@ -65,6 +65,7 @@ #include "private/qmath_p.h" #include "float.h" +#include <cmath> QT_BEGIN_NAMESPACE @@ -105,7 +106,7 @@ static inline QByteArray msgCouldNotResolveProperty(const QString &id, const QXm // ======== duplicated from qcolor_p -static inline int qsvg_h2i(char hex) +static inline int qsvg_h2i(char hex, bool *ok = nullptr) { if (hex >= '0' && hex <= '9') return hex - '0'; @@ -113,18 +114,20 @@ static inline int qsvg_h2i(char hex) return hex - 'a' + 10; if (hex >= 'A' && hex <= 'F') return hex - 'A' + 10; + if (ok) + *ok = false; return -1; } -static inline int qsvg_hex2int(const char *s) +static inline int qsvg_hex2int(const char *s, bool *ok = nullptr) { - return (qsvg_h2i(s[0]) << 4) | qsvg_h2i(s[1]); + return (qsvg_h2i(s[0], ok) * 16) | qsvg_h2i(s[1], ok); } -static inline int qsvg_hex2int(char s) +static inline int qsvg_hex2int(char s, bool *ok = nullptr) { - int h = qsvg_h2i(s); - return (h << 4) | h; + int h = qsvg_h2i(s, ok); + return (h * 16) | h; } bool qsvg_get_hex_rgb(const char *name, QRgb *rgb) @@ -134,26 +137,27 @@ bool qsvg_get_hex_rgb(const char *name, QRgb *rgb) name++; int len = qstrlen(name); int r, g, b; + bool ok = true; if (len == 12) { - r = qsvg_hex2int(name); - g = qsvg_hex2int(name + 4); - b = qsvg_hex2int(name + 8); + r = qsvg_hex2int(name, &ok); + g = qsvg_hex2int(name + 4, &ok); + b = qsvg_hex2int(name + 8, &ok); } else if (len == 9) { - r = qsvg_hex2int(name); - g = qsvg_hex2int(name + 3); - b = qsvg_hex2int(name + 6); + r = qsvg_hex2int(name, &ok); + g = qsvg_hex2int(name + 3, &ok); + b = qsvg_hex2int(name + 6, &ok); } else if (len == 6) { - r = qsvg_hex2int(name); - g = qsvg_hex2int(name + 2); - b = qsvg_hex2int(name + 4); + r = qsvg_hex2int(name, &ok); + g = qsvg_hex2int(name + 2, &ok); + b = qsvg_hex2int(name + 4, &ok); } else if (len == 3) { - r = qsvg_hex2int(name[0]); - g = qsvg_hex2int(name[1]); - b = qsvg_hex2int(name[2]); + r = qsvg_hex2int(name[0], &ok); + g = qsvg_hex2int(name[1], &ok); + b = qsvg_hex2int(name[2], &ok); } else { r = g = b = -1; } - if ((uint)r > 255 || (uint)g > 255 || (uint)b > 255) { + if ((uint)r > 255 || (uint)g > 255 || (uint)b > 255 || !ok) { *rgb = 0; return false; } @@ -669,6 +673,9 @@ static qreal toDouble(const QChar *&str) val = -val; } else { val = QByteArray::fromRawData(temp, pos).toDouble(); + // Do not tolerate values too wild to be represented normally by floats + if (qFpClassify(float(val)) != FP_NORMAL) + val = 0; } return val; @@ -721,15 +728,25 @@ static QVector<qreal> parseNumbersList(const QChar *&str) return points; } -static inline void parseNumbersArray(const QChar *&str, QVarLengthArray<qreal, 8> &points) +static inline void parseNumbersArray(const QChar *&str, QVarLengthArray<qreal, 8> &points, + const char *pattern = nullptr) { + const size_t patternLen = qstrlen(pattern); while (str->isSpace()) ++str; while (isDigit(str->unicode()) || *str == QLatin1Char('-') || *str == QLatin1Char('+') || *str == QLatin1Char('.')) { - points.append(toDouble(str)); + if (patternLen && pattern[points.size() % patternLen] == 'f') { + // flag expected, may only be 0 or 1 + if (*str != QLatin1Char('0') && *str != QLatin1Char('1')) + return; + points.append(*str == QLatin1Char('0') ? 0.0 : 1.0); + ++str; + } else { + points.append(toDouble(str)); + } while (str->isSpace()) ++str; @@ -1377,7 +1394,8 @@ static void parseFont(QSvgNode *node, break; case FontSizeValue: { QSvgHandler::LengthType dummy; // should always be pixel size - fontStyle->setSize(parseLength(attributes.fontSize, dummy, handler)); + fontStyle->setSize(qMin(parseLength(attributes.fontSize, dummy, handler), + qreal(0xffff))); } break; default: @@ -1530,13 +1548,19 @@ static void pathArc(QPainterPath &path, qreal y, qreal curx, qreal cury) { + const qreal Pr1 = rx * rx; + const qreal Pr2 = ry * ry; + + if (!Pr1 || !Pr2) + return; + qreal sin_th, cos_th; qreal a00, a01, a10, a11; qreal x0, y0, x1, y1, xc, yc; qreal d, sfactor, sfactor_sq; qreal th0, th1, th_arc; int i, n_segs; - qreal dx, dy, dx1, dy1, Pr1, Pr2, Px, Py, check; + qreal dx, dy, dx1, dy1, Px, Py, check; rx = qAbs(rx); ry = qAbs(ry); @@ -1548,8 +1572,6 @@ static void pathArc(QPainterPath &path, dy = (cury - y) / 2.0; dx1 = cos_th * dx + sin_th * dy; dy1 = -sin_th * dx + cos_th * dy; - Pr1 = rx * rx; - Pr2 = ry * ry; Px = dx1 * dx1; Py = dy1 * dy1; /* Spec : check if radii are large enough */ @@ -1573,6 +1595,8 @@ static void pathArc(QPainterPath &path, The arc fits a unit-radius circle in this space. */ d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0); + if (!d) + return; sfactor_sq = 1.0 / d - 0.25; if (sfactor_sq < 0) sfactor_sq = 0; sfactor = qSqrt(sfactor_sq); @@ -1602,6 +1626,7 @@ static void pathArc(QPainterPath &path, static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) { + const int maxElementCount = 0x7fff; // Assume file corruption if more path elements than this qreal x0 = 0, y0 = 0; // starting point qreal x = 0, y = 0; // current point char lastMode = 0; @@ -1609,28 +1634,31 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) const QChar *str = dataStr.constData(); const QChar *end = str + dataStr.size(); - while (str != end) { + bool ok = true; + while (ok && str != end) { while (str->isSpace() && (str + 1) != end) ++str; QChar pathElem = *str; ++str; QChar endc = *end; *const_cast<QChar *>(end) = 0; // parseNumbersArray requires 0-termination that QStringRef cannot guarantee + const char *pattern = nullptr; + if (pathElem == QLatin1Char('a') || pathElem == QLatin1Char('A')) + pattern = "rrrffrr"; QVarLengthArray<qreal, 8> arg; - parseNumbersArray(str, arg); + parseNumbersArray(str, arg, pattern); *const_cast<QChar *>(end) = endc; if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z')) arg.append(0);//dummy const qreal *num = arg.constData(); int count = arg.count(); - while (count > 0) { + while (ok && count > 0) { qreal offsetX = x; // correction offsets qreal offsetY = y; // for relative commands switch (pathElem.unicode()) { case 'm': { if (count < 2) { - num++; - count--; + ok = false; break; } x = x0 = num[0] + offsetX; @@ -1647,8 +1675,7 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) break; case 'M': { if (count < 2) { - num++; - count--; + ok = false; break; } x = x0 = num[0]; @@ -1674,8 +1701,7 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) break; case 'l': { if (count < 2) { - num++; - count--; + ok = false; break; } x = num[0] + offsetX; @@ -1688,8 +1714,7 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) break; case 'L': { if (count < 2) { - num++; - count--; + ok = false; break; } x = num[0]; @@ -1729,8 +1754,7 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) break; case 'c': { if (count < 6) { - num += count; - count = 0; + ok = false; break; } QPointF c1(num[0] + offsetX, num[1] + offsetY); @@ -1746,8 +1770,7 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) } case 'C': { if (count < 6) { - num += count; - count = 0; + ok = false; break; } QPointF c1(num[0], num[1]); @@ -1763,8 +1786,7 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) } case 's': { if (count < 4) { - num += count; - count = 0; + ok = false; break; } QPointF c1; @@ -1785,8 +1807,7 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) } case 'S': { if (count < 4) { - num += count; - count = 0; + ok = false; break; } QPointF c1; @@ -1807,8 +1828,7 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) } case 'q': { if (count < 4) { - num += count; - count = 0; + ok = false; break; } QPointF c(num[0] + offsetX, num[1] + offsetY); @@ -1823,8 +1843,7 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) } case 'Q': { if (count < 4) { - num += count; - count = 0; + ok = false; break; } QPointF c(num[0], num[1]); @@ -1839,8 +1858,7 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) } case 't': { if (count < 2) { - num += count; - count = 0; + ok = false; break; } QPointF e(num[0] + offsetX, num[1] + offsetY); @@ -1860,8 +1878,7 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) } case 'T': { if (count < 2) { - num += count; - count = 0; + ok = false; break; } QPointF e(num[0], num[1]); @@ -1881,8 +1898,7 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) } case 'a': { if (count < 7) { - num += count; - count = 0; + ok = false; break; } qreal rx = (*num++); @@ -1904,8 +1920,7 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) break; case 'A': { if (count < 7) { - num += count; - count = 0; + ok = false; break; } qreal rx = (*num++); @@ -1926,12 +1941,15 @@ static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path) } break; default: - return false; + ok = false; + break; } lastMode = pathElem.toLatin1(); + if (path.elementCount() > maxElementCount) + ok = false; } } - return true; + return ok; } static bool parseStyle(QSvgNode *node, @@ -2345,6 +2363,28 @@ static bool parseAnimateNode(QSvgNode *parent, return true; } +static int parseClockValue(const QString &instr, bool *ok) +{ + QStringRef str(&instr); + int res = 0; + int ms = 1000; + str = str.trimmed(); + if (str.endsWith(QLatin1String("ms"))) { + str.chop(2); + ms = 1; + } else if (str.endsWith(QLatin1String("s"))) { + str.chop(1); + } + double val = ms * toDouble(str, ok); + if (ok) { + if (val > std::numeric_limits<int>::min() && val < std::numeric_limits<int>::max()) + res = static_cast<int>(val); + else + *ok = false; + } + return res; +} + static bool parseAnimateColorNode(QSvgNode *parent, const QXmlStreamAttributes &attributes, QSvgHandler *handler) @@ -2378,23 +2418,13 @@ static bool parseAnimateColorNode(QSvgNode *parent, } } - int ms = 1000; - beginStr = beginStr.trimmed(); - if (beginStr.endsWith(QLatin1String("ms"))) { - beginStr.chop(2); - ms = 1; - } else if (beginStr.endsWith(QLatin1String("s"))) { - beginStr.chop(1); - } - durStr = durStr.trimmed(); - if (durStr.endsWith(QLatin1String("ms"))) { - durStr.chop(2); - ms = 1; - } else if (durStr.endsWith(QLatin1String("s"))) { - durStr.chop(1); - } - int begin = static_cast<int>(toDouble(beginStr) * ms); - int end = static_cast<int>((toDouble(durStr) + begin) * ms); + bool ok = true; + int begin = parseClockValue(beginStr, &ok); + if (!ok) + return false; + int end = begin + parseClockValue(durStr, &ok); + if (!ok || end <= begin) + return false; QSvgAnimateColor *anim = new QSvgAnimateColor(begin, end, 0); anim->setArgs((targetStr == QLatin1String("fill")), colors); @@ -2484,24 +2514,13 @@ static bool parseAnimateTransformNode(QSvgNode *parent, } } - int ms = 1000; - beginStr = beginStr.trimmed(); - if (beginStr.endsWith(QLatin1String("ms"))) { - beginStr.chop(2); - ms = 1; - } else if (beginStr.endsWith(QLatin1String("s"))) { - beginStr.chop(1); - } - int begin = static_cast<int>(toDouble(beginStr) * ms); - durStr = durStr.trimmed(); - if (durStr.endsWith(QLatin1String("ms"))) { - durStr.chop(2); - ms = 1; - } else if (durStr.endsWith(QLatin1String("s"))) { - durStr.chop(1); - ms = 1000; - } - int end = static_cast<int>(toDouble(durStr)*ms) + begin; + bool ok = true; + int begin = parseClockValue(beginStr, &ok); + if (!ok) + return false; + int end = begin + parseClockValue(durStr, &ok); + if (!ok || end <= begin) + return false; QSvgAnimateTransform::TransformType type = QSvgAnimateTransform::Empty; if (typeStr == QLatin1String("translate")) { @@ -2967,8 +2986,8 @@ static QSvgNode *createPathNode(QSvgNode *parent, QPainterPath qpath; qpath.setFillRule(Qt::WindingFill); - //XXX do error handling - parsePathDataFast(data, qpath); + if (!parsePathDataFast(data, qpath)) + qCWarning(lcSvgHandler, "Invalid path data; path truncated."); QSvgNode *path = new QSvgPath(parent, qpath); return path; @@ -3034,6 +3053,8 @@ static QSvgStyleProperty *createRadialGradientNode(QSvgNode *node, ncy = toDouble(cy); if (!r.isEmpty()) nr = toDouble(r); + if (nr < 0.5) + nr = 0.5; qreal nfx = ncx; if (!fx.isEmpty()) @@ -3634,6 +3655,10 @@ void QSvgHandler::init() parse(); } +// Having too many unfinished elements will cause a stack overflow +// in the dtor of QSvgTinyDocument, see oss-fuzz issue 24000. +static const int unfinishedElementsLimit = 2048; + void QSvgHandler::parse() { xml->setNamespaceProcessing(false); @@ -3642,6 +3667,7 @@ void QSvgHandler::parse() m_inStyle = false; #endif bool done = false; + int remainingUnfinishedElements = unfinishedElementsLimit; while (!xml->atEnd() && !done) { switch (xml->readNext()) { case QXmlStreamReader::StartElement: @@ -3653,7 +3679,10 @@ void QSvgHandler::parse() // namespaceUri is empty. The only possible strategy at // this point is to do what everyone else seems to do and // ignore the reported namespaceUri completely. - if (!startElement(xml->name().toString(), xml->attributes())) { + if (remainingUnfinishedElements + && startElement(xml->name().toString(), xml->attributes())) { + --remainingUnfinishedElements; + } else { delete m_doc; m_doc = 0; return; @@ -3661,9 +3690,8 @@ void QSvgHandler::parse() break; case QXmlStreamReader::EndElement: endElement(xml->name()); - // if we are using somebody else's qxmlstreamreader - // we should not read until the end of the stream - done = !m_ownsReader && (xml->name() == QLatin1String("svg")); + ++remainingUnfinishedElements; + done = (xml->name() == QLatin1String("svg")); break; case QXmlStreamReader::Characters: characters(xml->text()); |