Securely clear memory containing key information / passwords before freeing.
The previously used "memset(ptr, 0, size)" can get optimized away by compilers
if "ptr" is not used afterwards.
A new class "ZeroOnFreeBuffer" is introduced that can hold sensitive data and
that automatically clears underlying memory when it's no longer used.
Bug: webrtc:8806, webrtc:8897, webrtc:8905
Change-Id: Iedddddf80790f9af0addaab3346ec5bff102917d
Reviewed-on: https://webrtc-review.googlesource.com/41941
Commit-Queue: Joachim Bauch <jbauch@webrtc.org>
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22334}
diff --git a/rtc_base/buffer.h b/rtc_base/buffer.h
index eff3e40..64974d3 100644
--- a/rtc_base/buffer.h
+++ b/rtc_base/buffer.h
@@ -20,6 +20,7 @@
#include "api/array_view.h"
#include "rtc_base/checks.h"
#include "rtc_base/type_traits.h"
+#include "rtc_base/zero_memory.h"
namespace rtc {
@@ -44,7 +45,10 @@
// Basic buffer class, can be grown and shrunk dynamically.
// Unlike std::string/vector, does not initialize data when increasing size.
-template <typename T>
+// If "ZeroOnFree" is true, any memory is explicitly cleared before releasing.
+// The type alias "ZeroOnFreeBuffer" below should be used instead of setting
+// "ZeroOnFree" in the template manually to "true".
+template <typename T, bool ZeroOnFree = false>
class BufferT {
// We want T's destructor and default constructor to be trivial, i.e. perform
// no action, so that we don't have to touch the memory we allocate and
@@ -108,6 +112,8 @@
internal::BufferCompat<T, U>::value>::type* = nullptr>
BufferT(U (&array)[N]) : BufferT(array, N) {}
+ ~BufferT() { MaybeZeroCompleteBuffer(); }
+
// Get a pointer to the data. Just .data() will give you a (const) T*, but if
// T is a byte-sized integer, you may also use .data<U>() for any other
// byte-sized integer U.
@@ -195,8 +201,12 @@
internal::BufferCompat<T, U>::value>::type* = nullptr>
void SetData(const U* data, size_t size) {
RTC_DCHECK(IsConsistent());
+ const size_t old_size = size_;
size_ = 0;
AppendData(data, size);
+ if (ZeroOnFree && size_ < old_size) {
+ ZeroTrailingData(old_size - size_);
+ }
}
template <typename U,
@@ -229,8 +239,13 @@
internal::BufferCompat<T, U>::value>::type* = nullptr>
size_t SetData(size_t max_elements, F&& setter) {
RTC_DCHECK(IsConsistent());
+ const size_t old_size = size_;
size_ = 0;
- return AppendData<U>(max_elements, std::forward<F>(setter));
+ const size_t written = AppendData<U>(max_elements, std::forward<F>(setter));
+ if (ZeroOnFree && size_ < old_size) {
+ ZeroTrailingData(old_size - size_);
+ }
+ return written;
}
// The AppendData functions add data to the end of the buffer. They accept
@@ -301,8 +316,12 @@
// the existing contents will be kept and the new space will be
// uninitialized.
void SetSize(size_t size) {
+ const size_t old_size = size_;
EnsureCapacityWithHeadroom(size, true);
size_ = size;
+ if (ZeroOnFree && size_ < old_size) {
+ ZeroTrailingData(old_size - size_);
+ }
}
// Ensure that the buffer size can be increased to at least capacity without
@@ -317,6 +336,7 @@
// Resets the buffer to zero size without altering capacity. Works even if the
// buffer has been moved from.
void Clear() {
+ MaybeZeroCompleteBuffer();
size_ = 0;
RTC_DCHECK(IsConsistent());
}
@@ -346,11 +366,29 @@
std::unique_ptr<T[]> new_data(new T[new_capacity]);
std::memcpy(new_data.get(), data_.get(), size_ * sizeof(T));
+ MaybeZeroCompleteBuffer();
data_ = std::move(new_data);
capacity_ = new_capacity;
RTC_DCHECK(IsConsistent());
}
+ // Zero the complete buffer if template argument "ZeroOnFree" is true.
+ void MaybeZeroCompleteBuffer() {
+ if (ZeroOnFree && capacity_) {
+ // It would be sufficient to only zero "size_" elements, as all other
+ // methods already ensure that the unused capacity contains no sensitive
+ // data - but better safe than sorry.
+ ExplicitZeroMemory(data_.get(), capacity_ * sizeof(T));
+ }
+ }
+
+ // Zero the first "count" elements of unused capacity.
+ void ZeroTrailingData(size_t count) {
+ RTC_DCHECK(IsConsistent());
+ RTC_DCHECK_LE(count, capacity_ - size_);
+ ExplicitZeroMemory(data_.get() + size_, count * sizeof(T));
+ }
+
// Precondition for all methods except Clear and the destructor.
// Postcondition for all methods except move construction and move
// assignment, which leave the moved-from object in a possibly inconsistent
@@ -382,6 +420,10 @@
// By far the most common sort of buffer.
using Buffer = BufferT<uint8_t>;
+// A buffer that zeros memory before releasing it.
+template <typename T>
+using ZeroOnFreeBuffer = BufferT<T, true>;
+
} // namespace rtc
#endif // RTC_BASE_BUFFER_H_