7

So I'm really intrigued about whether or not it can survive aggressive optimization tactics employed by GCC and clang.

Considering the following example:

void* clean(void* pointer, std::size_t size) noexcept
{
    return new(pointer) char[size]{};
}

void doStuff()
{
    //...
    clean(pointer, size);
    //...
}

Can I trust it with the task of cleaning sensitive data?

bit2shift
  • 656
  • 1
  • 9
  • 17
  • 2
    You cannot trust array placement new [at all](http://stackoverflow.com/q/8720425)! – Kerrek SB Mar 10 '16 at 17:03
  • @KerrekSB the example specified in that question isn't a basic type. The "y" mentioned in there is the alignment required by the given type, if I'm not mistaken. – bit2shift Mar 10 '16 at 17:11
  • @KerrekSB Just checked myself, [`clean(pointer, size);` doesn't add any overhead whatsoever aka alignment offset.](http://coliru.stacked-crooked.com/a/83952ed415cbd363) It would make no sense at all for there to be overhead when using an 1-byte aligned type. – bit2shift Mar 10 '16 at 17:23
  • I don't think you can trust *any* code you come up with yourself, you must search out a function provided for this purpose by your compiler or OS. – Mark Ransom Mar 10 '16 at 17:29
  • @MarkRansom A OS function for this purpose might be subjected to the same treatment memset gets, not to mention portability between GCC and clang is important to me. – bit2shift Mar 10 '16 at 17:43
  • 2
    "A OS function for this purpose might be subjected to the same treatment memset gets" No, it cannot. This is exactly the point of having such function. Compiler cannot remove it as it cannot prove abscense of side effects. – Revolver_Ocelot Mar 10 '16 at 18:12
  • @bit2shift a compiler can't predict side effects, so there's no possible way a function call could be optimized out. As for portability, one has to hope that the same function is available in both; I don't see any way around it. – Mark Ransom Mar 10 '16 at 18:13
  • @M.M: There is no correct answer. You cannot use array placement new portably. – Kerrek SB Mar 10 '16 at 20:34
  • @KerrekSB your first comment should probably be an answer to this question then: since placement `new[]` cannot be used in the first place, the question is moot. (I guess we could say that `new(pointer) char[size]{};` causes UB because it is permitted to write more than `size` bytes). – M.M Mar 10 '16 at 20:44
  • @KerrekSB you can, goddamn it, but you need to avoid STL types on them. – bit2shift Mar 10 '16 at 20:44
  • @M.M with that reasoning, so does memset() cause UB. – bit2shift Mar 10 '16 at 20:47
  • @bit2shift please explain how memset causes UB – M.M Mar 10 '16 at 20:49
  • @M.M considering a pointer to a memory with 10 bytes, doing `memset(ptr, 0, 11);` causes UB since you're writing out-of-bounds. – bit2shift Mar 10 '16 at 20:51
  • @bit2shift That's right. What does that have to do with this question? – M.M Mar 10 '16 at 20:51
  • @M.M that UB is guaranteed to happen if you pass a larger size to it. But in this case, the placement new DOES NOT write extra bytes considering we're talking about a fundamental type. Not a class or struct, with or without vtables and/or an inheritance tree. – bit2shift Mar 10 '16 at 20:57
  • 1
    @bit2shift the C++ Standard says that `new(pointer) char[size]` writes `size + y` bytes where `y` is an unspecifed value – M.M Mar 10 '16 at 21:00
  • @bit2shift see section 5.3.4/14 . Is discussed in more detail on [this thread](http://stackoverflow.com/questions/8720425/array-placement-new-requires-unspecified-overhead-in-the-buffer/35926866) – M.M Mar 10 '16 at 21:08
  • BTW, scrubbing secure data from memory is practically impossible in C or C++, e.g. discussions [here](http://stackoverflow.com/questions/10683941/clearing-memory-securely-and-reallocations), [here](http://www.daemonology.net/blog/2014-09-06-zeroing-buffers-is-insufficient.html) – M.M Mar 10 '16 at 21:14
  • @M.M Did you bother reading the [16^ answer](http://stackoverflow.com/a/14591969/2748628)? – bit2shift Mar 10 '16 at 21:19
  • 1
    @bit2shift Yep, sure did – M.M Mar 10 '16 at 21:22
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/105948/discussion-between-bit2shift-and-m-m). – bit2shift Mar 10 '16 at 21:24

2 Answers2

2

I do not think optimization can play any tricks on you here. Standard mandates value initialization in this case: new(pointer) char[size]{}, so after this call memory pointed to by pointer would be filled with 0.

May be compiler can optimize it if you never access the new pointer or override it before accessin (based on observability). If you want to avoid this slight possibility, you'd need to define your pointer as a pointer to volatile.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • But after, the buffer is unused. – Jarod42 Mar 10 '16 at 17:36
  • @Jarod42, yes, I thought about it and updated my answer. – SergeyA Mar 10 '16 at 17:38
  • In both scenarios I'm using this approach, the pointer either gets returned (allocation) or gets passed to a deallocation function leading to `std::free`. – bit2shift Mar 10 '16 at 17:47
  • @Jarod42, i think, cast to void* before placement new (to get rid of volatile) would be OK.... I think. – SergeyA Mar 10 '16 at 18:07
  • 3
    An optimizer is allowed to do anything that has no observable effect on the program. If it can establish that the object won't be used, and that the constructor has no side effects, then it is perfectly within its rights to skip the constructor entirely. – Mark Ransom Mar 10 '16 at 18:16
  • @SergeyA: if you remove the `volatile` for the `new` it is really useless. – Jarod42 Mar 10 '16 at 18:21
  • @Jarod42, I am not sure about it. It is hard to say what qualifies for observable effect anymore. I honestly don't know. – SergeyA Mar 10 '16 at 18:29
  • @SergeyA "observable behaviour" is defined by section [intro.execution]/8 of the C++ standard – M.M Mar 10 '16 at 21:03
2

I am not sure whether this is an answer to your question or just a side note but you can disable optimization on that specific function using optimize() compiler directive

void* __attribute__((optimize("O0"))) clean(void* pointer, std::size_t size) {
    // unmodifiable compiler code
}

This will ensure your clean() function will not be optimized away

DanielHsH
  • 4,287
  • 3
  • 30
  • 36