hauron
hauron

Reputation: 4668

How to cleanse (overwrite with random bytes) std::string internal buffer?

Consider a scenario, where std::string is used to store a secret. Once it is consumed and is no longer needed, it would be good to cleanse it, i.e overwrite the memory that contained it, thus hiding the secret.

std::string provides a function const char* data() returning a pointer to (since C++11) continous memory.

Now, since the memory is continous and the variable will be destroyed right after the cleanse due to scope end, would it be safe to:

char* modifiable = const_cast<char*>(secretString.data());
OpenSSL_cleanse(modifiable, secretString.size());

According to standard quoted here:

$5.2.11/7 - Note: Depending on the type of the object, a write operation through the pointer, lvalue or pointer to data member resulting from a const_cast that casts away a const-qualifier68 may produce undefined behavior (7.1.5.1).

That would advise otherwise, but do the conditions above (continuous, to-be-just-removed) make it safe?

Upvotes: 25

Views: 3466

Answers (6)

ericcurtin
ericcurtin

Reputation: 1717

Tested solution on CentOS 6, Debian 8 and Ubuntu 16.04 (g++/clang++, O0, O1, O2, O3):

secretString.resize(secretString.capacity(), '\0');
OPENSSL_cleanse(&secretString[0], secretString.size());
secretString.clear();

If you were really paranoid you could randomise the data in the cleansed string, so as not to give away the length of the string or a location that contained sensitive data:

#include <string>

#include <stdlib.h>
#include <string.h>

typedef void* (*memset_t)(void*, int, size_t);

static volatile memset_t memset_func = memset;

void cleanse(std::string& to_cleanse) {
  to_cleanse.resize(to_cleanse.capacity(), '\0');
  for (int i = 0; i < to_cleanse.size(); ++i) {
    memset_func(&to_cleanse[i], rand(), 1);
  }
  to_cleanse.clear();
}

You could seed the rand() if you wanted also.

You could also do similar string cleansing without openssl dependency, by using explicit_bzero to null the contents:

#include <string>
#include <string.h>

int main() {
  std::string secretString = "ajaja";
  secretString.resize(secretString.capacity(), '\0');
  explicit_bzero(&secretString[0], secretString.size());
  secretString.clear();

  return 0;
}

Upvotes: 1

Cort Ammon
Cort Ammon

Reputation: 10883

There's a better answer: don't!

std::string is a class which is designed to be userfriendly and efficient. It was not designed with cryptography in mind, so there are few guarantees written into it to help you out. For example, there's no guarantees that your data hasn't been copied elsewhere. At best, you could hope that a particular compiler's implementation offers you the behavior you want.

If you actually want to treat a secret as a secret, you should handle it using tools which are designed for handling secrets. In fact, you should develop a threat model for what capabilities your attacker has, and choose your tools accordingly.

Upvotes: 4

Jonathan Wakely
Jonathan Wakely

Reputation: 171363

The standard explicitly says you must not write to the const char* returned by data(), so don't do that.

There are perfectly safe ways to get a modifiable pointer instead:

if (secretString.size())
  OpenSSL_cleanse(&secretString.front(), secretString.size());

Or if the string might have been shrunk already and you want to ensure its entire capacity is wiped:

if (secretString.capacity()) {
  secretString.resize(secretString.capacity());
  OpenSSL_cleanse(&secretString.front(), secretString.size());
}

Upvotes: 18

David Haim
David Haim

Reputation: 26506

You can use std::fill to fill the string with trash:

std::fill(str.begin(),str.end(), 0);

Do note that simply clearing or shrinking the string (with methods such clear or shrink_to_fit) does not guarantee that the string data will be deleted from the process memory. Malicious processes may dump the process memory and can extract the secret if the string is not overwritten correctly.

Bonus: Interestingly, the ability to trash the string data for security reasons forces some programming languages like Java to return passwords as char[] and not String. In Java, String is immutable, so "trashing" it will make a new copy of the string. Hence, you need a modifiable object like char[] which does not use copy-on-write.

Edit: if your compiler does optimize this call out, you can use specific compiler flags to make sure a trashing function will not be optimized out:

#ifdef WIN32

#pragma optimize("",off)
void trashString(std::string& str){
   std::fill(str.begin(),str.end(),0);
}
#pragma optimize("",on)

#endif

#ifdef __GCC__

void __attribute__((optimize("O0"))) trashString(std::string& str) {
       std::fill(str.begin(),str.end(),0);
}


#endif

#ifdef __clang__

void __attribute__ ((optnone))  trashString(std::string& str) {
       std::fill(str.begin(),str.end(),0);
}

#endif

Upvotes: 12

Galik
Galik

Reputation: 48635

It is probably safe. But not guaranteed.

However, since C++11, a std::string must be implemented as contiguous data so you can safely access its internal array using the address of its first element &secretString[0].

if(!secretString.empty()) // avoid UB
{
    char* modifiable = &secretString[0];
    OpenSSL_cleanse(modifiable, secretString.size());
}

Upvotes: 16

David Thomas
David Thomas

Reputation: 798

std::string is a poor choice to store secrets. Since strings are copyable and sometimes copies go unnoticed, your secret may "get legs". Furthermore, string expansion techniques may cause multiple copies of fragments (or all of) your secrets.

Experience dictates a movable, non-copyable, wiped clean on destroy, unintelligent (no tricky copies under-the-hood) class.

Upvotes: 14

Related Questions