diff options
author | Marc Mutz <[email protected]> | 2025-03-31 14:27:10 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <[email protected]> | 2025-04-18 17:41:10 +0000 |
commit | dd934d675a10a0b06ba14dabd492e096658780fb (patch) | |
tree | 550417179e9d440f7aaee3758ccc53fbbd2ab539 | |
parent | 45e8c81c525cc86924537fe81c9ddfef79e2fee9 (diff) |
QHeaderView: fix more UB (signed integer overflow) in setOffset()
We fixed the first line of defense in
03d1e81516be9af37fa08900f9a2d88d34abc4df, but that commit didn't rule
out ndelta == INT_MIN, in which case -ndelta overflows a few lines
below.
Coverity pointed this out.
Add a check that exposes this problem to ubsan, and avoid the overflow
by using qMulOverflow<-1>()¹ and not scrolling when it overflows, but
emitting a qWarning().
¹ There's no qNegateOverflow()...
When state == QHeaderViewPrivate::ResizeSection, we assume that
everything happens on the actual screen, which has physical limits to
the setOffset() argument, and therefore these arithmetic operations
don't need to be protected.
I fully expect that this will just be a rat's tail, one we can only
hope to control by using Peppe's safe integers everywhere, at which
point we've probably blown our executable code size out of any
proportions. So leave it at this, for the time being.
Amends 03d1e81516be9af37fa08900f9a2d88d34abc4df.
Coverity-Id: 479557
Pick-to: 6.5
Change-Id: I2e31fc9be21e7d59563b67f3cd26c29dcea61b55
Reviewed-by: Axel Spoerl <[email protected]>
(cherry picked from commit 49fcac99deea390901000a74deea1c0c690b6ae2)
Reviewed-by: Qt Cherry-pick Bot <[email protected]>
(cherry picked from commit f98c49666a518df3ac182e1f4920b581d1a6bda7)
-rw-r--r-- | src/widgets/itemviews/qheaderview.cpp | 22 | ||||
-rw-r--r-- | tests/auto/widgets/itemviews/qheaderview/tst_qheaderview.cpp | 27 |
2 files changed, 46 insertions, 3 deletions
diff --git a/src/widgets/itemviews/qheaderview.cpp b/src/widgets/itemviews/qheaderview.cpp index 4c394339294..89c4617fb02 100644 --- a/src/widgets/itemviews/qheaderview.cpp +++ b/src/widgets/itemviews/qheaderview.cpp @@ -34,6 +34,14 @@ QT_BEGIN_NAMESPACE +Q_DECL_COLD_FUNCTION +static void warn_overflow(const char *caller, const char *callee, int value) +{ + qWarning("Integer argument %d causes overflow in %s when calling %s, " + "results may not be as you expect", + value, caller, callee); +} + #ifndef QT_NO_DATASTREAM QDataStream &operator<<(QDataStream &out, const QHeaderViewPrivate::SectionItem §ion) { @@ -408,10 +416,18 @@ void QHeaderView::setOffset(int newOffset) // don't overflow; this function is checked with both INT_MIN and INT_MAX... const int ndelta = q26::saturate_cast<int>(d->headerOffset - qint64{newOffset}); d->headerOffset = newOffset; - if (d->orientation == Qt::Horizontal) - d->viewport->scroll(isRightToLeft() ? -ndelta : ndelta, 0); - else + if (d->orientation == Qt::Horizontal) { + if (isLeftToRight()) { + if (int r; !qMulOverflow<-1>(ndelta, &r)) + d->viewport->scroll(r, 0); + else + warn_overflow("QHeaderView::setOffset", "QWidget::scroll", newOffset); + } else { + d->viewport->scroll(ndelta, 0); + } + } else { d->viewport->scroll(0, ndelta); + } if (d->state == QHeaderViewPrivate::ResizeSection && !d->preventCursorChangeInSetOffset) { QPoint cursorPos = QCursor::pos(); if (d->orientation == Qt::Horizontal) diff --git a/tests/auto/widgets/itemviews/qheaderview/tst_qheaderview.cpp b/tests/auto/widgets/itemviews/qheaderview/tst_qheaderview.cpp index b8b750d13e3..ddba11fb5fd 100644 --- a/tests/auto/widgets/itemviews/qheaderview/tst_qheaderview.cpp +++ b/tests/auto/widgets/itemviews/qheaderview/tst_qheaderview.cpp @@ -381,6 +381,22 @@ public: bool m_bMultiLine = false; }; +static Qt::LayoutDirection otherLayoutDirection(Qt::LayoutDirection current) +{ + switch (current) { + case Qt::LayoutDirection::LeftToRight: return Qt::LayoutDirection::RightToLeft; + case Qt::LayoutDirection::RightToLeft: return Qt::LayoutDirection::LeftToRight; + case Qt::LayoutDirection::LayoutDirectionAuto: + ; + } + Q_UNREACHABLE_RETURN(Qt::LayoutDirection::LayoutDirectionAuto); +} + +static void swapLayoutDirection(QWidget &w) +{ + w.setLayoutDirection(otherLayoutDirection(w.layoutDirection())); +} + // Testing get/set functions void tst_QHeaderView::getSetCheck() { @@ -426,9 +442,20 @@ void tst_QHeaderView::getSetCheck() QCOMPARE(0, obj1.offset()); obj1.setOffset(std::numeric_limits<int>::min()); QCOMPARE(std::numeric_limits<int>::min(), obj1.offset()); + QTest::ignoreMessage(QtWarningMsg, // one of the INT_MAX will hit this; not necessarily the first one + "Integer argument 2147483647 causes overflow in QHeaderView::setOffset " + "when calling QWidget::scroll, results may not be as you expect"); obj1.setOffset(std::numeric_limits<int>::max()); QCOMPARE(std::numeric_limits<int>::max(), obj1.offset()); + // and again with RTL (or LTR, if it was RTL before): + swapLayoutDirection(obj1); + obj1.setOffset(0); + QCOMPARE(0, obj1.offset()); + obj1.setOffset(std::numeric_limits<int>::min()); + QCOMPARE(std::numeric_limits<int>::min(), obj1.offset()); + obj1.setOffset(std::numeric_limits<int>::max()); + QCOMPARE(std::numeric_limits<int>::max(), obj1.offset()); } tst_QHeaderView::tst_QHeaderView() |