Live data from Hacker News

The Lost Art of Structure Packing (2018)

catb.org

111–120 of 120 posts

Re: The Lost Art of Structure Packing (2018)

#111

Biggest surprise for me is learning that a pointer is a whopping eight bites! I have always mistakenly assumed they were small and just four bites.

It depends on the platform! On ILP64 and LP64 (and presumably P64, though I've never heard of this actually being used anywhere) they'll be 8 bytes, but not on most 32-bit architectures.

Isn't that the _definition_ of a 32-bit (or 64-bit or 8-bit or however-many-bit) - that 32 bits is the length of a pointer?

Re: The Lost Art of Structure Packing (2018)

#112

Earlier quoted context omitted.

memset definitely writes to padding bytes, as noted by several other folks here. you observe these padding bytes during serialization and deserialization (disk, network, memory mapped buffer, etc.) Note this is important enough that compiler folks consider it a bug if it doesn't work correctly. see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92486 for a recent example involving memset and memcpy.

As mentioned in the bug report, the value of padding bytes is unspecified by the C standard. Actually, according to the linked Defect Report mentioned in the bug, there is a bit of discussion on whether the functions you mentioned should be entirely undefined to call or if some exceptions should be carved out. If the functions are legal to call, it is very likely that the results will be some arbitrary reification of…

There is some discussion of that, but it is wrong. There are many comments there, so you should read the whole bug report. The value of the padding bytes must be what is specified in memset.

Re: The Lost Art of Structure Packing (2018)

#113

Earlier quoted context omitted.

memset is absolutely required to zero out padding bytes. Padding is part of the structure size, and memset hast to zero out exactly as many bytes as it is told. memseting a structure to zero is commonly done in programs that send structure outside of the process (like passing it to communication or storage-related system calls) because the padding can leak sensitive information. That better work! If the size of a str…

memset is not required to do anything if the side effects are not observable; this is the entire reason why memset_s needed to be added (and also why __attribute__((packed)) is recommended for anything that is being sent directly over the wire, if this kind of construction cannot be avoided). Compilers often inline and unroll small, constant-size memsets anyways, and since it is not possible to observe a consistent p…

I've been duped into participating in misleading thread without brushing up on this. The problem is that memset can be entirely optimized away when it's a dead store, which is reasonable:

  {
    struct foo x;
    // sensitive calculation with x
    memset(&x, 0, sizeof x);
  }
Basic liveness analysis (compiler technique from the 1970's if not older) tells us that the object has no "next use" at the point where it is being written by memcpy. That's a dead store that can be eliminated. The object is about to become toast. This is a problem for sensitive code (e.g. crypto).

I've been discussing only this case:

  {
    struct foo x;
    memset(&x, 0, sizeof x);
    // init x
    syscall(&x, sizeof x);
  }
Here, the memset cannot be optimized away. So we can only have some academic discussion about how part of the memset could be optimized away: that part which flosses the structure padding between the members and at the end.

That's a stupid and dangerous optimization that threatens a whole lot of code in the wild.

A good defense against this sort of time-wasting nonsense is "I'm not fixing anything without a repro test case; have a nice day".

Re: The Lost Art of Structure Packing (2018)

#114
post #42

Earlier quoted context omitted.

First of all, there is no way the compiler is going to optimize a call to memset(&foo, 0, sizeof(foo)) when &foo is being interpreted as a void pointer. That doesn't even make sense. Second of all, in a generic C interface keys are likely to be treated as void pointer and almost certainly are going to be moved around with memcpy etc. rather than returned/passed by value since doing so would make the interface non-gen…

The compiler knows what memset does . One of the earliest steps of optimization is replacing calls to well-known functions with intrinsics. Try it! https://godbolt.org/z/QUREQi Yes, it's true that generic C code will type-erase the key type. However it just takes a little refactoring in specific code to move the struct initialization across a call boundary from where it is passed to the generic code.

The compiler knows that memset clobbers an object, and can classify that as a dead store.

I'm skeptical about compilers optimizing memset not to cover padding between structure members.

Firstly, that would introduce security holes into a heck of a lot more existing code compared to code that uses a dead-store memset to wipe sensitive crypto.

Secondly, it wouldn't run any faster. Gaps in a structure and at the end exist in order to eliminate misalignment. Before most padding, there is a member that ends on a misaligned address. It's slower to update just that member, and leave the padding alone, than to clobber the padding.

For instance if we have a { char a; int b; char c; } structure, we gain nothing by zeroing just one byte of a, b and c.

In some compiler for an 8 bit system, this reasoning is likely false; I will worry about it when porting to that. Very little existing code will fit; you're coding from scratch for such things.

Re: The Lost Art of Structure Packing (2018)

#115
post #88

Earlier quoted context omitted.

Agree. memset should be secure by default. That means it should not be optimized away by the compiler. And second the secure variant should flush the content, so that cache attacks can not read secrets which were memset'ed, but still in the cache on broken CPU's (Intel).

sloooooooooooooooow. No, really, do you want a full flush every time you do a memset? That would be horrendous for performance.

"And second the secure variant should flush ..."

Probably means the extra-secure variant, more secure than the regular non-optimized-away memset that really clears the memory, but without the flush.

Re: The Lost Art of Structure Packing (2018)

#116

Earlier quoted context omitted.

It depends on the platform! On ILP64 and LP64 (and presumably P64, though I've never heard of this actually being used anywhere) they'll be 8 bytes, but not on most 32-bit architectures.

Isn't that the _definition_ of a 32-bit (or 64-bit or 8-bit or however-many-bit) - that 32 bits is the length of a pointer?

Certain strange platforms (arm64_32 for Apple Watch) run ILP32 on AArch64.

Re: The Lost Art of Structure Packing (2018)

#117

Earlier quoted context omitted.

sloooooooooooooooow. No, really, do you want a full flush every time you do a memset? That would be horrendous for performance.

"And second the secure variant should flush ..." Probably means the extra-secure variant, more secure than the regular non-optimized-away memset that really clears the memory, but without the flush.

Yeah, that's a more reasonable interpretation.

Re: The Lost Art of Structure Packing (2018)

#118

Earlier quoted context omitted.

memset is not required to do anything if the side effects are not observable; this is the entire reason why memset_s needed to be added (and also why __attribute__((packed)) is recommended for anything that is being sent directly over the wire, if this kind of construction cannot be avoided). Compilers often inline and unroll small, constant-size memsets anyways, and since it is not possible to observe a consistent p…

I've been duped into participating in misleading thread without brushing up on this. The problem is that memset can be entirely optimized away when it's a dead store, which is reasonable: { struct foo x; // sensitive calculation with x memset(&x, 0, sizeof x); } Basic liveness analysis (compiler technique from the 1970's if not older) tells us that the object has no "next use" at the point where it is being written b…

As I was discussing with 'gpderetta, it's complicated: https://news.ycombinator.com/item?id=23002423. Depending on how the followup to the DR I mentioned goes, your call is somewhere between "illegal as this invokes undefined behavior" and "sending structs across the wire better not be an infoleak". We'll see where the standards people take this. (Personally, I'm of the opinion that reading padding should always give an unspecified value–that is, a read can give an arbitrary, possibly inconsistent but valid value for that byte, but copies of it would have a fixed value. Code in the wild that has historically not cared for standards compliance anyways, e.g. Linux, should get a GCC flag for "make my padding bits what I want them to be".)

Re: The Lost Art of Structure Packing (2018)

#119
post #86

I've documented how GCC does bitfield packing in the TXR reference manual. Or rather, the abstract algorithm used in the FFI to replicate it. https://www.nongnu.org/txr/txr-manpage.html#N-027D075C This is the result of empirical investigation. The description also covers allocation of non-bitfields (paragraph 3) and the padding of the structure (paragraph 9) which require few words. I felt that the bitfield handling…

You could have saved time and gone to the ABI spec. https://itanium-cxx-abi.github.io/cxx-abi/abi.html This was originally developed as a joint effort to make compilers ABI-compatible on Itanium, but it's also used (by GCC, clang, Intel's proprietary compiler and others) on x86-64. An old Hacker News comment said that it's from Intel; it's not, it was a joint effort with lots of work from CodeSourcery and Red Hat fol…

The document you link to is entirely about C++, and makes numerous references to some "base (C) ABI".

"The size and alignment of a type which is a POD for the purpose of layout is as specified by the base (C) ABI"

The links in 1.5 Base Documents are old and broken.

Re: The Lost Art of Structure Packing (2018)

#120

Earlier quoted context omitted.

I've been duped into participating in misleading thread without brushing up on this. The problem is that memset can be entirely optimized away when it's a dead store, which is reasonable: { struct foo x; // sensitive calculation with x memset(&x, 0, sizeof x); } Basic liveness analysis (compiler technique from the 1970's if not older) tells us that the object has no "next use" at the point where it is being written b…

As I was discussing with 'gpderetta, it's complicated: https://news.ycombinator.com/item?id=23002423 . Depending on how the followup to the DR I mentioned goes, your call is somewhere between "illegal as this invokes undefined behavior" and "sending structs across the wire better not be an infoleak". We'll see where the standards people take this. (Personally, I'm of the opinion that reading padding should always giv…

[deleted]
Post reply on HN