
This CL adds a new class crypto::ProcessBoundString to support functionality to bind allocated memory to a process. Data placed into a crypto::ProcessBoundString no longer appears in crash dumps or memory dumps so should be used for any sensitive data that should be process-bound. This does not entirely prevent the data from existing in memory since eventually it has to be converted to real data for use elsewhere in the code (e.g. written to network). A best-efforts convenience self-wiping basic_string class is also provided to help prevent this as much as possible. If storing data for process lifetime e.g. in a std::map or std::set of static data, crypto::ProcessBoundString can be used to help prevent this data from ending up in dumps by default. A feature "ProcessBoundStringEncryption" is added to control whether or not encryption is enabled. In addition, encryption is only implemented on Windows at the moment. BUG=372873695 Change-Id: I883fd96764f899ed56c17beb0e29127c2351e0e2 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5905063 Reviewed-by: Adam Langley <agl@chromium.org> Commit-Queue: Will Harris <wfh@chromium.org> Cr-Commit-Position: refs/heads/main@{#1367859}
154 lines
5.8 KiB
C++
154 lines
5.8 KiB
C++
// Copyright 2024 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#ifndef CRYPTO_PROCESS_BOUND_STRING_H_
|
|
#define CRYPTO_PROCESS_BOUND_STRING_H_
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "base/check.h"
|
|
#include "base/containers/span.h"
|
|
#include "base/feature_list.h"
|
|
#include "base/gtest_prod_util.h"
|
|
#include "crypto/crypto_export.h"
|
|
#include "crypto/features.h"
|
|
|
|
namespace crypto {
|
|
|
|
namespace internal {
|
|
|
|
// Maybe round the size of the data to a size needed for the encrypt or decrypt
|
|
// operation. Returns the new size, or `size` if no rounding up is needed.
|
|
CRYPTO_EXPORT size_t MaybeRoundUp(size_t size);
|
|
|
|
// Maybe encrypt a buffer, in place. Returns true if the buffer was successfully
|
|
// encrypted or false if unsupported by the platform or failed to encrypt.
|
|
CRYPTO_EXPORT bool MaybeEncryptBuffer(base::span<uint8_t> buffer);
|
|
|
|
// Maybe decrypt a buffer, in place. Returns true if the buffer was successfully
|
|
// decrypted or false if unsupported by the platform or failed to decrypt.
|
|
CRYPTO_EXPORT bool MaybeDecryptBuffer(base::span<uint8_t> buffer);
|
|
|
|
// Securely zero a buffer using a platform specific method.
|
|
CRYPTO_EXPORT void SecureZeroBuffer(base::span<uint8_t> buffer);
|
|
|
|
} // namespace internal
|
|
|
|
// SecureAllocator is used by the SecureString variants below to clear the
|
|
// memory when the string moves out of scope.
|
|
template <typename T>
|
|
struct CRYPTO_EXPORT SecureAllocator {
|
|
using value_type = T;
|
|
|
|
SecureAllocator() noexcept = default;
|
|
|
|
T* allocate(std::size_t n) { return std::allocator<T>().allocate(n); }
|
|
|
|
void deallocate(T* p, std::size_t n) noexcept {
|
|
if (p) {
|
|
// SAFETY: deallocate() has a fixed prototype from the std library, and
|
|
// passes an unsafe buffer, so convert it to a base::span here.
|
|
internal::SecureZeroBuffer(UNSAFE_BUFFERS(
|
|
base::span<uint8_t>(reinterpret_cast<uint8_t*>(p), n * sizeof(T))));
|
|
std::allocator<T>().deallocate(p, n);
|
|
}
|
|
}
|
|
};
|
|
|
|
// On supported platforms, a process bound string cannot have its content read
|
|
// by other processes on the system. On unsupported platforms it provides no
|
|
// difference over a native string except it does more copies.
|
|
template <typename StringType>
|
|
class CRYPTO_EXPORT ProcessBound {
|
|
public:
|
|
using CharType = typename StringType::value_type;
|
|
|
|
ProcessBound(const ProcessBound& other) = default;
|
|
ProcessBound(ProcessBound&& other) = default;
|
|
ProcessBound& operator=(const ProcessBound& other) = default;
|
|
ProcessBound& operator=(ProcessBound&& other) = default;
|
|
|
|
// Create a process bound string. Takes a copy of the string passed in.
|
|
explicit ProcessBound(const StringType& value)
|
|
: original_size_(value.size()) {
|
|
std::vector<CharType> data(value.begin(), value.end());
|
|
if (base::FeatureList::IsEnabled(
|
|
crypto::features::kProcessBoundStringEncryption)) {
|
|
data.resize(internal::MaybeRoundUp(data.size()));
|
|
encrypted_ =
|
|
internal::MaybeEncryptBuffer(base::as_writable_byte_span(data));
|
|
}
|
|
maybe_encrypted_data_ = std::move(data);
|
|
}
|
|
|
|
~ProcessBound() = default;
|
|
|
|
// Return the decrypted string.
|
|
StringType value() const { return StringType(secure_value()); }
|
|
|
|
// Return the decrypted string as a string that attempts to wipe itself after
|
|
// use. Prefer over calling `value()` if caller can support it.
|
|
std::basic_string<CharType,
|
|
std::char_traits<CharType>,
|
|
SecureAllocator<CharType>>
|
|
secure_value() const {
|
|
if (!encrypted_) {
|
|
return std::basic_string<CharType, std::char_traits<CharType>,
|
|
SecureAllocator<CharType>>(
|
|
maybe_encrypted_data_.data(), original_size_);
|
|
}
|
|
|
|
// Copy to decrypt in-place.
|
|
std::basic_string<CharType, std::char_traits<CharType>,
|
|
SecureAllocator<CharType>>
|
|
decrypted(maybe_encrypted_data_.begin(), maybe_encrypted_data_.end());
|
|
// Attempt to avoid Small String Optimization (SSO) by reserving a larger
|
|
// allocation than the SSO default, forcing a dynamic allocation to occur,
|
|
// before any decrypted data is written to the string. This value was
|
|
// determined empirically.
|
|
constexpr size_t kSSOMaxSize = 64u;
|
|
if (decrypted.size() < kSSOMaxSize) {
|
|
decrypted.reserve(kSSOMaxSize);
|
|
}
|
|
CHECK(internal::MaybeDecryptBuffer(base::as_writable_byte_span(decrypted)));
|
|
decrypted.resize(original_size_);
|
|
return decrypted;
|
|
}
|
|
|
|
size_t size() const { return original_size_; }
|
|
bool empty() const { return size() == 0; }
|
|
|
|
private:
|
|
FRIEND_TEST_ALL_PREFIXES(ProcessBoundFeatureTest, Encryption);
|
|
std::vector<CharType> maybe_encrypted_data_;
|
|
size_t original_size_;
|
|
bool encrypted_ = false;
|
|
};
|
|
|
|
using ProcessBoundString = ProcessBound<std::string>;
|
|
using ProcessBoundWString = ProcessBound<std::wstring>;
|
|
using ProcessBoundU16String = ProcessBound<std::u16string>;
|
|
|
|
// SecureString variants here attempt to clean memory for the string data when
|
|
// the string goes out of scope. However, while in memory it can be read, and if
|
|
// copied somewhere else, the memory can also be read. This is a defense in
|
|
// depth hardening and not meant to provide strong security guarantees.
|
|
using SecureString =
|
|
std::basic_string<std::string::value_type,
|
|
std::char_traits<std::string::value_type>,
|
|
SecureAllocator<std::string::value_type>>;
|
|
using SecureWString =
|
|
std::basic_string<std::wstring::value_type,
|
|
std::char_traits<std::wstring::value_type>,
|
|
SecureAllocator<std::wstring::value_type>>;
|
|
using SecureU16String =
|
|
std::basic_string<std::u16string::value_type,
|
|
std::char_traits<std::u16string::value_type>,
|
|
SecureAllocator<std::u16string::value_type>>;
|
|
|
|
} // namespace crypto
|
|
|
|
#endif // CRYPTO_PROCESS_BOUND_STRING_H_
|