From c4ee38987d0f586e4d1483b65cd61fadf244a312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Tue, 30 Jun 2026 18:53:26 +0200 Subject: [PATCH] Fix getHeaderInfo() signed-shift UB and use fixed-width Header types The big-endian assembly shifted unsigned char operands that promote to int, so bytes with the high bit set produced signed left-shift UB and sign-extended when widened to the Header fields. Cast each byte to uint32_t before shifting, via a local readBE32 helper that also removes the repetitive byte arithmetic. Header used platform-variable `unsigned long` (32-bit on LLP64 Windows, 64-bit on LP64), giving a cross-platform field-width mismatch. Switch the struct to fixed-width types (uint8_t / uint32_t). Fixes DB-02 and DB-03 from the code review. --- CHANGELOG.md | 1 + include/SQLiteCpp/Database.h | 45 ++++++++------- src/Database.cpp | 108 ++++++++--------------------------- 3 files changed, 47 insertions(+), 107 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 923ac26f..7fb52662 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -308,3 +308,4 @@ Version 3.4.0 - 2026 ??? - Fix execute_many() to clear stale bindings between parameter sets (#554) - Fix Column::operator<< to stream the exact column bytes via getString() (#556) - Restore the Coverity Scan static analysis as a GitHub Actions workflow, replacing the old Travis CI job +- Fix Database::getHeaderInfo() signed-shift UB and use fixed-width types for the Header struct (#558) diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index e92e2898..f728513f 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -64,6 +64,7 @@ namespace filesystem = experimental::filesystem; #endif // SQLITECPP_HAVE_STD_EXPERIMENTAL_FILESYSTEM +#include #include #include @@ -120,28 +121,28 @@ SQLITECPP_API int getLibVersionNumber() noexcept; // Public structure for representing all fields contained within the SQLite header. // Official documentation for fields: https://www.sqlite.org/fileformat.html#the_database_header struct Header { - unsigned char headerStr[16]; - unsigned int pageSizeBytes; - unsigned char fileFormatWriteVersion; - unsigned char fileFormatReadVersion; - unsigned char reservedSpaceBytes; - unsigned char maxEmbeddedPayloadFrac; - unsigned char minEmbeddedPayloadFrac; - unsigned char leafPayloadFrac; - unsigned long fileChangeCounter; - unsigned long databaseSizePages; - unsigned long firstFreelistTrunkPage; - unsigned long totalFreelistPages; - unsigned long schemaCookie; - unsigned long schemaFormatNumber; - unsigned long defaultPageCacheSizeBytes; - unsigned long largestBTreePageNumber; - unsigned long databaseTextEncoding; - unsigned long userVersion; - unsigned long incrementalVaccumMode; - unsigned long applicationId; - unsigned long versionValidFor; - unsigned long sqliteVersion; + std::uint8_t headerStr[16]; + std::uint32_t pageSizeBytes; + std::uint8_t fileFormatWriteVersion; + std::uint8_t fileFormatReadVersion; + std::uint8_t reservedSpaceBytes; + std::uint8_t maxEmbeddedPayloadFrac; + std::uint8_t minEmbeddedPayloadFrac; + std::uint8_t leafPayloadFrac; + std::uint32_t fileChangeCounter; + std::uint32_t databaseSizePages; + std::uint32_t firstFreelistTrunkPage; + std::uint32_t totalFreelistPages; + std::uint32_t schemaCookie; + std::uint32_t schemaFormatNumber; + std::uint32_t defaultPageCacheSizeBytes; + std::uint32_t largestBTreePageNumber; + std::uint32_t databaseTextEncoding; + std::uint32_t userVersion; + std::uint32_t incrementalVaccumMode; + std::uint32_t applicationId; + std::uint32_t versionValidFor; + std::uint32_t sqliteVersion; }; /** diff --git a/src/Database.cpp b/src/Database.cpp index 3f6fb97f..f893cabd 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -329,97 +329,35 @@ Header Database::getHeaderInfo(const std::string& aFilename) throw SQLite::Exception("Invalid or encrypted SQLite header in file " + aFilename); } - h.pageSizeBytes = (buf[16] << 8) | buf[17]; + const auto readBE32 = [&buf](std::size_t offset) -> std::uint32_t + { + return (static_cast(buf[offset]) << 24) | + (static_cast(buf[offset + 1]) << 16) | + (static_cast(buf[offset + 2]) << 8) | + (static_cast(buf[offset + 3]) << 0); + }; + + h.pageSizeBytes = (static_cast(buf[16]) << 8) | static_cast(buf[17]); h.fileFormatWriteVersion = buf[18]; h.fileFormatReadVersion = buf[19]; h.reservedSpaceBytes = buf[20]; h.maxEmbeddedPayloadFrac = buf[21]; h.minEmbeddedPayloadFrac = buf[22]; h.leafPayloadFrac = buf[23]; - - h.fileChangeCounter = - (buf[24] << 24) | - (buf[25] << 16) | - (buf[26] << 8) | - (buf[27] << 0); - - h.databaseSizePages = - (buf[28] << 24) | - (buf[29] << 16) | - (buf[30] << 8) | - (buf[31] << 0); - - h.firstFreelistTrunkPage = - (buf[32] << 24) | - (buf[33] << 16) | - (buf[34] << 8) | - (buf[35] << 0); - - h.totalFreelistPages = - (buf[36] << 24) | - (buf[37] << 16) | - (buf[38] << 8) | - (buf[39] << 0); - - h.schemaCookie = - (buf[40] << 24) | - (buf[41] << 16) | - (buf[42] << 8) | - (buf[43] << 0); - - h.schemaFormatNumber = - (buf[44] << 24) | - (buf[45] << 16) | - (buf[46] << 8) | - (buf[47] << 0); - - h.defaultPageCacheSizeBytes = - (buf[48] << 24) | - (buf[49] << 16) | - (buf[50] << 8) | - (buf[51] << 0); - - h.largestBTreePageNumber = - (buf[52] << 24) | - (buf[53] << 16) | - (buf[54] << 8) | - (buf[55] << 0); - - h.databaseTextEncoding = - (buf[56] << 24) | - (buf[57] << 16) | - (buf[58] << 8) | - (buf[59] << 0); - - h.userVersion = - (buf[60] << 24) | - (buf[61] << 16) | - (buf[62] << 8) | - (buf[63] << 0); - - h.incrementalVaccumMode = - (buf[64] << 24) | - (buf[65] << 16) | - (buf[66] << 8) | - (buf[67] << 0); - - h.applicationId = - (buf[68] << 24) | - (buf[69] << 16) | - (buf[70] << 8) | - (buf[71] << 0); - - h.versionValidFor = - (buf[92] << 24) | - (buf[93] << 16) | - (buf[94] << 8) | - (buf[95] << 0); - - h.sqliteVersion = - (buf[96] << 24) | - (buf[97] << 16) | - (buf[98] << 8) | - (buf[99] << 0); + h.fileChangeCounter = readBE32(24); + h.databaseSizePages = readBE32(28); + h.firstFreelistTrunkPage = readBE32(32); + h.totalFreelistPages = readBE32(36); + h.schemaCookie = readBE32(40); + h.schemaFormatNumber = readBE32(44); + h.defaultPageCacheSizeBytes = readBE32(48); + h.largestBTreePageNumber = readBE32(52); + h.databaseTextEncoding = readBE32(56); + h.userVersion = readBE32(60); + h.incrementalVaccumMode = readBE32(64); + h.applicationId = readBE32(68); + h.versionValidFor = readBE32(92); + h.sqliteVersion = readBE32(96); return h; }