Uninitialized Reads: Understanding the proposed revisions to the C language
queue.acm.org
queue.acm.org
Ouch! This is a really terrible idea and people should stop doing it in C. It's a bad entropy source as, unless you're very specific about the hardware, you have no idea what you're going to get. Maybe it is all zeroes. Maybe it's a page of a previously executed program.
If you're going to do this, do it in assembler so it's deliberately unportable.
Using uninitialized memory wasn't an actual problem there, as it was mixed in with a bunch of other entropy before being used, and those other sources were sufficient even if the uninitialized memory had predictable contents. However, tools like Valgrind complained about it, so somebody fixed it, and inadvertently wiped out almost all of the other entropy sources going into it as well.
It had (and still has) a -DPURIFY flag available at compile time that gets rid of that stupidity, which is still not default. OpenSSL have not changed it, as I understand, because of a concern that there might exist some embedded system somewhere where that is the only useful source of randomness.
A debian developer saw the errors all over the place, wrote a patch to fix it. The patch, unfortunately, went to far and managed to knock out the secure randomness source to. The patch was posted to the OpenSSL list and no one responded "No don't!" or "Have you seen the already existing DPURIFY?"... and the rest is history.
Fedora, at least, adds DPURIFY, but many other distributions do not.
The embedded system argument makes no sense to me (understanding that you're just repeating it, not defending it). If there's no other source of randomness then there's no good source of randomness, period, and you're screwed. Better not to even try, then.
Either way it seems unlikely to be very random. In my experience 0 is by far the most common value in unitialized memory, to the point of making many uninitialized memory bugs difficult to notice or even later reproduce.
In BSS, yes (IMO, BSS cannot be really considered as uninitialized, but you didn't give precisions).
On the stack... I don't remember offhand whether the thread's stack is initially mapped in using zeropage which is then replaced by a real page on a first write. Even so, the first write would be saving the return address, so occurring pretty quickly. In any case, code moving up and down along the stack (calling into a chain of embedded routine inside a loop) will mostly spot its own garbage, hardly entropic.
The "trap values" concept is interesting. They're thinking that some floating point values are signaling NaNs, which will cause an exception if processed.
One way to approach this is to divide types into "fully mapped", where all bit patterns map to a valid value, and "partially mapped", where they don't. All the integer types, "char", "short", "int", "long", etc. are fully mapped on modern machines. "bool" isn't. Floating point isn't. Pointers are not. Enums are not. Structs are fully mapped if all their fields are fully mapped.
Initializing a fully mapped type with junk won't break the C/++ machine model. That's not true of partially mapped types. So this could be enforced in casts. Casting to fully mapped types could be permitted as at present, but casting to a partially mapped type should require something visibly unsafe, like C++'s "reinterpret_cast". In C, turning the raw memory from "malloc" into a partially mapped type should be a visible act. In C++, all partially mapped fields should be required to be initialized in the constructor. It's OK at the language level if fully mapped fields start as junk.
Requiring that pointers be initialized would be good for safety. Failure to initialize a pointer is usually a bug. Sometimes it's an entry point for an exploit.
That's what the article calls them, but the Standard uses the term "trap representations". The point is that a trap representation does not represent a value. (A representation is the bit sequence stored in an object. A value is more abstract; it's a member of the set of possible values of a type. A value can have more than one representation.)
I recall some SPARC arch (uSparc?) circa 199x that would place chars and shorts in a struct as if it were an int (i.e, aligned on boundary of 4). Then, when loading this field from the structure, it would load a full word, then shift it 16 or 24 bits left then right, to propagate sign or 0 and squeeze out undefined bits.
Interestingly enough, for programming it we used Sun Workbench which had some rudimentary equivalent of valgrind. So if in my code I would initialize the struct by initializing every attribute, then copy the entire structure using memcpy, the tool would complain about reads from uninitialised -- exactly because of the paddings of shorter types.
And then there's Itanium: https://blogs.msdn.microsoft.com/oldnewthing/20040119-00/?p=...
Consider that any particular local variable might be allocated to a register. If the local variable has not been initialized, it might have junk from a previous use, and that junk might have the NaT bit set. Reading from that local variable, or even spilling it to the stack, can cause an exception.
Now this would break at least one possible optimization that at least GCC does, namely that if you know that the value is either 1 or 0, then "!x" can be implemented as "x^1" (bitwise xor the value with 1) instead of a branch. I'm not sure that this optimization is particularly significant in real world code, though.
`int x` is initialized to 0 by default, but those who really care can write `int x = void` to get an uninitialized value.
I think that feature could even be a backwards-compatible addition to C.
We would really need a "not-a-value" for integral types in order to create them initialized by default, and there is no such thing unfortunately. This is different for pointers (except in very specific situations) and for floating-point types, so these should be initialized.
A compromise there would be to define it to be initialized to zero, but leave uninitialized use still defined as an error in the program (just not one optimizers can exploit.).
Unfortunately the impact on optimization is also a consideration too.
For malloc, I can already choose fairly easily to 0 out memory, but here it can get more expensive if one isn't careful.
I've always wondered why the compiler can't warn about this at compile time. It should know when uninitialized stack space is being used since it is, after all, the compiler artificially designating what the stack is!
void do_something(int *);
int f() {
int x;
do_something(&x);
return x;
}
Is the return of x a use of initialized memory? Maybe. Relink the program and the answer might change.I googled it and this[0] seemed relevant:
1655 — if it has arithmetic type, it is initialized to (positive or unsigned) zero;
1652 If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate.
(x is an 'auto' variable in the given program.)If x was static, this would be true. But in this case, x is just a default automatic variable. For x to be static, it would have to be declared outside the function, or have the keyword 'static'.
For anyone else interested in a longer explanation these SO comments are pretty informative: http://stackoverflow.com/questions/14049777/why-are-global-v...
But the vastly more common case is uninitialized usage in the same compilation unit and I argue that that case should give a warning.
(Probably something to do with globals/scope, right?)
int y, z;
if (x < 3) { y = 0; }
if (x < 4) { z = y + 1; }There are plenty of warnings in -Wall that are conservative. For example in g++ C++, out of order constructor initialization even for primitive types is warned for the same reason.
In this case the use of y on the RHS without explicit initialization should be warned.
It sounds annoying, but having a bug's behaviour change between different build modes is much more irritating.
The compiler could just be "conservative" like you said and warn whenever it couldn't statically determine validity, but note that initializing e.g. `y = 0` has a performance cost (cf. reordering constructor initializers).
You could say "warn if there are any inputs, and any return values for any function I call, that can lead to a problem" but I suspect that would produce too many false positives.
I'd really like to find a fully copy of the survey and the response stats.
int x;
x = (x == x);
This is indeed what clang and gcc emit for e.g. _mm_set1_epi8(-1). All values are equal to themselves - well, except NaNs!It's true that the given C code doesn't have those semantics, but you can see what the GP was driving at if you squint a bit. Uh, and if you know how these instructions work already.