8

I have some code in which I want to make absolutely sure that a moved-from std::vector will not leave secret data around (think about crypto key management). In my class' move constructor, I do something like:

X(X&& rhs): secret_vector{std::move(rhs.secret_vector)}{
    rhs.secret_vector.resize(N);
    safe_zero(rhs.secret_vector); // zero out all elements
    rhs.secret_vector.resize(0);
}

As you can see, I re-use the secret vector after moving from it. I looked at

Reusing a moved container?

but it was not absolutely clear that I can do this (I did not understand what "pre-conditions" really are).

My question is: can I resize a moved-from std::vector, perform some operation on it, then resize it back to zero?

vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • `std::move` does not move the _content_ of the std::vector. So if you want to zero out the _content_ of the vector, you can simply `safe_zero` _it_, no need to zero `secret_vector` after it has been moved. – yyny Mar 07 '19 at 23:10
  • @YoYoYonnY I know std::move does not move anything. However, if someone does something like `X x = std::move(y);`, then `y` is in a moved-from state. I want to make sure that whatever data is in `y` is zeroed in. The standard DOES NOT guarantee that the `y` vector simply transfer the internal pointer, in fact a perfectly valid (but stupid) move will be to simply copy the data. – vsoftco Mar 07 '19 at 23:13
  • 1
    With complexity guaranty of move constructor of vector, I think that moved-from vector can no longer have old data. (move assignment might be more tricky as `swap` would be valid implementation). – Jarod42 Mar 07 '19 at 23:13
  • @Jarod42 That was my initial though... however I cannot find any definite answer/quote saying that. And I really don't want to leave sensitive data floating around, no matter what. Do you have any standard reference in which `std::vector(std::vector&&)` is guaranteed `O(1)`? – vsoftco Mar 07 '19 at 23:15
  • @vsoftco According to [cppreference](http://www.cplusplus.com/reference/vector/vector/vector/), this is false. The standard DOES guarantee that the `y` vector transfers it's internal pointer. – yyny Mar 07 '19 at 23:26
  • 3
    [cppreference](https://en.cppreference.com/w/cpp/container/vector/vector) states a `O(1)` complexity for (6) `vector( vector&& other ) /*noexcept*/`. – Jarod42 Mar 07 '19 at 23:26
  • @YoYoYonnY, Jarod42, thanks! I missed that somehow... – vsoftco Mar 07 '19 at 23:28
  • The internal vector is transferred but it is also possible that the move is accomplished by **swapping** the vector's data so in a *constructor* you're fine but in a *move assignment operator* you need to zero the moved from data (or the moved to data before the move). – Galik Mar 07 '19 at 23:29
  • I will probably still keep my "paranoid" implementation, as I don't want to have some "DEBUG" like build in which some crazy copying may be happening or stuff of that sort... – vsoftco Mar 07 '19 at 23:30
  • @Galik Right... – vsoftco Mar 07 '19 at 23:31
  • 1
    It is possible the compiler will optimize the zeroing out if it sees you writing to temporary though... so its probably worth using an opaque function to do the erazing so the compiler doesn't now if there are side effects or not. – Galik Mar 07 '19 at 23:33
  • @Galik Yes indeed, that's what I am actually using behind the curtains. In particular, I am calling a C-like function from an already-compiled library. The compiler should not be able to deduce what the function does at compile time. – vsoftco Mar 07 '19 at 23:35
  • The resize to zero will probably be a no-op since the size will most likely already be zero. Thus neither the expand nor contract logic of resize will operate and nothing will happen. – David Schwartz Mar 07 '19 at 23:58
  • Copying the vector makes more sense than moving it and re-growing the source – M.M Mar 08 '19 at 00:03
  • @DavidSchwartz I'm first resizing to `N`, then resizing to 0. Oh.. you mean that the compiler may just decide to ignore my first `resize(N)`?! That will be bad! – vsoftco Mar 08 '19 at 00:07
  • The resize will likely allocate an entirely new memory block and have no effect on the existing block that contains the data. If the data gets copied into it, you're just overwriting the newly-made copy. If it doesn't, you're overwriting blank data. In neither case does the `resize` help. – David Schwartz Mar 08 '19 at 00:11
  • @M.M You mean simply `delete`-ing the move semantics? That may be indeed an option if things tend to get ugly... – vsoftco Mar 08 '19 at 00:11
  • @DavidSchwartz The initial size of the moved-from vector is `0`. So I'm not worried about `resize(N)`. This will simply allocate memory, and nothing will be copied around. Am I missing something here? Remember that first the vector is being moved-from, and that will guarantee that the size of the moved-from vector is zero. I just want to make sure that, no matter what, data does not "stick" around in what's being moved from. Crypto paranoia that you're probably very familiar with... – vsoftco Mar 08 '19 at 00:13
  • @vsoftco Then why not copy the old vector into a new vector, overwrite the data in the old vector, and then resize it to zero? – David Schwartz Mar 08 '19 at 00:28
  • @DavidSchwartz That's what I'm actually doing, but instead of copying I'm using moving. I'm just worried that someone may write something like: `Signature s = Signature(some_secret_key)`. In this case, the rhs is a rvalue, `s` will invoke the move constructor, and move the rhs into `s`. However I want to make sure that "hot" memory in what was rhs is not hot anymore. I can just disable move semantics altogether, but for the sake of learning I wanted to see whether my solution is a valid one. Note that in the case of copying, I'm safe, as the destructor takes care of zeroing the memory out. – vsoftco Mar 08 '19 at 00:34
  • 2
    @vsoftco I would suggest that you implement your own move operations. There's no way you can know for sure what moving a `std::vector` will do. It could make an inaccessible copy. (It probably doesn't, but you can't know.) – David Schwartz Mar 08 '19 at 00:40
  • @DavidSchwartz good point, I'll think about it – vsoftco Mar 08 '19 at 00:52
  • You should consider using `volatile` – curiousguy Mar 08 '19 at 08:36

3 Answers3

5

My question is: can I resize a moved-from std::vector, perform some operation on it, then resize it back to zero?

A moved from object should be in unspecified but valid state.

So you have the right to resize it (no precondition required for that). safe_zero it, and clear it.

(I did not understand what "pre-conditions" really are).

There are state conditions that object should have to not invoke UB.

For example, operator[](std::size_t i) requires that i < size().

resize(), clear() doesn't have requirements.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
4

[defns.valid] valid but unspecified state value of an object that is not specified except that the object’s invariants are met and operations on the object behave as specified for its type

[Example: If an object x of type std::vector is in a valid but unspecified state, x.empty() can be called unconditionally, and x.front() can be called only if x.empty() returns false. — end example]

std::vector::resize does not have any preconditions. No matter what valid state a vector is in, resizing it would not have undefined behaviour (disregarding UB caused by constructors of contained elements; but those are not called when the argument is 0).

eerorika
  • 232,697
  • 12
  • 197
  • 326
2

Yes. The object is in valid but in unspecified state as stated in the linked question. That means that you cannot assume anything about the content of std::vector. Calling size is safe but it might not return the same value as before the move. The state of the vector is valid, meaning there are no dangling pointers inside or anything and all member functions including resize will work just fine. Moreover calling resize is one of the few meaningful functions to call because it "respecifies" the vector's state.

Quimby
  • 17,735
  • 4
  • 35
  • 55