6 ms·
Nope it doesn't: //GCC: -O0 //MSVC: /Od #include <memory.h> #include <stdio.h> struct S { unsigned char x; int y; }; int f(int f) { S a; memset(&a
by mehrdadn 8y ago
Nope it doesn't:
//GCC: -O0
//MSVC: /Od
#include <memory.h>
#include <stdio.h>
struct S { unsigned char x; int y; };
int f(int f) { S a; memset(&a, f, sizeof(a)); unsigned char y[2]; memcpy(&y, &a, sizeof(y)); return y[1]; }
int g() { S a = { 1, 2 }; unsigned char y[2]; memcpy(&y, &a, sizeof(y)); return y[1]; }
int main(int argc, char *argv[]) { f(0xDD); printf("%#x\n", g()); f(0xFF); printf("%#x\n", g()); }
It definitely seems like the author didn't realize this fact, or I'd have expected it to be addressed in the article.
- unmole 8y agoI am aware of the fact that unitilized padding can have nasty side effects: https://lwn.net/Articles/417989/ https://lwn.net/Articles/417989/ But yeah, I should have mentioned the caveats too. I will update the post once I'm back at my computer.
- mehrdadn 8y agoI'm confused what I'm supposed to look for in that article (do you mean you were the author?) but anyway -- I feel like calling this merely a "caveat" gives the wrong impression? It seems a bit like telling people to mix bleach with vinegar and then saying "Oops, sorry, I did know that it produces chlorine! I forgot to mention that caveat." The caveat isn't just a side note for the margins, it's a critical reason why people avoid this sort of thing.
- Dylan16807 8y agoTechnically I don't think memset forces the compiler to clear the padding and keep it clear either though. The magic of the C abstract machine.
- mehrdadn 8y agoWhy? memset followed by memcpy must return the same byte everywhere. Otherwise what would memset even mean?
- saagarjha 8y agoI feel like accessing the bytes that correspond to padding might be implementation-defined, but I couldn't find this in the standard.
- mehrdadn 8y agoI don't think it is. It wouldn't make sense since memcpy is basically supposed to convert between arbitrary binary data.
- saagarjha 8y agoFrom a practical point of view, yes, but I would be very surprised if the C standard had any accommodation for memcpy being a way to leak the value of a structure's padding.
- DougBTX 8y agoParent comment was edited to include this quote: _6 When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values. 51)_ So sure, a memcpy after a memset aught to preserve all the bytes, but if they are updated through a struct which contains padding then the values in the padding bytes will be left unspecified.
- boulos 8y agoDo you mean in the sense that a compiler can say "I know what memset is supposed to do" then decides that even though sizeof returns a particular size, that it might ignore that and set fewer bytes when it does a substitution for the memcpy call?
- Dylan16807 8y agoYes. As long as the behavior observable by a conforming C program is the same, the compiler is allowed to change anything else. Even if memset in a vacuum is guaranteed, look at the quote by anyfoo. "When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values." If the compiler knows you're going to write to the members right after the memset, it can put the padding back to its previous values. And by that I mean as far as your code can tell it put it back, but in actuality it never zeroed the padding to begin with. And since there are performance benefits to pretending, now you have a situation where a "helpful" compiler and a malicious compiler have the same effect: data can get leaked.
- jcr1488 8y agoAs soon as a pointer to said memory is passed to an extern function in another translation unit, the compiler can't prove anything about how it's used, which is the case in pretty much all of the examples mentioned in this thread. Also, type-punning is a thing. memset is byte-oriented/memory-oriented. Just because you're using it to zero a struct of a particular kind doesn't mean that's the only way the memory will accessed. Just because reading the padding of some struct is undefined behaviour doesn't mean accessing those bits by some other means is also undefined. A compiler usually can't eliminate a call to memset() in most practical cases of initialization (where memory leakage is also a concern) because they almost always pass a reference to a routine in another translation unit. Something like memzero_explicit() can be used anyway -- but you're massively overstating the relevance of compilers eliminating dead stores done via memset(). It's much more of an issue for post-destruction memory sanitizing (which is the primary use case for memzero_explicit) than it is for compiler f*ckery when memset() is used for initialization.
- deleted 8y ago[deleted]
- ploxiln 8y agoFailing to clear padding in structs is _really_ not often a problem. It's pretty much only a problem when copying un-packed structs from kernel-space to user-space. So if you're not working on an OS kernel somewhere in the syscall path - really not a common activity even among C programmers - it's not a problem. If you are passing a struct from one part of your program to another part, or to a library you use which is not isolated or sandboxed in some way, it's not a problem. If you pass a struct to a _more_ privileged context like a syscall, it's not a problem. If you are passing structs over a network connection or writing to a file, you'll pack them and be aware of every byte anyway, in order to be compatible with another implementation reading them. If you _are_ passing a struct from a more privileged context to a less privileged context, then yeah you have to memset().
- mehrdadn 8y agoI agree, but the frequency isn't the issue. You could say that's where my analogy breaks down, and if that's your point then maybe it does (although how often do people want to mix bleach with vinegar?), but that entirely misses the point. The point was that the failure mode, when it does come up, is a critical one, and if you're going to criticize a safe coding practice and tell people to switch to a potentially dangerous one, it behooves you to explain to them this very fact. It's just irresponsible to tell people to switch to a dangerous practice without making them aware of that fact and telling them how to deal with it.
- kazinator 8y ago> If you pass a struct to a _more_ privileged context like a syscall, it's not a problem. Simply not true; that context could pass a copy of that structure to a less privileged context other than the original one, without doing anything about the padding. (Well, you could argue that it's the privileged kernel's fault: it should meticulously floss the structure between the members to clear the padding to zero while preserving the values.) Someone upthread mentioned "sending over a network": that's an example. Sending over a network (or local socket/pipe) begins with pushing it down to a more privileged context, which then assiduously places all the bytes into a buffer that is blasted out on the wire. In this case, the more privileged context has no idea what the structure even is; it's just a blob of bytes.
- bleair 8y agoIf the author is only going to add "caveat", then that sounds like word games and being unwilling to admit that the blog post's advice is wrong (for the security risks and other reasons pointed out here in the comments)
- deleted 8y ago[deleted]
- Someone 8y agoThat doesn’t check whether that memset call writes 0xFF bytes in the padding. The compiler could also determine that &a.x)[1] is undefined behavior in C (reading past the end of an object), and take advantage of that. The way to check that is by disassembling the code, and even that doesn’t say what a C compiler should do, only what these particular compilers do.
- deleted 8y ago[deleted]
- mehrdadn 8y agoI feel like you're missing the point (the gcc -O0 was there for a reason) but I updated it. Is this better?
- hermitdev 8y agoIf you're relying upon the value of padding, you're already into undefined behavior.
- hermitdev 8y agoIf you're either using via either assigning or reading the value that is stored in the padded area of a class or struct you are in undefined behavior, any version of the standard you choose. So, care to explain the downvotes?
- tlholaday 8y agoAlthough I did not downvote, I suspect your comment is being downvoted because it appears to overlook the risk of data leaking to an adversary through the padding bytes.
- jcr1488 8y agoIf you pass partially uninitialized objects (including padding) between kernel space and user space, there's a chance that the (less privileged) user space code can recover information it shouldn't be able to see, regardless of what the spec says about undefined behaviour. Such information may include secrets that previously occupied the memory where a new, unrelated struct now resides. The same can also apply to passing objects between different machines. All "undefined behaviour" means is that the spec can no longer guarantee anything about the execution of a program once a constraint is violated. It doesn't say anything about the practicality of actually doing it on any given implementation. FWIW I didn't downvote you, but your reasoning about undefined behaviour seems overly simplistic. It should never be used to explain away security concerns.
- Dylan16807 8y agoIt shouldn't be used to explain away security concerns, but it shouldn't be used to overstate security fixes either. You can memset to 0, and on your compiler it might be secure, but it's not enough to keep your security guaranteed against future compilers. There's a reason functions like SecureZeroMemory exist. In a similar situation, you try to prevent leaking secure secrets by zeroing memory before releasing it. But the compiler sees you never use the variable again, and optimizes away the zeroing. The message to take away is not "be less paranoid". It's "be more paranoid".