diff --git a/include/etl/basic_string.h b/include/etl/basic_string.h index 096c30b3..e3effdf0 100644 --- a/include/etl/basic_string.h +++ b/include/etl/basic_string.h @@ -50,6 +50,7 @@ SOFTWARE. #include "error_handler.h" #include "integral_limits.h" #include "exception.h" +#include "memory.h" #undef ETL_FILE #define ETL_FILE "27" @@ -229,15 +230,32 @@ namespace etl is_truncated = false; } + //************************************************************************* + /// Sets the 'secure' flag to the requested state. + //************************************************************************* + void set_secure() + { + clear_afer_use = true; + } + + //************************************************************************* + /// Gets the 'secure' state flag. +//************************************************************************* + bool is_secure() const + { + return clear_afer_use; + } + protected: //************************************************************************* /// Constructor. //************************************************************************* string_base(size_t max_size_) - : is_truncated(false), - current_size(0), - CAPACITY(max_size_) + : is_truncated(false) + , clear_afer_use(false) + , current_size(0) + , CAPACITY(max_size_) { } @@ -248,9 +266,10 @@ namespace etl { } - bool is_truncated; ///< Set to true if the operation truncated the string. - size_type current_size; ///< The current number of elements in the string. - const size_type CAPACITY; ///< The maximum number of elements in the string. + bool is_truncated; ///< Set to true if the operation truncated the string. + bool clear_afer_use; ///< Set to true if the string must be cleared after use. + size_type current_size; ///< The current number of elements in the string. + const size_type CAPACITY; ///< The maximum number of elements in the string. }; //*************************************************************************** @@ -531,6 +550,8 @@ namespace etl { is_truncated = true; } + + cleanup(); } //********************************************************************* @@ -1059,6 +1080,7 @@ namespace etl current_size -= n_delete; p_buffer[current_size] = 0; + cleanup(); return first; } @@ -1383,6 +1405,11 @@ namespace etl // Insert the new stuff. insert(position, str, subposition, sublength); + if (str.truncated()) + { + is_truncated = true; + } + return *this; } @@ -1983,7 +2010,8 @@ namespace etl void initialise() { current_size = 0; - p_buffer[0] = 0; + cleanup(); + p_buffer[0] = 0; is_truncated = false; } @@ -2037,9 +2065,25 @@ namespace etl } } - // Disable copy construction. + //************************************************************************* + /// Clear the unused trailing portion of the string. + //************************************************************************* + void cleanup() + { + if (is_secure()) + { + etl::memory_clear_range(&p_buffer[current_size], &p_buffer[CAPACITY]); + } + } + + //************************************************************************* + /// Disable copy construction. + //************************************************************************* ibasic_string(const ibasic_string&); + //************************************************************************* + /// Pointer to the string's buffer. + //************************************************************************* T* p_buffer; //************************************************************************* @@ -2047,15 +2091,17 @@ namespace etl //************************************************************************* #if defined(ETL_POLYMORPHIC_STRINGS) || defined(ETL_POLYMORPHIC_CONTAINERS) public: - virtual ~ibasic_string() - { - } + virtual #else protected: +#endif ~ibasic_string() { + if (is_secure()) + { + initialise(); + } } -#endif }; //*************************************************************************** diff --git a/include/etl/memory.h b/include/etl/memory.h index 69af6ed9..606e6a7a 100644 --- a/include/etl/memory.h +++ b/include/etl/memory.h @@ -1108,7 +1108,6 @@ namespace etl //***************************************************************************** /// A low level function that clears an object's memory to zero. - ///\param p Pointer to the memory. ///\param n Size of the memory. ///\ingroup memory @@ -1128,11 +1127,39 @@ namespace etl ///\ingroup memory //***************************************************************************** template - void memory_clear(T &object) + void memory_clear(volatile T &object) { memory_clear(reinterpret_cast(&object), sizeof(T)); } + //***************************************************************************** + /// A low level function that clears a range to zero. + ///\tparam T The type. + ///\param begin The first object in the range. + ///\param n The number of objects. + ///\ingroup memory + //***************************************************************************** + template + void memory_clear_range(volatile T* begin, size_t n) + { + memory_clear(reinterpret_cast(begin), n * sizeof(T)); + } + + //***************************************************************************** + /// A low level function that clears a range to zero. + ///\tparam T The type. + ///\param begin The first object in the range. + ///\param end One past the last object in the range. + ///\ingroup memory + //***************************************************************************** + template + void memory_clear_range(volatile T* begin, volatile T* end) + { + const size_t n = static_cast(std::distance(begin, end)); + + memory_clear_range(begin, n); + } + //***************************************************************************** /// A low level function that clears an object's memory to zero. ///\param p Pointer to the memory. @@ -1156,10 +1183,56 @@ namespace etl ///\ingroup memory //***************************************************************************** template - void memory_set(T &object, char value) + void memory_set(volatile T &object, const char value) { memory_set(reinterpret_cast(&object), sizeof(T), value); } + + //***************************************************************************** + /// A low level function that clears a range to zero. + ///\tparam T The type. + ///\param begin The first object in the range. + ///\param n The number of objects. + ///\param value The value to set the object's memory to. + ///\ingroup memory + //***************************************************************************** + template + void memory_set_range(volatile T* begin, size_t n, const char value) + { + memory_set(reinterpret_cast(begin), n * sizeof(T), value); + } + + //***************************************************************************** + /// A low level function that clears a range to zero. + ///\tparam T The type. + ///\param begin The first object in the range. + ///\param end One past the last object in the range. + ///\param value The value to set the object's memory to. + ///\ingroup memory + //***************************************************************************** + template + void memory_set_range(volatile T* begin, volatile T* end, const char value) + { + const size_t n = static_cast(std::distance(begin, end)); + + memory_set_range(begin, n, value); + } + + //***************************************************************************** + /// Base class for objects that require their memory to be wiped after use. + /// Erases the object's memory to zero. + /// Note: This may not work for multiply inherited objects. + ///\tparam T The derived type. + ///\ingroup memory + //***************************************************************************** + template + struct wipe_on_destruct + { + ~wipe_on_destruct() + { + memory_clear(static_cast(*this)); + } + }; } #endif diff --git a/include/etl/version.h b/include/etl/version.h index ab365918..da98a129 100644 --- a/include/etl/version.h +++ b/include/etl/version.h @@ -38,8 +38,8 @@ SOFTWARE. ///\ingroup utilities #define ETL_VERSION_MAJOR 14 -#define ETL_VERSION_MINOR 22 -#define ETL_VERSION_PATCH 1 +#define ETL_VERSION_MINOR 23 +#define ETL_VERSION_PATCH 0 #define ETL_VERSION ETL_STRINGIFY(ETL_VERSION_MAJOR) ETL_STRINGIFY(ETL_VERSION_MINOR) ETL_STRINGIFY(ETL_VERSION_PATCH) #define ETL_VERSION_W ETL_WIDE_STRING(ETL_CONCAT(ETL_CONCAT(ETL_VERSION_MAJOR, ETL_VERSION_MINOR), ETL_VERSION_PATCH)) diff --git a/support/Release notes.txt b/support/Release notes.txt index e4201fa2..42f12388 100644 --- a/support/Release notes.txt +++ b/support/Release notes.txt @@ -1,3 +1,8 @@ +=============================================================================== +14.23.0 +Added an optional secure mode to strings so that unused space will be cleared to zero +and also cleared on destruction. + =============================================================================== 14.22.1 Modified memory functions so that they will not be optimised away. diff --git a/test/test_memory.cpp b/test/test_memory.cpp index 49b0a3fb..68a62caa 100644 --- a/test/test_memory.cpp +++ b/test/test_memory.cpp @@ -546,6 +546,52 @@ namespace CHECK_EQUAL(0x00, data.d2); } + //************************************************************************* + TEST(test_memory_clear_range_pointer_n) + { + struct Data + { + uint32_t d1; + char d2; + }; + + Data data[3] = { { 0xFFFFFFFF, char(0xFF) }, { 0xFFFFFFFF, char(0xFF) }, { 0xFFFFFFFF, char(0xFF) } }; + + etl::memory_clear_range(data, 3); + + CHECK_EQUAL(0x00000000, data[0].d1); + CHECK_EQUAL(0x00, data[0].d2); + + CHECK_EQUAL(0x00000000, data[1].d1); + CHECK_EQUAL(0x00, data[1].d2); + + CHECK_EQUAL(0x00000000, data[2].d1); + CHECK_EQUAL(0x00, data[2].d2); + } + + //************************************************************************* + TEST(test_memory_clear_range_pointer_pointer) + { + struct Data + { + uint32_t d1; + char d2; + }; + + Data data[3] = { { 0xFFFFFFFF, char(0xFF) }, { 0xFFFFFFFF, char(0xFF) }, { 0xFFFFFFFF, char(0xFF) } }; + + etl::memory_clear_range(std::begin(data), std::end(data)); + + CHECK_EQUAL(0x00000000, data[0].d1); + CHECK_EQUAL(0x00, data[0].d2); + + CHECK_EQUAL(0x00000000, data[1].d1); + CHECK_EQUAL(0x00, data[1].d2); + + CHECK_EQUAL(0x00000000, data[2].d1); + CHECK_EQUAL(0x00, data[2].d2); + } + //************************************************************************* TEST(test_memory_set) { @@ -562,5 +608,51 @@ namespace CHECK_EQUAL(0x5A5A5A5A, data.d1); CHECK_EQUAL(0x5A, data.d2); } + + //************************************************************************* + TEST(test_memory_set_range_pointer_n) + { + struct Data + { + uint32_t d1; + char d2; + }; + + Data data[3] = { { 0xFFFFFFFF, char(0xFF) }, { 0xFFFFFFFF, char(0xFF) }, { 0xFFFFFFFF, char(0xFF) } }; + + etl::memory_set_range(data, 3, 0x5A); + + CHECK_EQUAL(0x5A5A5A5A, data[0].d1); + CHECK_EQUAL(0x5A, data[0].d2); + + CHECK_EQUAL(0x5A5A5A5A, data[1].d1); + CHECK_EQUAL(0x5A, data[1].d2); + + CHECK_EQUAL(0x5A5A5A5A, data[2].d1); + CHECK_EQUAL(0x5A, data[2].d2); + } + + //************************************************************************* + TEST(test_memory_set_range_pointer_pointer) + { + struct Data + { + uint32_t d1; + char d2; + }; + + Data data[3] = { { 0xFFFFFFFF, char(0xFF) }, { 0xFFFFFFFF, char(0xFF) }, { 0xFFFFFFFF, char(0xFF) } }; + + etl::memory_set_range(std::begin(data), std::end(data), 0x5A); + + CHECK_EQUAL(0x5A5A5A5A, data[0].d1); + CHECK_EQUAL(0x5A, data[0].d2); + + CHECK_EQUAL(0x5A5A5A5A, data[1].d1); + CHECK_EQUAL(0x5A, data[1].d2); + + CHECK_EQUAL(0x5A5A5A5A, data[2].d1); + CHECK_EQUAL(0x5A, data[2].d2); + } }; } diff --git a/test/test_string_char.cpp b/test/test_string_char.cpp index b40a4ae4..9d5644fc 100644 --- a/test/test_string_char.cpp +++ b/test/test_string_char.cpp @@ -3651,5 +3651,90 @@ namespace text.clear_truncated(); CHECK(!text.truncated()); } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_destructor) + { + char buffer[sizeof(Text)]; + std::fill_n(buffer, sizeof(Text), 0); + ::new (buffer) Text(STR("ABCDEF")); + + Text& text = *reinterpret_cast(buffer); + text.set_secure(); + + CHECK(Text(STR("ABCDEF")) == text); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + // Destroy the text object. + text.~Text(); + + // Check there no non-zero values in the string. + CHECK(std::find_if(pb, pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_assign) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.assign(STR("ABC")); + + CHECK(std::find_if(text.end(), pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_erase) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.erase(pb + 2, pb + 5); + + // Check there no non-zero values in the remainder of the string. + CHECK(std::find_if(text.end(), pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_replace) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.replace(pb + 1, pb + 4, STR("G")); + + // Check there no non-zero values in the remainder of the string. + CHECK(std::find_if(text.end(), pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_clear) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.clear(); + + // Check there no non-zero values in the remainder of the string. + CHECK(std::find_if(pb, pe, [](Text::value_type x) { return x != 0; }) == pe); + } }; } diff --git a/test/test_string_u16.cpp b/test/test_string_u16.cpp index ba4c94af..08382b8c 100644 --- a/test/test_string_u16.cpp +++ b/test/test_string_u16.cpp @@ -3650,5 +3650,90 @@ namespace text.clear_truncated(); CHECK(!text.truncated()); } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_destructor) + { + char buffer[sizeof(Text)]; + std::fill_n(buffer, sizeof(Text), 0); + ::new (buffer) Text(STR("ABCDEF")); + + Text& text = *reinterpret_cast(buffer); + text.set_secure(); + + CHECK(Text(STR("ABCDEF")) == text); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + // Destroy the text object. + text.~Text(); + + // Check there no non-zero values in the string. + CHECK(std::find_if(pb, pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_assign) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.assign(STR("ABC")); + + CHECK(std::find_if(text.end(), pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_erase) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.erase(pb + 2, pb + 5); + + // Check there no non-zero values in the remainder of the string. + CHECK(std::find_if(text.end(), pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_replace) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.replace(pb + 1, pb + 4, STR("G")); + + // Check there no non-zero values in the remainder of the string. + CHECK(std::find_if(text.end(), pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_clear) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.clear(); + + // Check there no non-zero values in the remainder of the string. + CHECK(std::find_if(pb, pe, [](Text::value_type x) { return x != 0; }) == pe); + } }; } diff --git a/test/test_string_u32.cpp b/test/test_string_u32.cpp index 4acfeb8a..55e9400a 100644 --- a/test/test_string_u32.cpp +++ b/test/test_string_u32.cpp @@ -3650,5 +3650,90 @@ namespace text.clear_truncated(); CHECK(!text.truncated()); } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_destructor) + { + char buffer[sizeof(Text)]; + std::fill_n(buffer, sizeof(Text), 0); + ::new (buffer) Text(STR("ABCDEF")); + + Text& text = *reinterpret_cast(buffer); + text.set_secure(); + + CHECK(Text(STR("ABCDEF")) == text); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + // Destroy the text object. + text.~Text(); + + // Check there no non-zero values in the string. + CHECK(std::find_if(pb, pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_assign) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.assign(STR("ABC")); + + CHECK(std::find_if(text.end(), pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_erase) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.erase(pb + 2, pb + 5); + + // Check there no non-zero values in the remainder of the string. + CHECK(std::find_if(text.end(), pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_replace) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.replace(pb + 1, pb + 4, STR("G")); + + // Check there no non-zero values in the remainder of the string. + CHECK(std::find_if(text.end(), pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_clear) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.clear(); + + // Check there no non-zero values in the remainder of the string. + CHECK(std::find_if(pb, pe, [](Text::value_type x) { return x != 0; }) == pe); + } }; } diff --git a/test/test_string_wchar_t.cpp b/test/test_string_wchar_t.cpp index 0aba10d1..a00d8e02 100644 --- a/test/test_string_wchar_t.cpp +++ b/test/test_string_wchar_t.cpp @@ -3650,5 +3650,90 @@ namespace text.clear_truncated(); CHECK(!text.truncated()); } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_destructor) + { + char buffer[sizeof(Text)]; + std::fill_n(buffer, sizeof(Text), 0); + ::new (buffer) Text(STR("ABCDEF")); + + Text& text = *reinterpret_cast(buffer); + text.set_secure(); + + CHECK(Text(STR("ABCDEF")) == text); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + // Destroy the text object. + text.~Text(); + + // Check there no non-zero values in the string. + CHECK(std::find_if(pb, pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_assign) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.assign(STR("ABC")); + + CHECK(std::find_if(text.end(), pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_erase) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.erase(pb + 2, pb + 5); + + // Check there no non-zero values in the remainder of the string. + CHECK(std::find_if(text.end(), pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_replace) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.replace(pb + 1, pb + 4, STR("G")); + + // Check there no non-zero values in the remainder of the string. + CHECK(std::find_if(text.end(), pe, [](Text::value_type x) { return x != 0; }) == pe); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_secure_after_clear) + { + Text text; + text.set_secure(); + text.assign(STR("ABCDEF")); + + Text::pointer pb = text.begin(); + Text::pointer pe = text.end(); + + text.clear(); + + // Check there no non-zero values in the remainder of the string. + CHECK(std::find_if(pb, pe, [](Text::value_type x) { return x != 0; }) == pe); + } }; }