c++security

Vector securely erasing its memory


I need to create a secure, self cleaning memory buffer, that leaves no traces of its content behind in memory when being cleared or going out of scope.

My example code shows a comparison between my first experimental vectorc and the std::vector. You can see in the output, that vectorc overwrites its memory when calling clear() while the content of the std::vector is still accessible after clear().

I fear that the compiler may optimize away the memory overwriting in some cases, if the memory is no longer accessed after clear() or going out of scope (destructor). Is using volatile valid, like in the line commented out in clear()? The memset() does not like volatile void*, so I had to cast it away again. There must be a better way of doing this.

I know that a growing vector and a resize may leave content behind, as the memory block may be moved to a different location, but let's ignore this point to keep the example short.

I access memory behind size() up to the capacity(). This may lead to undefined behavior, or is it okay the way I do it?

Any thoughts on the topic? Any better solutions?

vectorc.cpp:

#include <vector>
#include <cstring>
#include <iostream>

template<typename T> class vectorc : public std::vector<T>
{
  public:

  ~vectorc()
  {
    clear();
  }

  void clear()
  {
    // volatile T* ptr = std::vector<T>::data(); ???
    T* ptr = std::vector<T>::data();
    memset((void*)ptr, 0, std::vector<T>::capacity()); // May have been shrinked, so size may leave memory behind?!
    std::vector<T>::clear();    
  }
};

void printmem(const std::vector<char>& v)
{
   const char* ptr = v.data();
   std::cout << "capacity=" << v.capacity() << ", size=" << v.size() << ", content=\"";
   for(size_t i = 0; i < v.capacity(); ++i, ++ptr)
     std::cout << *ptr;
   std::cout << "\"" << std::endl;
}

int main()
{
  std::cout << "start" << std::endl;

  std::cout << "vectorc:" << std::endl;
  {
    vectorc<char> vs;
    vs.push_back('s');
    vs.push_back('e');
    vs.push_back('c');
    vs.push_back('r');
    vs.push_back('e');
    vs.push_back('t');

    printmem(vs);
    vs.clear();
    printmem(vs);
  }
  // vs went out of scope and I want all it's allocated memory set to zero.

  std::cout << "std::vector:" << std::endl;
  {
    std::vector<char> vp;
    vp.push_back('p');
    vp.push_back('u');
    vp.push_back('b');
    vp.push_back('l');
    vp.push_back('i');
    vp.push_back('c');

    printmem(vp);
    vp.clear();
    printmem(vp);
  }

  std::cout << "end" << std::endl;
}

Compile and run:

g++ vectorc.cpp
./a.out

Output:

start
vectorc:
capacity=8, size=6, content="secret"
capacity=8, size=0, content=""
std::vector:
capacity=8, size=6, content="public"
capacity=8, size=0, content="public"
end

Solution

  • Since search engines seems to always put this question on top of their results for secure erasing of std::vector memory, it will be helpful if there is an answer with the solution even if the comments already indicate the use of custom allocator.

    So below is an implementation that uses a custom allocator that extends the standard std::allocator class by providing secure erazing of memory upon deallocation. It is actually based on a blog post that predates OP question by 3 years: https://blog.noser.com/securely-deallocate-a-stdvector-or-a-stdstring/

    // Secure erazing of memory macro for Windows and non-Windows systems
    #if defined(_WIN32)
    #include <windows.h>
    #define secure_zeromem(mem,size) do { RtlSecureZeroMemory (mem, size); } while (0)
    #else
    #define secure_zeromem(mem,size) do { volatile char *burnm = (volatile char *)(mem); int burnc = size; while (burnc--) *burnm++ = 0; } while (0)
    #endif
    
    
    template <class T>
    class SecureAllocator
        : public std::allocator<T>
    {
    public:
        template<class U> struct rebind
        {
            typedef SecureAllocator<U> other;
        };
    
        SecureAllocator() throw()
            : std::allocator<T>()
        {
    
        }
    
        SecureAllocator(const SecureAllocator& other) throw()
            : std::allocator<T>(other)
        {
    
        }
    
        template <class U> SecureAllocator(const SecureAllocator<U>& other) throw()
            : std::allocator<T>(other)
        {
    
        }
    
    
        void deallocate(T* ptr, std::size_t n) {
            if (ptr != nullptr && n > 0) {
                secure_zeromem(ptr, n * sizeof(T));
            }
            std::allocator<T>::deallocate(ptr, n);
        }
    };
    
    typedef std::vector<unsigned char, SecureAllocator<unsigned char> > SecureVector;
    typedef std::basic_string<char, std::char_traits<char>, SecureAllocator<char> > SecureString;