mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-19 04:24:14 +02:00
.qetz: read the project database the way SQLite advises for untrusted files
A .qetz's project.sqlite comes from a file, which may be damaged, made by another tool, edited by hand (issue #1458) or made to harm. SQLite's advice for such files (https://www.sqlite.org/security.html) was only partly followed: trusted_schema was off and every name had to be a plain table. readAsUntrusted() now also turns on the defensive mode, turns off triggers and views, checks each cell's size as its page is read, limits a value to 256 MiB, and runs quick_check before anything is read, for the database read on opening and for the one a save copies kept folios from. A damaged database is now refused as "the project database is damaged", where it was "cannot be read: database disk image is malformed" or a missing row. Damage on disk was already caught before SQLite: the zip checks each entry (one flipped bit in project.sqlite: refused, "unexpected decompressed size"). quick_check does not see a changed value inside a row (49 of 400 random small changes read silently, quick_check "ok"): only the zip's checksum does. New test damagedDatabaseRefused (a page overwritten at three places): fails without this change on the message. Opening nuri (136 folios): 3.56-3.66 s with and without. ctest: the 7 container, save and lazy folio suites pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QtEndian>
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -533,6 +534,38 @@ private slots:
|
||||
QVERIFY(!error.isEmpty());
|
||||
}
|
||||
|
||||
// A page of the database overwritten, as a bad disk or a broken copy
|
||||
// would: refused, and said to be damaged (quick_check on opening),
|
||||
// whichever table the page held
|
||||
void damagedDatabaseRefused_data()
|
||||
{
|
||||
QTest::addColumn<double>("where");
|
||||
QTest::newRow("second page") << 0.0;
|
||||
QTest::newRow("middle page") << 0.5;
|
||||
QTest::newRow("last page") << 1.0;
|
||||
}
|
||||
void damagedDatabaseRefused()
|
||||
{
|
||||
QFETCH(double, where);
|
||||
QDomDocument original;
|
||||
QList<QetZip::Entry> entries = splitFile(QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"), &original);
|
||||
bool damaged = false;
|
||||
for (QetZip::Entry &entry : entries) {
|
||||
if (entry.name != QLatin1String("project.sqlite")) continue;
|
||||
const int page_size = qFromBigEndian<quint16>(entry.data.constData() + 16);
|
||||
const int pages = int(entry.data.size() / page_size);
|
||||
QVERIFY(pages > 3);
|
||||
const int page = 1 + int(where * (pages - 2));
|
||||
entry.data.replace(page * page_size, page_size, QByteArray(page_size, char(0xff)));
|
||||
damaged = true;
|
||||
}
|
||||
QVERIFY(damaged);
|
||||
QDomDocument back;
|
||||
QString error;
|
||||
QVERIFY(!QetContainer::join(entries, &back, &error));
|
||||
QVERIFY2(error.contains(QStringLiteral("damaged")), qPrintable(error));
|
||||
}
|
||||
|
||||
// project.sqlite changed by @p sql in @p entries
|
||||
static void alterDatabase(QList<QetZip::Entry> &entries, const char *sql)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user