summaryrefslogtreecommitdiff
path: root/src/svg/qsvghandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/svg/qsvghandler.cpp')
-rw-r--r--src/svg/qsvghandler.cpp226
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());