From f03f45d6427b47b4f1c41917dccf813a821eb394 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 11 Sep 2026 11:09:08 +0300 Subject: [PATCH] Add limits to decrypting files IB-9052 Signed-off-by: Raul Metsma --- client/CDocSupport.cpp | 59 +++++++++++++++++++++++++++++++++------ client/CDocSupport.h | 31 +++++++++++++++++--- client/CryptoDoc.cpp | 23 +++++++++++++++ client/libcdoc | 2 +- client/translations/en.ts | 8 ++++++ client/translations/et.ts | 8 ++++++ 6 files changed, 118 insertions(+), 13 deletions(-) diff --git a/client/CDocSupport.cpp b/client/CDocSupport.cpp index 19cf47812..88f29c23f 100644 --- a/client/CDocSupport.cpp +++ b/client/CDocSupport.cpp @@ -18,8 +18,10 @@ */ #include +#include #include #include +#include #include #include #include @@ -399,27 +401,59 @@ void DDCDocLogger::setUpLogger(const QString &path) void DDCDocLogger::setLogLevel(libcdoc::LogLevel level) { - DDCDocLogger *logger = getLogger(); - logger->setMinLogLevel(level); + getLogger()->setMinLogLevel(level); +} + +TempListConsumer::TempListConsumer() +{ + const QStorageInfo storage(QDir::tempPath()); + if(storage.isValid() && storage.isReady() && storage.bytesAvailable() >= 0) + { + const quint64 available = quint64(storage.bytesAvailable()); + const size_t safeAvailable = size_t(available > MIN_FREE_DISK_SIZE ? available - MIN_FREE_DISK_SIZE : 0); + if(safeAvailable < _disk_limit) + _disk_limit = safeAvailable; + } } TempListConsumer::~TempListConsumer() { if (!files.empty()) { - IOEntry& file = files.back(); - file.data->close(); + files.back().data->close(); } } +libcdoc::result_t TempListConsumer::reject(Rejection reason, libcdoc::result_t code) noexcept +{ + if(_rejection == Rejection::None) + _rejection = reason; + return code; +} + libcdoc::result_t TempListConsumer::write(const uint8_t *src, size_t size) noexcept { if (files.empty()) return libcdoc::OUTPUT_ERROR; + if (_rejection != Rejection::None) + return libcdoc::OUTPUT_ERROR; IOEntry &file = files.back(); if (!file.data->isWritable()) return libcdoc::OUTPUT_ERROR; + + // An entry must not exceed the size its own TAR header declared; one that + // does is malformed, not merely large. Entries without a declared size are + // bounded by the cumulative disk budget below. + if(_declared >= 0 && (file.size > _declared || + std::cmp_greater(size, uint64_t(_declared - file.size)))) + return reject(Rejection::Overrun, libcdoc::DATA_FORMAT_ERROR); + + if(!_in_memory && exceedsDiskBudget(size)) { + return reject(Rejection::Disk, libcdoc::OUTPUT_ERROR); + } + if (auto result = file.data->write((const char *)src, size); std::cmp_not_equal(result , size)) return result; file.size += size; + (_in_memory ? _memory_used : _disk_used) += size; return size; } @@ -446,12 +480,21 @@ TempListConsumer::open(const std::string& name, int64_t size) std::string truncated = name; if (truncated.starts_with("./PaxHeaders.X/")) truncated = truncated.substr(15); + if(files.size() >= MAX_FILE_COUNT) + return reject(Rejection::Count, libcdoc::OUTPUT_ERROR); + IOEntry io({std::move(truncated), "application/octet-stream", 0, {}}); - if ((size < 0) || (size > MAX_VEC_SIZE)) { - io.data = std::make_unique(); - } else { + // Buffer in memory only while the shared budget has room for the whole + // entry; everything else, including entries of undeclared size, spills to a + // temporary file. + _declared = size; + _in_memory = size >= 0 && std::cmp_less_equal(size, MAX_MEMORY_SIZE - _memory_used); + if(!_in_memory && size >= 0 && exceedsDiskBudget(size_t(size))) + return reject(Rejection::Disk, libcdoc::OUTPUT_ERROR); + if(_in_memory) io.data = std::make_unique(); - } + else + io.data = std::make_unique(); io.data->open(QIODevice::ReadWrite); files.push_back(std::move(io)); return libcdoc::OK; diff --git a/client/CDocSupport.h b/client/CDocSupport.h index 7a9fe5573..92f9e21fc 100644 --- a/client/CDocSupport.h +++ b/client/CDocSupport.h @@ -159,10 +159,11 @@ struct IOEntry }; struct TempListConsumer final : public libcdoc::MultiDataConsumer { - static constexpr int64_t MAX_VEC_SIZE = 500L * 1024L * 1024L; + /// Why extraction stopped, so policy limits can be distinguished from + /// malformed container data. + enum class Rejection : quint8 { None, Count, Disk, Overrun }; - explicit TempListConsumer(size_t max_memory_size = 500L * 1024L * 1024L) - : _max_memory_size(max_memory_size) {} + TempListConsumer(); ~TempListConsumer(); libcdoc::result_t write(const uint8_t *src, size_t size) noexcept final; @@ -171,8 +172,30 @@ struct TempListConsumer final : public libcdoc::MultiDataConsumer { libcdoc::result_t open(const std::string &name, int64_t size) final; - size_t _max_memory_size; + Rejection rejection() const noexcept { return _rejection; } + std::vector files; + +private: + static constexpr size_t MAX_MEMORY_SIZE = 500ULL * 1024ULL * 1024ULL; + static constexpr size_t MAX_DISK_SIZE = 8ULL * 1024ULL * 1024ULL * 1024ULL; + static constexpr size_t MIN_FREE_DISK_SIZE = 1ULL * 1024ULL * 1024ULL * 1024ULL; + static constexpr size_t MAX_FILE_COUNT = 1000; + + [[nodiscard]] bool exceedsDiskBudget(size_t size) const noexcept + { + return _disk_used > _disk_limit || size > _disk_limit - _disk_used; + } + libcdoc::result_t reject(Rejection reason, libcdoc::result_t code) noexcept; + + Rejection _rejection = Rejection::None; + size_t _memory_used = 0; + size_t _disk_used = 0; + size_t _disk_limit = MAX_DISK_SIZE; + /// Size the container declared for the open entry, or -1 if unstated. + int64_t _declared = -1; + /// Whether the open entry is buffered in memory rather than on disk. + bool _in_memory = false; }; struct StreamListSource final : public libcdoc::MultiDataSource { diff --git a/client/CryptoDoc.cpp b/client/CryptoDoc.cpp index a83b10d91..5f70f1560 100644 --- a/client/CryptoDoc.cpp +++ b/client/CryptoDoc.cpp @@ -340,6 +340,29 @@ bool CryptoDoc::decrypt(const libcdoc::Lock *lock, const QByteArray& secret) if (result != libcdoc::OK) { QString str; const std::string &msg = d->reader->getLastErrorStr(); + // Resource-limit failures and malformed declared sizes need specific + // messages, so report them before the generic mapping below. + if(cons.rejection() != TempListConsumer::Rejection::None) { + switch(cons.rejection()) { + case TempListConsumer::Rejection::Count: + str = tr("The container contains too many files."); + break; + case TempListConsumer::Rejection::Disk: + str = tr("The container requires more temporary disk space than the application can safely use."); + break; + case TempListConsumer::Rejection::Overrun: + str = tr("Corrupted or tampered file."); + break; + case TempListConsumer::Rejection::None: + break; + } + WarningDialog::create() + ->withTitle(QSigner::tr("Failed to decrypt document")) + ->withText(str) + ->withDetails(QString::fromStdString(msg)) + ->open(); + return false; + } switch (result) { case libcdoc::WRONG_KEY: str = (lock->type == libcdoc::Lock::PASSWORD) ? tr("Wrong password.") : tr("Wrong key."); diff --git a/client/libcdoc b/client/libcdoc index 83408f9fc..1a3eda9bf 160000 --- a/client/libcdoc +++ b/client/libcdoc @@ -1 +1 @@ -Subproject commit 83408f9fcf4af25e81d891c34f9cc437d23f021a +Subproject commit 1a3eda9bf9f27d6c8460c97986131a32387e8ae4 diff --git a/client/translations/en.ts b/client/translations/en.ts index e0ee8edf5..98ee5f7c3 100644 --- a/client/translations/en.ts +++ b/client/translations/en.ts @@ -584,6 +584,10 @@ You do not have the key to decrypt this document You do not have the key to decrypt this document + + The container contains too many files. + The container contains too many files. + No keys specified No recipients specified @@ -592,6 +596,10 @@ Failed to add key Failed to add key + + The container requires more temporary disk space than the application can safely use. + The container requires more temporary disk space than the application can safely use. + Please check your internet connection and network settings. Please check your internet connection and network settings. diff --git a/client/translations/et.ts b/client/translations/et.ts index b8052a14a..7e857d4cd 100644 --- a/client/translations/et.ts +++ b/client/translations/et.ts @@ -584,6 +584,10 @@ You do not have the key to decrypt this document Sul puudub võti millega dekrüpteerida seda turvaümbrikut + + The container contains too many files. + Konteiner sisaldab liiga palju faile. + No keys specified Ühtegi adressaati ei ole lisatud @@ -592,6 +596,10 @@ Failed to add key Võtme lisamine ebaõnnestus + + The container requires more temporary disk space than the application can safely use. + Konteiner vajab rohkem ajutist kettaruumi, kui rakendus saab turvaliselt kasutada. + Please check your internet connection and network settings. Palun kontrolli internetiühendust ja võrgu sätteid.