Skip to content

Commit 138fd8f

Browse files
committed
feat(secure_buffer): add lifecycle APIs and hardened zeroing
1 parent a717833 commit 138fd8f

4 files changed

Lines changed: 81 additions & 6 deletions

File tree

CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ option(HMACCPP_BUILD_BENCH "Build benchmarks" OFF)
77
option(HMACCPP_BUILD_SHARED "Build hmac_cpp as a shared library" OFF)
88
option(HMACCPP_ENABLE_MLOCK "Pin secret buffers in RAM using mlock/VirtualLock" ON)
99

10+
include(CheckSymbolExists)
11+
check_symbol_exists(explicit_bzero "strings.h" HAVE_EXPLICIT_BZERO)
12+
1013
if(HMACCPP_BUILD_SHARED)
1114
set(BUILD_SHARED_LIBS ON)
1215
endif()
@@ -45,13 +48,17 @@ target_include_directories(hmac_cpp PUBLIC
4548
if(BUILD_SHARED_LIBS)
4649
target_compile_definitions(hmac_cpp PRIVATE HMAC_CPP_BUILD)
4750
else()
48-
target_compile_definitions(hmac_cpp PUBLIC HMAC_CPP_STATIC)
51+
target_compile_definitions(hmac_cpp PUBLIC HMAC_CPP_STATIC)
4952
endif()
5053

5154
if(HMACCPP_ENABLE_MLOCK)
5255
target_compile_definitions(hmac_cpp PUBLIC HMAC_CPP_ENABLE_MLOCK)
5356
endif()
5457

58+
if(HAVE_EXPLICIT_BZERO)
59+
target_compile_definitions(hmac_cpp PUBLIC HAVE_EXPLICIT_BZERO)
60+
endif()
61+
5562
if(MSVC)
5663
target_compile_options(hmac_cpp PRIVATE /wd4251)
5764
else()

README-RU.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,9 @@ cl /EHsc example.cpp /I _install\\include /link /LIBPATH:_install\\lib hmac_cpp.
359359

360360
## 🔒 Примечания по безопасности
361361

362-
`secure_buffer` очищает память при разрушении. Он не закрепляет страницы в RAM,
363-
не предоставляет защиту страниц и не предотвращает атаки соседних буферов.
362+
`secure_buffer` очищает память при разрушении и обнуляет усечённый хвост при
363+
изменении размера. Он не закрепляет страницы в RAM, не предоставляет защиту
364+
страниц и не предотвращает атаки соседних буферов.
364365

365366
---
366367

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,8 +382,9 @@ cl /EHsc example.cpp /I _install\include /link /LIBPATH:_install\lib hmac_cpp.li
382382

383383
## 🔒 Security notes
384384

385-
`secure_buffer` wipes its memory on destruction. It does not page‑lock buffers,
386-
provide guard pages, or mitigate neighboring memory attacks.
385+
`secure_buffer` wipes its memory on destruction and zeroizes truncated bytes on
386+
resize. It does not page‑lock buffers, provide guard pages, or mitigate
387+
neighboring memory attacks.
387388

388389
---
389390

include/hmac_cpp/secure_buffer.hpp

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
#ifndef HMAC_CPP_SECURE_BUFFER_HPP
22
#define HMAC_CPP_SECURE_BUFFER_HPP
33

4+
#include <atomic>
45
#include <cstddef>
56
#include <cstdint>
7+
#include <cstring>
68
#include <vector>
79
#include <type_traits>
810
#include <string>
911
#include "hmac_cpp/memlock.hpp"
1012

13+
#if defined(HAVE_EXPLICIT_BZERO)
14+
#include <strings.h>
15+
#endif
16+
1117
// Macro to mark deprecated APIs in a compiler-portable way
1218
#ifndef HMACCPP_DEPRECATED
1319
#if defined(__clang__) || defined(__GNUC__)
@@ -25,10 +31,17 @@ namespace hmac_cpp {
2531
/// \param ptr Pointer to the memory to wipe.
2632
/// \param len Number of bytes to set to zero.
2733
inline void secure_zero(void* ptr, size_t len) {
34+
#if defined(__STDC_LIB_EXT1__)
35+
(void)memset_s(ptr, len, 0, len);
36+
#elif defined(HAVE_EXPLICIT_BZERO)
37+
explicit_bzero(ptr, len);
38+
#else
2839
volatile unsigned char* p = static_cast<volatile unsigned char*>(ptr);
2940
while (len--) {
3041
*p++ = 0;
3142
}
43+
std::atomic_signal_fence(std::memory_order_seq_cst);
44+
#endif
3245
}
3346

3447
/// \brief Vector-like buffer that zeroizes its contents on destruction.
@@ -114,11 +127,64 @@ struct secure_buffer {
114127
}
115128

116129
/// \brief Zeroize contents on destruction.
117-
~secure_buffer() {
130+
~secure_buffer() noexcept { clear(); }
131+
132+
/// \brief Check whether pages are locked.
133+
bool is_locked() const noexcept { return locked_; }
134+
135+
/// \brief Clear and deallocate the buffer.
136+
void clear() noexcept {
137+
secure_zero(buf.data(), buf.size() * sizeof(T));
138+
if (locked_) {
139+
unlock_pages(buf.data(), buf.size() * sizeof(T));
140+
locked_ = false;
141+
}
142+
buf.clear();
143+
buf.shrink_to_fit();
144+
}
145+
146+
/// \brief Resize the buffer, zeroizing truncated data.
147+
void resize(size_t n) {
148+
T* old_ptr = buf.data();
149+
size_t old_sz = buf.size();
150+
if (n < old_sz) {
151+
secure_zero(old_ptr + n, (old_sz - n) * sizeof(T));
152+
}
153+
buf.resize(n);
154+
if (LockOnAlloc && old_ptr != buf.data()) {
155+
if (locked_) {
156+
unlock_pages(old_ptr, old_sz * sizeof(T));
157+
}
158+
if (!buf.empty()) {
159+
locked_ = lock_pages(buf.data(), buf.size() * sizeof(T));
160+
} else {
161+
locked_ = false;
162+
}
163+
}
164+
}
165+
166+
/// \brief Assign from raw pointer.
167+
void assign(const T* p, size_t n) {
118168
secure_zero(buf.data(), buf.size() * sizeof(T));
119169
if (locked_) {
120170
unlock_pages(buf.data(), buf.size() * sizeof(T));
121171
}
172+
buf.assign(p, p + n);
173+
if (LockOnAlloc && !buf.empty()) {
174+
locked_ = lock_pages(buf.data(), buf.size() * sizeof(T));
175+
} else {
176+
locked_ = false;
177+
}
178+
}
179+
180+
/// \brief Assign from std::string rvalue and zeroize the source.
181+
template<class U = T, typename std::enable_if<std::is_same<U, uint8_t>::value, int>::type = 0>
182+
void assign(std::string&& s) {
183+
assign(reinterpret_cast<const uint8_t*>(s.data()), s.size());
184+
if (!s.empty()) {
185+
secure_zero(&s[0], s.size());
186+
s.clear();
187+
}
122188
}
123189

124190
T* data() { return buf.data(); }

0 commit comments

Comments
 (0)