From 5930b503079130f0df11415dc76d196755524ee5 Mon Sep 17 00:00:00 2001 From: Adam Cooper Date: Mon, 6 Apr 2020 14:29:13 -0400 Subject: SERVER-47733 SymmetricEncryptorWindows shouldn't pad when update is called (cherry picked from commit 2f6e5d0f94c06fde943ed6a25a9b7ecf6f774ce5) --- src/mongo/crypto/symmetric_crypto_windows.cpp | 105 ++++++++++++++++++++++++-- src/mongo/db/storage/remove_saver.cpp | 24 ++++-- src/mongo/db/storage/remove_saver.h | 15 +++- 3 files changed, 129 insertions(+), 15 deletions(-) diff --git a/src/mongo/crypto/symmetric_crypto_windows.cpp b/src/mongo/crypto/symmetric_crypto_windows.cpp index 25dd5f304b8..0c90c3b4a1b 100644 --- a/src/mongo/crypto/symmetric_crypto_windows.cpp +++ b/src/mongo/crypto/symmetric_crypto_windows.cpp @@ -31,6 +31,7 @@ #include "mongo/platform/basic.h" +#include #include #include @@ -206,16 +207,99 @@ protected: std::vector _iv; }; +/** + * Like other symmetric encryptors, this class encrypts block-by-block with update and then only + * pads once finalize is called. However, the Windows's BCrypt implementation does not natively + * implement this functionality (see SERVER-47733), and will either require block aligned inputs or + * will attempt to pad every input. This class bulks together inputs in a local buffer which is + * flushed to BCrypt whenever a full block is accumulated via update invocations. Data provided to + * update may be encrypted immediately, on a subsequent call to update, or on the call to finalize. + */ class SymmetricEncryptorWindows : public SymmetricImplWindows { public: using SymmetricImplWindows::SymmetricImplWindows; + SymmetricEncryptorWindows(const SymmetricKey& key, + aesMode mode, + const uint8_t* iv, + size_t ivLen) + : _blockData(_blockBuffer->data(), _blockBuffer->size()), + _blockCursor(_blockData), + SymmetricImplWindows(key, mode, iv, ivLen) {} + StatusWith update(const uint8_t* in, size_t inLen, uint8_t* out, size_t outLen) final { - ULONG len = 0; + ULONG blockBufferEncryptLen = 0; + ULONG inputEncryptLen = 0; + ConstDataRange inData(in, inLen); + ConstDataRangeCursor inCursor(inData); + + // If we have an incomplete block, we need to fill it before encrypting. + // If the total amount of input bytes will not fill the blockBuffer, just add it all to + // the buffer. + if (inLen < _blockCursor.length()) { + _blockCursor.writeAndAdvance(inCursor); + return 0; + } else if (_blockCursor.length() < _blockData.length() && _blockCursor.length() > 0) { + // Entering this code path means that we had data left over from the last time update + // was called. What we do below is fill the buffer with new input data until it is full. + // We then encrypt that buffer. We skip this step when the buffer is empty. + uint8_t bytesToFill = _blockCursor.length(); + ConstDataRange bytesToFillRange(inCursor.data(), bytesToFill); + _blockCursor.writeAndAdvance(bytesToFillRange); + inCursor.advance(bytesToFill); + // We now encrypt the full buffer. + NTSTATUS status = BCryptEncrypt(_keyHandle, + const_cast(_blockBuffer->data()), + _blockBuffer->size(), + NULL, + _iv.data(), + _iv.size(), + out, + outLen, + &blockBufferEncryptLen, + 0); + if (status != STATUS_SUCCESS) { + return Status{ErrorCodes::OperationFailed, + str::stream() << "Encrypt failed: " << statusWithDescription(status)}; + } + _blockCursor = DataRangeCursor(_blockData); + } + + // we will attempt to encrypt as much of the remaining data as we can (i.e. the largest + // available size that is a multiple of the block length) + size_t remainingBytes = inCursor.length(); + ULONG bytesToEncrypt = remainingBytes - (remainingBytes % aesBlockSize); NTSTATUS status = BCryptEncrypt(_keyHandle, - const_cast(in), - inLen, + const_cast(inCursor.data()), + bytesToEncrypt, + NULL, + _iv.data(), + _iv.size(), + out + blockBufferEncryptLen, + outLen - blockBufferEncryptLen, + &inputEncryptLen, + 0); + + if (status != STATUS_SUCCESS) { + return Status{ErrorCodes::OperationFailed, + str::stream() << "Encrypt failed: " << statusWithDescription(status)}; + } + + inCursor.advance(bytesToEncrypt); + + // we now have to store what is left of the input in the block buffer + _blockCursor.writeAndAdvance(inCursor); + + return static_cast(blockBufferEncryptLen + inputEncryptLen); + } + + StatusWith finalize(uint8_t* out, size_t outLen) final { + // if there is any data left over in the block buffer, we will encrypt it with padding + ULONG len = 0; + NTSTATUS status = BCryptEncrypt(_keyHandle, + const_cast(_blockBuffer->data()), + _blockBuffer->size() - _blockCursor.length(), NULL, _iv.data(), _iv.size(), @@ -229,18 +313,23 @@ public: str::stream() << "Encrypt failed: " << statusWithDescription(status)}; } - return static_cast(len); - } + // we will now start a new block + _blockCursor = DataRangeCursor(_blockData); - StatusWith finalize(uint8_t* out, size_t outLen) final { - // No finalize needed - return 0; + return static_cast(len); } StatusWith finalizeTag(uint8_t* out, size_t outLen) final { // Not a tagged cipher mode, write nothing. return 0; } + +private: + // buffer to store a single block of data, to be encrypted by update when filled, or by finalize + // with padding. 16 is the block length for AES. + SecureAllocatorDefaultDomain::SecureHandle> _blockBuffer; + DataRange _blockData; + DataRangeCursor _blockCursor; }; class SymmetricDecryptorWindows : public SymmetricImplWindows { diff --git a/src/mongo/db/storage/remove_saver.cpp b/src/mongo/db/storage/remove_saver.cpp index e24f33ecadd..440d6139b31 100644 --- a/src/mongo/db/storage/remove_saver.cpp +++ b/src/mongo/db/storage/remove_saver.cpp @@ -35,6 +35,7 @@ #include #include +#include #include "mongo/db/service_context.h" #include "mongo/db/storage/encryption_hooks.h" @@ -50,7 +51,11 @@ using std::stringstream; namespace mongo { -RemoveSaver::RemoveSaver(const string& a, const string& b, const string& why) { +RemoveSaver::RemoveSaver(const string& a, + const string& b, + const string& why, + std::unique_ptr storage) + : _storage(std::move(storage)) { static int NUM = 0; _root = storageGlobalParams.dbpath; @@ -119,16 +124,14 @@ RemoveSaver::~RemoveSaver() { << " for remove saving: " << redact(errnoWithDescription()); fassertFailed(34354); } + + _storage->dumpBuffer(); } } Status RemoveSaver::goingToDelete(const BSONObj& o) { if (!_out) { - // We don't expect to ever pass "" to create_directories below, but catch - // this anyway as per SERVER-26412. - invariant(!_root.empty()); - boost::filesystem::create_directories(_root); - _out.reset(new ofstream(_file.string().c_str(), ios_base::out | ios_base::binary)); + _out = _storage->makeOstream(_file, _root); if (_out->fail()) { string msg = str::stream() << "couldn't create file: " << _file.string() @@ -175,4 +178,13 @@ Status RemoveSaver::goingToDelete(const BSONObj& o) { return Status::OK(); } +std::unique_ptr RemoveSaver::Storage::makeOstream( + const boost::filesystem::path& file, const boost::filesystem::path& root) { + // We don't expect to ever pass "" to create_directories below, but catch + // this anyway as per SERVER-26412. + invariant(!root.empty()); + boost::filesystem::create_directories(root); + return std::make_unique(file.string().c_str(), + std::ios_base::out | std::ios_base::binary); +} } // namespace mongo diff --git a/src/mongo/db/storage/remove_saver.h b/src/mongo/db/storage/remove_saver.h index 0704ddb4d49..463fe7f8410 100644 --- a/src/mongo/db/storage/remove_saver.h +++ b/src/mongo/db/storage/remove_saver.h @@ -51,7 +51,11 @@ class RemoveSaver { RemoveSaver& operator=(const RemoveSaver&) = delete; public: - RemoveSaver(const std::string& type, const std::string& ns, const std::string& why); + class Storage; + RemoveSaver(const std::string& type, + const std::string& ns, + const std::string& why, + std::unique_ptr storage = std::make_unique()); ~RemoveSaver(); /** @@ -77,11 +81,20 @@ public: } void file() && = delete; + class Storage { + public: + virtual ~Storage() = default; + virtual std::unique_ptr makeOstream(const boost::filesystem::path& file, + const boost::filesystem::path& root); + virtual void dumpBuffer() {} + }; + private: boost::filesystem::path _root; boost::filesystem::path _file; std::unique_ptr _protector; std::unique_ptr _out; + std::unique_ptr _storage; }; } // namespace mongo -- cgit v1.2.1