Fix a symbol with a huge size aborting QElectroTech in its preview

ElementPictureFactory::pixmap() made a pixmap of whatever width and
height the symbol file declares. A symbol whose parts span 280 000 px
(made by the GUI fuzzer in the symbol editor) asked for a ~315 GB
pixmap. Under AddressSanitizer that aborts QElectroTech; the symbol then
sat in the user collection and every later start died loading its icon.

The preview is now drawn scaled down to fit 4096 px on its longer side.
The largest symbols in the shipped collection are 3160 px, so none of
them changes. The size is also bounded before it is rounded up to a
multiple of 10, so a crafted value near INT_MAX cannot overflow.

Checked with a user collection holding one symbol declared 280000 x
280000, on ASan builds: master aborts (out of memory, exit 1) 3/3 when
the collection is expanded; with this change QElectroTech keeps running
and lists the symbol, 3/3.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-10-02 16:17:58 +13:00
parent 51b1229935
commit 1002b269ac
+18 -2
View File
@@ -135,14 +135,29 @@ QPixmap ElementPictureFactory::pixmap(const ElementsLocation &location)
{
auto doc = location.pugiXml();
//size
int w = doc.document_element().attribute("width").as_int();
int h = doc.document_element().attribute("height").as_int();
//Bounded first, so the rounding below cannot overflow on a
//crafted file.
int w = qBound(0, doc.document_element().attribute("width").as_int(), 1000000);
int h = qBound(0, doc.document_element().attribute("height").as_int(), 1000000);
while (w % 10) ++ w;
while (h % 10) ++ h;
//hotspot
int hsx = qMin(doc.document_element().attribute("hotspot_x").as_int(), w);
int hsy = qMin(doc.document_element().attribute("hotspot_y").as_int(), h);
//The size comes straight from the file. A symbol whose parts
//span hundreds of thousands of pixels would ask for a pixmap of
//hundreds of gigabytes; draw it scaled down to fit instead. The
//largest symbols in the shipped collection are about 3200 px,
//so this only changes files nobody would draw on purpose.
const int max_side = 4096;
qreal scale = 1.0;
if (w > max_side || h > max_side) {
scale = qreal(max_side) / qMax(w, h);
w = qMax(1, qRound(w * scale));
h = qMax(1, qRound(h * scale));
}
QPixmap pix(w, h);
//Element definitions almost always draw with a hardcoded black
//stroke color, on the assumption of the white diagram sheet they
@@ -157,6 +172,7 @@ QPixmap ElementPictureFactory::pixmap(const ElementsLocation &location)
QPainter painter(&pix);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
painter.scale(scale, scale);
painter.translate(hsx, hsy);
painter.drawPicture(0, 0, m_pictures_H.value(uuid));