7 ms·
Uninitialized Reads: Understanding the proposed revisions to the C language
- CJefferson 10y agoI wish one of clang, or gcc, would gain an option to let me initalise any stack variable to 0 if there is not an explicit assignment to it. I suspect (and I'd really like to know) that with optimisation, the practical cost of this would be 0, and a whole bunch of issues would be cleaned up. For malloc, I can already choose fairly easily to 0 out memory, but here it can get more expensive if one isn't careful.
- CoolGuySteve 10y agomsvc detects uninitialized stack variable usage as a run time check and throws an exception. 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!
- gpderetta 10y agoCompilers are certainly capable of warning about uses of uninitialized variables, but there are necessarily false positives and false negatives as the problem is in general undecidable.
- ci5er 10y agoIs it? Why isn't it as simple as a rule stating that a warning is generated whenever, in the AST, a variable is used on the RHS before it's ever used on the LHS of an expression? (Probably something to do with globals/scope, right?)
- CJefferson 10y agoWhen you have ifs and for loops around, it maybe depend on arguments to the function, and also the return values of functions you call. 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.
- mnarayan01 10y agoThat's easy enough to do (and plenty of compilers do it), the problem is determining it for things set conditionally (uncomputable in the the general case) e.g.: int y, z; if (x < 3) { y = 0; } if (x < 4) { z = y + 1; }
- oldmanhorton 10y agoWell, in the most general case where x's value can only be determined at runtime, most any compiler can and will assume that the initial if could or could not be hit and will therefore assume that y could, conservatively, be uninitialized. Its not uncomputable, its just conservative.
- mnarayan01 10y agoI mean it's undecidable even if x is computed from purely static values: `x` can be the result of an arbitrary C-function and C is Turing complete.
- nitrogen 10y agoIf x is a 32-bit int, then there are only ~4bil possible values to consider. If any of them not ruled out already would lead to an uninitialized read, issue a warning.
- CoolGuySteve 10y agoBut this is still an error when x == 3, y will be uninitialized when z is set. 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.
- mnarayan01 10y agoI probably should have made the second conditional `(x < 2)` rather than `(x < 4)` so that the compiler should not generate a warning. Here a serious compiler would likely be able to statically determine that the variable was always initialized, but for a sufficiently complex function call (even an inlined one) it would not. 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).
- gsg 10y agoIn general it's impossible. Consider this C program: 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.
- smhenderson 10y agoIsn't x already initialized to zero as it's static? I googled it and this[0] seemed relevant: 1655 — if it has arithmetic type, it is initialized to (positive or unsigned) zero; [0] http://c0x.coding-guidelines.com/6.7.8.html http://c0x.coding-guidelines.com/6.7.8.html
- nkurz 10y ago> Isn't x already initialized to zero as it's static? 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'.
- smhenderson 10y agoOK, thanks, appreciate the correction. I see what I had wrong now. For anyone else interested in a longer explanation these SO comments are pretty informative: http://stackoverflow.com/questions/14049777/why-are-global-variables-always-initialized-to-0-but-not-local-variables#14049791 http://stackoverflow.com/questions/14049777/why-are-global-v...
- gsg 10y agoWhat you want is just a few lines up: 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.)
- smhenderson 10y agoYeah, that's what I get for replying without making sure I'm correct! :-)
- pjc50 10y ago> but some do it anyway—for example, to create entropy 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.
- DubiousPusher 10y agoOk, couple questions. Entropy in what context? Just as a source of randomness? Which isn't what entropy really is right? 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.
- minipci1321 10y ago> In my experience 0 is by far the most common value in unitialized memory, 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.
- TazeTSchnitzel 10y agoIt always struck me that there's a good chance it's completely or mostly predictable, if you end up grabbing something your program produced.
- mikeash 10y agoFor those who don't remember, the infamous Debian bug which resulted in weak keys being generated on Debian systems for a couple of years stemmed from this. 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.
- Animats 10y agoThe C and C++ standards people are finally trying to deal with their safety problems, decades too late. At least they're trying. Some of this may be an attempt to catch up to Rust. 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.
- panic 10y agoIIRC signaling NaNs aren't trap representations: they're fully-defined values which support operations like isnan().
- brandmeyer 10y agoCorrect. For more detail: the only mandated "signaling" for a signaling NaN is that it sets the sticky Invalid bit in the floating-point status register (aka Invalid exception). It is up to the host processor as to whether it is possible to get a signal on changes to the floating-point exception bits. Some CPU's don't have the ability at all.
- minipci1321 10y ago> Structs are fully mapped if all their fields are fully mapped. 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.
- ridiculous_fish 10y agoFun fact: the fastest way to load an XMM register (that is, SSE) to all bits 1 is to compare that register to itself, essentially: 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!
- poikniok 10y agoCould you explain further, so x = 1 now, what exactly has all bits set to 1?
- gsg 10y agox86-64 vector compare instructions (mostly) produce masks, which are vector-sized values containing 1 for each bit of the input for which the comparison is true, and 0 where it is false. Since a vector analogue of x == x would be true everywhere, it would result in 0b1111.... 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.
- heinrich5991 10y agoThis code has undefined behavior.
- ridiculous_fish 10y agoYep! The compiler gets to emit it, but you don't!
- pornel 10y agoD has got it right. `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.
- minipci1321 10y agoI don't have a strong feeling either way, but sometimes not giving a value to a variable allows the compiler and static analyser to quite easily detect uses of non-initialized. OTOH, assigning '0', a perfectly valid value, to a variable initially, might help to conceal bugs when the variable is not updated to the actual value before use. 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.
- nullc 10y agoThe compiler detecting uninitialized use catches a lot of real bugs. 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.
- braveo 10y agoIn languages that allow you to do things like set the value of enumerations, I always start with 1. The reason being that an uninitialized variable, bad outside input, etc, is more likely to get caught as bad input rather than whatever happens to be in slot 0.
- nullc 10y agoThis is the paper on the survey they mentioned: http://www.cl.cam.ac.uk/research/security/ctsrd/pdfs/201606-pldi2016-clanguage.pdf http://www.cl.cam.ac.uk/research/security/ctsrd/pdfs/201606-... I'd really like to find a fully copy of the survey and the response stats.