Make QHeaderView restore state from different stream versions

If restoring a QHeaderView state from a data stream with version Qt_5_0,
check alignment and resize mode properites for out-of-bound values.

If out of bounds, try QDataStream version Qt_6_0, which is used by KDE
apps compiled with 5.15.2 or 6.2.3.

QFileDialog stores settings in the same settings file across different
Qt versions, using different QDataStream versions. That makes
QFileDialog vulnerable to the issue (QTBUG-104962). A respective auto
test is added with this patch.

Fixes: QTBUG-104962
Pick-to: 6.4 6.3 6.2
Task-number: QTBUG-104425
Change-Id: I666207fca7ab837ad27a247e504a40757ee8afab
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Axel Spoerl 2022-08-05 08:33:56 +02:00 committed by Volker Hilsheimer
parent e38c7618be
commit 854cb55987
2 changed files with 71 additions and 15 deletions

View File

@ -1762,22 +1762,27 @@ bool QHeaderView::restoreState(const QByteArray &state)
Q_D(QHeaderView);
if (state.isEmpty())
return false;
QByteArray data = state;
QDataStream stream(&data, QIODevice::ReadOnly);
stream.setVersion(QDataStream::Qt_5_0);
int marker;
int ver;
stream >> marker;
stream >> ver;
if (stream.status() != QDataStream::Ok
|| marker != QHeaderViewPrivate::VersionMarker
|| ver != 0) // current version is 0
return false;
if (d->read(stream)) {
emit sortIndicatorChanged(d->sortIndicatorSection, d->sortIndicatorOrder );
d->viewport->update();
return true;
for (const auto dataStreamVersion : {QDataStream::Qt_5_0, QDataStream::Qt_6_0}) {
QByteArray data = state;
QDataStream stream(&data, QIODevice::ReadOnly);
stream.setVersion(dataStreamVersion);
int marker;
int ver;
stream >> marker;
stream >> ver;
if (stream.status() != QDataStream::Ok
|| marker != QHeaderViewPrivate::VersionMarker
|| ver != 0) { // current version is 0
return false;
}
if (d->read(stream)) {
emit sortIndicatorChanged(d->sortIndicatorSection, d->sortIndicatorOrder );
d->viewport->update();
return true;
}
}
return false;
}
@ -4131,6 +4136,15 @@ bool QHeaderViewPrivate::read(QDataStream &in)
in >> global;
// Check parameter consistency
// Global orientation out of bounds?
if (global < 0 || global > QHeaderView::ResizeToContents)
return false;
// Alignment out of bounds?
if (align < 0 || align > Qt::AlignVertical_Mask)
return false;
in >> sectionItemsIn;
// In Qt4 we had a vector of spans where one span could hold information on more sections.
// Now we have an itemvector where one items contains information about one section

View File

@ -106,6 +106,10 @@ private slots:
void dontShowCompleterOnRoot();
void nameFilterParsing_data();
void nameFilterParsing();
#if QT_CONFIG(settings)
void settingsCompatibility_data();
void settingsCompatibility();
#endif
private:
void cleanupSettingsFile();
@ -446,6 +450,44 @@ void tst_QFileDialog2::task180459_lastDirectory()
delete dlg;
}
#if QT_CONFIG(settings)
void tst_QFileDialog2::settingsCompatibility_data()
{
QTest::addColumn<QString>("qtVersion");
QTest::addColumn<QDataStream::Version>("dsVersion");
QTest::newRow("6.2.3") << "6.2.3" << QDataStream::Qt_6_0;
QTest::newRow("6.5") << "6.5" << QDataStream::Qt_5_0;
QTest::newRow("15.5.2") << "5.15.2" << QDataStream::Qt_5_15;
QTest::newRow("15.5.9") << "5.15.9" << QDataStream::Qt_5_15;
}
void tst_QFileDialog2::settingsCompatibility()
{
static const QByteArray ba32 = QByteArrayLiteral("\x00\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xF7\x00\x00\x00\x04\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00""d\xFF\xFF\xFF\xFF\x00\x00\x00\x81\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x01\t\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00>\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00""B\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00n\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03\xE8\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00");
static const QByteArray ba64 = QByteArrayLiteral("\x00\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xF7\x00\x00\x00\x04\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00""d\xFF\xFF\xFF\xFF\x00\x00\x00\x81\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x01\t\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00>\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00""B\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00n\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03\xE8\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00");
QFETCH(QString, qtVersion);
QFETCH(QDataStream::Version, dsVersion);
// Create a header view, convert template to target format and store it in settings
{
QSettings settings(QSettings::UserScope, "QtProject");
settings.beginGroup("FileDialog");
settings.setValue("sidebarWidth", 93); // random value
settings.setValue("shortcuts", QStringList({settings.fileName(), "/tmp"}));
settings.setValue("qtVersion", qtVersion);
settings.setValue("treeViewHeader", dsVersion < QDataStream::Qt_6_0 ? ba32 : ba64);
settings.endGroup();
}
// Create a file dialog, read settings write them back
{
QFileDialog fd;
}
// Read back settings and compare byte array
QSettings settings(QSettings::UserScope, "QtProject");
settings.beginGroup("FileDialog");
const QByteArray savedState = settings.value("treeViewHeader").toByteArray();
QCOMPARE(savedState, ba32);
}
#endif
class FilterDirModel : public QSortFilterProxyModel