11#ifndef HMAC_CPP_SECURE_BUFFER_HPP
22#define HMAC_CPP_SECURE_BUFFER_HPP
33
4- #include < atomic>
54#include < algorithm>
5+ #include < atomic>
66#include < cstddef>
77#include < cstdint>
88#include < cstring>
9- #include < vector>
10- #include < type_traits>
9+ #include < memory>
1110#include < string>
11+ #include < type_traits>
12+ #include < vector>
1213#include " hmac_cpp/memlock.hpp"
1314
1415#if defined(HAVE_EXPLICIT_BZERO)
2829
2930namespace hmac_cpp {
3031
32+ // / \brief Default page-locking policy used by secure_buffer.
33+ struct page_locker {
34+ static bool lock (void * ptr, size_t len) noexcept { return lock_pages (ptr, len); }
35+ static bool unlock (void * ptr, size_t len) noexcept { return unlock_pages (ptr, len); }
36+ };
37+
3138// / \brief Securely zeroes a memory region.
3239// / \param ptr Pointer to the memory to wipe.
3340// / \param len Number of bytes to set to zero.
@@ -48,81 +55,71 @@ static inline void secure_zero(void* ptr, size_t len) {
4855// / \brief Vector-like buffer that zeroizes its contents on destruction.
4956// / \tparam T Trivial value type stored in the buffer (defaults to uint8_t).
5057// / \tparam LockOnAlloc Lock pages in memory on allocation.
51- template <class T = uint8_t , bool LockOnAlloc = false >
58+ // / \tparam Allocator Allocator used for the backing storage.
59+ // / \tparam PageLocker Page-locking policy used when LockOnAlloc is true.
60+ template <class T = uint8_t ,
61+ bool LockOnAlloc = false ,
62+ class Allocator = std::allocator<T>,
63+ class PageLocker = page_locker>
5264struct secure_buffer {
5365 static_assert (std::is_trivial<T>::value, " secure_buffer requires trivial type" );
5466
67+ using vector_type = std::vector<T, Allocator>;
68+
5569 secure_buffer () {
56- if (LockOnAlloc && !buf.empty ()) {
57- locked_ = lock_pages (buf.data (), buf.size () * sizeof (T));
58- }
70+ lock_current_range ();
5971 }
6072
6173 // / \brief Construct with n default-initialized elements.
6274 // / \param n Element count.
6375 explicit secure_buffer (size_t n) : buf(n) {
64- if (LockOnAlloc && !buf.empty ()) {
65- locked_ = lock_pages (buf.data (), buf.size () * sizeof (T));
66- }
76+ lock_current_range ();
6777 }
6878
6979 // / \brief Construct from vector, moving its contents.
7080 // / \param v Source vector.
71- explicit secure_buffer (std::vector<T>&& v) : buf(std::move(v)) {
72- if (LockOnAlloc && !buf.empty ()) {
73- locked_ = lock_pages (buf.data (), buf.size () * sizeof (T));
74- }
81+ explicit secure_buffer (vector_type&& v) : buf(std::move(v)) {
82+ lock_current_range ();
7583 }
7684
7785 // / \brief Construct from std::string rvalue and zeroize the source.
7886 // / \param s Source string.
7987 template <class U = T, typename std::enable_if<std::is_same<U, uint8_t >::value, int >::type = 0 >
8088 explicit secure_buffer (std::string&& s) : buf(s.begin(), s.end()) {
81- if (LockOnAlloc && !buf.empty ()) {
82- locked_ = lock_pages (buf.data (), buf.size () * sizeof (T));
83- }
89+ lock_current_range ();
8490 if (!s.empty ()) {
8591 secure_zero (&s[0 ], s.size ());
8692 s.clear ();
8793 }
8894 }
8995
9096 secure_buffer (const secure_buffer& other) : buf(other.buf) {
91- if (LockOnAlloc && !buf.empty ()) {
92- locked_ = lock_pages (buf.data (), buf.size () * sizeof (T));
93- }
97+ lock_current_range ();
9498 }
9599
96100 secure_buffer& operator =(const secure_buffer& other) {
97101 if (this != &other) {
98- secure_zero (buf.data (), buf.size () * sizeof (T));
99- if (locked_) {
100- unlock_pages (buf.data (), buf.size () * sizeof (T));
101- }
102+ clear ();
102103 buf = other.buf ;
103- if (LockOnAlloc && !buf.empty ()) {
104- locked_ = lock_pages (buf.data (), buf.size () * sizeof (T));
105- } else {
106- locked_ = false ;
107- }
104+ lock_current_range ();
108105 }
109106 return *this ;
110107 }
111108
112109 secure_buffer (secure_buffer&& other) noexcept
113- : buf(std::move(other.buf)), locked_(other.locked_) {
114- other.locked_ = false ;
110+ : buf(std::move(other.buf)),
111+ locked_ptr_ (other.locked_ptr_),
112+ locked_bytes_(other.locked_bytes_) {
113+ other.clear_locked_range ();
115114 }
116115
117116 secure_buffer& operator =(secure_buffer&& other) noexcept {
118117 if (this != &other) {
119- secure_zero (buf.data (), buf.size () * sizeof (T));
120- if (locked_) {
121- unlock_pages (buf.data (), buf.size () * sizeof (T));
122- }
118+ clear ();
123119 buf = std::move (other.buf );
124- locked_ = other.locked_ ;
125- other.locked_ = false ;
120+ locked_ptr_ = other.locked_ptr_ ;
121+ locked_bytes_ = other.locked_bytes_ ;
122+ other.clear_locked_range ();
126123 }
127124 return *this ;
128125 }
@@ -131,17 +128,14 @@ struct secure_buffer {
131128 ~secure_buffer () noexcept { clear (); }
132129
133130 // / \brief Check whether pages are locked.
134- bool is_locked () const noexcept { return locked_ ; }
131+ bool is_locked () const noexcept { return locked_ptr_ != nullptr ; }
135132
136133 // / \brief Clear and deallocate the buffer.
137134 void clear () noexcept {
138135 secure_zero (buf.data (), buf.size () * sizeof (T));
139- if (locked_) {
140- unlock_pages (buf.data (), buf.size () * sizeof (T));
141- locked_ = false ;
142- }
143- buf.clear ();
144- buf.shrink_to_fit ();
136+ unlock_locked_range ();
137+ vector_type empty (buf.get_allocator ());
138+ buf.swap (empty);
145139 }
146140
147141 // / \brief Resize the buffer, zeroizing data before a reallocation releases it.
@@ -156,35 +150,26 @@ struct secure_buffer {
156150 }
157151
158152 // std::vector::resize may release the old allocation before we can
159- // wipe it. Allocate the replacement first so the old secret remains
160- // reachable until it has been zeroized and unlocked.
161- std::vector<T> replacement (n);
153+ // wipe it. Allocate and lock the replacement first so the old secret
154+ // remains reachable until it has been zeroized and unlocked.
155+ vector_type replacement (n, T{}, buf.get_allocator ());
156+ replacement_guard guard (replacement.data (), replacement.size () * sizeof (T));
157+ guard.lock ();
162158 std::copy (buf.begin (), buf.end (), replacement.begin ());
163159
164160 secure_zero (buf.data (), old_sz * sizeof (T));
165- if (locked_) {
166- unlock_pages (buf.data (), old_sz * sizeof (T));
167- locked_ = false ;
168- }
161+ unlock_locked_range ();
169162
170163 buf.swap (replacement);
171- if (LockOnAlloc && !buf.empty ()) {
172- locked_ = lock_pages (buf.data (), buf.size () * sizeof (T));
173- }
164+ set_locked_range (buf.data (), buf.size () * sizeof (T), guard.release ());
174165 }
175166
176167 // / \brief Assign from raw pointer.
177168 void assign (const T* p, size_t n) {
178169 secure_zero (buf.data (), buf.size () * sizeof (T));
179- if (locked_) {
180- unlock_pages (buf.data (), buf.size () * sizeof (T));
181- }
170+ unlock_locked_range ();
182171 buf.assign (p, p + n);
183- if (LockOnAlloc && !buf.empty ()) {
184- locked_ = lock_pages (buf.data (), buf.size () * sizeof (T));
185- } else {
186- locked_ = false ;
187- }
172+ lock_current_range ();
188173 }
189174
190175 // / \brief Assign from std::string rvalue and zeroize the source.
@@ -204,14 +189,73 @@ struct secure_buffer {
204189 T& operator [](size_t i) { return buf[i]; }
205190 const T& operator [](size_t i) const { return buf[i]; }
206191
207- typename std::vector<T> ::iterator begin () { return buf.begin (); }
208- typename std::vector<T> ::iterator end () { return buf.end (); }
209- typename std::vector<T> ::const_iterator begin () const { return buf.begin (); }
210- typename std::vector<T> ::const_iterator end () const { return buf.end (); }
192+ typename vector_type ::iterator begin () { return buf.begin (); }
193+ typename vector_type ::iterator end () { return buf.end (); }
194+ typename vector_type ::const_iterator begin () const { return buf.begin (); }
195+ typename vector_type ::const_iterator end () const { return buf.end (); }
211196
212197private:
213- std::vector<T> buf;
214- bool locked_{};
198+ struct replacement_guard {
199+ replacement_guard (void * ptr, size_t bytes) noexcept : ptr_(ptr), bytes_(bytes) {}
200+
201+ ~replacement_guard () {
202+ if (!active_) {
203+ return ;
204+ }
205+ secure_zero (ptr_, bytes_);
206+ if (locked_) {
207+ PageLocker::unlock (ptr_, bytes_);
208+ }
209+ }
210+
211+ void lock () noexcept {
212+ if (LockOnAlloc && ptr_ != nullptr && bytes_ != 0 ) {
213+ locked_ = PageLocker::lock (ptr_, bytes_);
214+ }
215+ }
216+
217+ bool release () noexcept {
218+ active_ = false ;
219+ return locked_;
220+ }
221+
222+ void * ptr_{};
223+ size_t bytes_{};
224+ bool locked_{};
225+ bool active_{true };
226+ };
227+
228+ void lock_current_range () noexcept {
229+ if (LockOnAlloc && !buf.empty ()) {
230+ set_locked_range (buf.data (), buf.size () * sizeof (T),
231+ PageLocker::lock (buf.data (), buf.size () * sizeof (T)));
232+ }
233+ }
234+
235+ void unlock_locked_range () noexcept {
236+ if (locked_ptr_ != nullptr ) {
237+ PageLocker::unlock (locked_ptr_, locked_bytes_);
238+ }
239+ clear_locked_range ();
240+ }
241+
242+ void set_locked_range (void * ptr, size_t bytes, bool locked) noexcept {
243+ if (locked) {
244+ locked_ptr_ = ptr;
245+ locked_bytes_ = bytes;
246+ } else {
247+ clear_locked_range ();
248+ }
249+ }
250+
251+ void clear_locked_range () noexcept {
252+ locked_ptr_ = nullptr ;
253+ locked_bytes_ = 0 ;
254+ }
255+
256+ vector_type buf;
257+ void * locked_ptr_{};
258+ size_t locked_bytes_{};
215259};
216260
217261} // namespace hmac_cpp
0 commit comments