Sunday 25 February 2018

C++14 std::memset Not Working?

When I say not working, I do not mean the function does not work, of course it works.  What I mean is that left to it's on devices the GCC compiler can actually chose to not perform a memset in certain circumstances, you can read more detail about this in the official bug list.

So, what's going on?  Well, I have a piece of code which uses a common buffer, each function locks a mutex (std::lock_guard), fills out the buffer and then transmits over USB to target devices, crucially I then want to clear the common buffer.

The purpose of this architecture is to keep sensitive information being transmitted through the buffer present for as little time as possible (i.e. the time between filling out and transmit, ~35ms) rather than filling out, transmitting and leaving the data in the buffer; as the time between calls may be anything up to 5 seconds, plenty of time for someone to halt the program and inspect memory dispositions.

In pseudo code therefore our sequence looks something like this:

COMMON BUFFER [256]
CLEAR BUFFER

USB CALL:
    FILL  BUFFER
    USB TRANSMIT
    std::memset(BUFFER, 0, 256);

In debug this worked no problems, all my soak testing worked, I was quite happy.

Until one switches to release, whereupon the GCC compiler can opt to optimise away calls which it defines as not having any noticeable effect.

In the case of the memset call here, to the compiler, locally sees nothing inspect the BUFFER variable after the memset is performed, it therefore decides nothing is bothered the buffer is cleared or not.

Of course we know the next call to any call, or the same call, will rely on the BUFFER being empty when it enters.

There are therefore two problems, if one had this code:

USB CALL:
    std::memset(BUFFER, 0, 250);
    FILL  BUFFER
    USB TRANSMIT
    std::memset(BUFFER, 0, 256);

You would of course pass all your tests, even in release, the buffer would work as expected, you could even assume no data is being left in RAM between transmit calls; you'd be dead wrong, but you can assume anything you like really.  No, what's happening here of course is that the lead-in memset is clearing the buffer before use, but the sensitive data is left in the buffer between calls as the latter memset is still being optimised away.

The code remains vulnerable to introspective intrusion attempts.

The real solution?  Well, I've gone with:

USB CALL:
    std::memset(BUFFER, 0, 250);
    FILL  BUFFER
    USB TRANSMIT
    std::memset(BUFFER, 0, 256);
    auto l_temp(buffer[0]);
    buffer[0] = buffer[1]
    buffer[0] = l_temp;

Whatever you do to solve this, don't spend a whole day as I just have.

In conclusion, of course the call works, it's the compiler which is optimising the call away.

No comments:

Post a Comment