Can I resize a vector that was moved from?
Asked Answered
F

3

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?

Female answered 7/3, 2019 at 23:1 Comment(23)
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.Patchy
@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.Female
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).Utricle
@Utricle 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)?Female
@Female According to cppreference, this is false. The standard DOES guarantee that the y vector transfers it's internal pointer.Patchy
cppreference states a O(1) complexity for (6) vector( vector&& other ) /*noexcept*/.Utricle
@YoYoYonnY, Jarod42, thanks! I missed that somehow...Female
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).Camelliacamelopard
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...Female
@Camelliacamelopard Right...Female
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.Camelliacamelopard
@Camelliacamelopard 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.Female
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.Corneliacornelian
Copying the vector makes more sense than moving it and re-growing the sourceOhalloran
@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!Female
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.Corneliacornelian
@Ohalloran You mean simply delete-ing the move semantics? That may be indeed an option if things tend to get ugly...Female
@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...Female
@Female Then why not copy the old vector into a new vector, overwrite the data in the old vector, and then resize it to zero?Corneliacornelian
@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.Female
@Female 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.)Corneliacornelian
@DavidSchwartz good point, I'll think about itFemale
You should consider using volatileCouch
U
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.

Utricle answered 7/3, 2019 at 23:18 Comment(0)
F
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).

Feinleib answered 7/3, 2019 at 23:18 Comment(0)
C
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.

Cly answered 7/3, 2019 at 23:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.