15 comments

[ 1.4 ms ] story [ 51.8 ms ] thread
There is some code that does unintialized reads on purpose.

For example highly vectorized loops that process a big buffer of data, but might read a bit out of bounds at the start or end of the buffer. The algorithm as a whole is guaranteed not to depend on the outcomes of those out of bounds reads, so as long as they can be guaranteed not to segfault, they should be safe.

Not allowing this usually results in either slower code (can't vectorize as much) or bigger code (need to have a special case codepath for the first/last items to be processed). The bigger code is often slower too due to worse branch prediction, more cache pressure, etc.

You could over-allocate to make sure that there's some spare capacity at the beginning/end. Or use mask registers or do the common thing that doesn't trigger UB:

> need to have a special case codepath for the first/last items to be processed

It doesn't seem like this checker would conflict with that use case. All this checker wants is that if you allocated some bytes, you touch it somewhere before reading it anywhere.
That conflicts.

Suppose you want to allocate 24 bytes and process them using SIMD instructions.

You call `aligned_alloc(24, 16)` to get a 24 byte allocation allocated to a 16 byte boundary.

Since the OS doesn't give the allocator 24 bytes, but whole memory pages, _it is valid_ for your program to read 32 bytes starting at the pointer returned, since that will never read out of the memory page in which this allocation resides.

This means that you can actually read the 24 bytes using 1 AVX instruction to read 32 bytes at once into a SIMD register, and do stuff with it (masking the uninitialized memory, etc.).

From those 32 bytes that you read, only the first 24 bytes might have been touched. That's a perfectly normal thing (e.g. you ask the allocator for 24 bytes, and it gives you a pointer to a 32 byte allocation, such that the last 8 bytes will never be touched by anyone).

But this thing works page-by-page.
Correct, this technique works on a page by page basis. It's the only way to do it with userfaultfd as the technique relies entirely on the first page fault being a read and not a write. Im working on a feature that returns an allocation that straddles 2 pages to catch reads of structure paddings but it will come at the cost of an additional 4k of memory and/or possibly returning unaligned memory.
So it only errors if you try to access a page in which no byte has been initialized ?

That is, if a program writes only to the 0th byte of a page, this tool won't warn on it reading from the 42nd byte ?

No it will still catch it if you access the 42nd byte with a read, but not if you write to any other byte first. Thats just a limitation of page faults and the information provided by userfaultfd.
You need to be routinely running valgrind on every bit of C code you write, and that will catch uninitialized reads along with many other classes of bugs.
Valgrind can prohibitively slow down your process though, where many timing dependent code paths aren't hit and the app behaves dramatically different than it would in production.
‘Many’? Like what kind of code paths?
Really any code that involves multiple threads, because there may be race conditions.
Grey text on white background. Ouch, my eyes.
I've gotten this feedback before. It's probably time to change it from #414141
(comment deleted)