Safely reading uninitialized memory


#21

After reading the above link I still feel that there’s a disconnect here. The approaches explored in the above paper discuss clearing the final state of sensitive storage, but do not address “under the hood” relocations of sensitive data such as occurs when the Rust runtime grows or shrinks a vec that contains sensitive data. Thus the storage that was once occupied by that data, but that is no longer so occupied at Drop, is never cleared.

The paper suggests a language enhancement to permit annotating selective data items as requiring guaranteed erasure on drop/relocation. Such an indication of programmer intent is probably the only means, long term, to address the issue in the presence of increasingly-sophisticated code optimization. It is also good programming practice: specify intent explicitly, rather than attempting some obscure kludge where the desired result is actually a side-effect.

If a programmer forgets to declare that specific data is „sensitive", that’s the programmer’s fault. If it is so declared, then the compiler can enforce storage being overwritten immediately prior to the item’s destruction or after its relocation, leaving leakage and various side-channel attacks as the primary remaining vulnerabilities.


#22

When you say that, you mean… it’s unimplementable in safe Rust due to a silly reason called safety.

If you really want this, why not use mem::uninitialized? This seems like exactly the sort of thing unsafe was created for: allowing “flexibility” for these sorts of, umm, exotic use cases while still allowing the safe subset of the language to retain some pretty strong safety guarantees, like all memory is initialized before use.

Though this paper claims these particular algorithms will always produce the same results regardless of what the values of the uninitialized memory were to begin with, that isn’t always the case. Here’s a paper on exploiting uninitialized kernel memory to affect privilege escalation:

Exposing uninitialized memory has multiple potential security ramifications, the exact extent of which will vary heavily depending on your application of Rust and application of uninitialized memory. Experience suggests, however, that if you give attackers wiggle room they will exploit it, so it’s best not to give it to them in the first place.


#23

Note that OP talked about it being implementable in Rust, not safe Rust. Using mem::uninitialized is unsafe, but certain usage patterns are sound. This one isn’t, as pointed out multiple times throughout the thread.


#24

Perhaps the title of the thread should be changed to “soundly reading uninitialized memory” then.


#25

(I only skimmed the paper, but it didn’t appear to cover the following user space-visible effect, which I think is a good illustration of how UB goes beyond the compiler optimizing stuff away.)

Notably, a CppCon talk told the story of a bug in Facebook’s string class that wrote the zero terminator lazily. The problem was that when the position of the zero terminator fell on the first byte of a memory page and the process had not written to that page at all, trying to read the terminator position yielded zero making it look like the terminator had already been written. Yet, a later read yielded non-zero at that memory location. The reason was that a read from a page that had never been written to caused the kernel to map a zeroed page. However, when the kernel evicted the page, it simply threw it away, because it hadn’t been written to and, therefore, wasn’t dirty. Next time when the memory location was read from, the page happened to have unrelated data written to it and the string no longer appeared zero-terminated. (The page also happened to be in an interesting position relative to jemalloc’s buckets.)