Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 51 additions & 8 deletions client/CDocSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
*/

#include <QtCore/QBuffer>
#include <QtCore/QDir>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QStorageInfo>
#include <QtCore/QtEndian>
#include <QtCore/QTemporaryFile>
#include <QtCore/QUrlQuery>
Expand Down Expand Up @@ -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;
}

Expand All @@ -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<QTemporaryFile>();
} 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<QBuffer>();
}
else
io.data = std::make_unique<QTemporaryFile>();
io.data->open(QIODevice::ReadWrite);
files.push_back(std::move(io));
return libcdoc::OK;
Expand Down
31 changes: 27 additions & 4 deletions client/CDocSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<IOEntry> 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 {
Expand Down
23 changes: 23 additions & 0 deletions client/CryptoDoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down
8 changes: 8 additions & 0 deletions client/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,10 @@
<source>You do not have the key to decrypt this document</source>
<translation>You do not have the key to decrypt this document</translation>
</message>
<message>
<source>The container contains too many files.</source>
<translation>The container contains too many files.</translation>
</message>
<message>
<source>No keys specified</source>
<translation>No recipients specified</translation>
Expand All @@ -592,6 +596,10 @@
<source>Failed to add key</source>
<translation>Failed to add key</translation>
</message>
<message>
<source>The container requires more temporary disk space than the application can safely use.</source>
<translation>The container requires more temporary disk space than the application can safely use.</translation>
</message>
<message>
<source>Please check your internet connection and network settings.</source>
<translation>Please check your internet connection and network settings.</translation>
Expand Down
8 changes: 8 additions & 0 deletions client/translations/et.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,10 @@
<source>You do not have the key to decrypt this document</source>
<translation>Sul puudub võti millega dekrüpteerida seda turvaümbrikut</translation>
</message>
<message>
<source>The container contains too many files.</source>
<translation>Konteiner sisaldab liiga palju faile.</translation>
</message>
<message>
<source>No keys specified</source>
<translation>Ühtegi adressaati ei ole lisatud</translation>
Expand All @@ -592,6 +596,10 @@
<source>Failed to add key</source>
<translation>Võtme lisamine ebaõnnestus</translation>
</message>
<message>
<source>The container requires more temporary disk space than the application can safely use.</source>
<translation>Konteiner vajab rohkem ajutist kettaruumi, kui rakendus saab turvaliselt kasutada.</translation>
</message>
<message>
<source>Please check your internet connection and network settings.</source>
<translation>Palun kontrolli internetiühendust ja võrgu sätteid.</translation>
Expand Down
Loading