1

Consider a program that can allocate and read uninitialized memory regions, e.g. for performance reasons.

Should said program assume that those regions may contain sensitive information, or is it the previous owner of those regions responsible for writing 0s before deallocating them?

I am almost certain that it is the latter, as the former would require all uninitialized regions to be assumed to be "sensitive", but I could not find any authoritative source on this.

1
  • It's a fundamental error in programming to fail to initialize memory before attempting to use it. Conversely, high security programs wipe sensitive memory before releasing it, but that is a very small subset of programs. Mar 9 at 5:22
5

From an application development standpoint, you should never presume that memory will be zeroed unless the allocator API you are using explicitly guarantees it. Making that assumption without a guarantee may lead to memory corruption and undefined behaviour. This may have security consequences, but more importantly it's likely to cause subtle and hard-to-reproduce bugs.

Your memory allocation API should always clearly define the initialisation status of the memory it is allocating for you. For example, malloc does not guarantee that memory will be zeroed, but calloc does. You can design your code with this in mind.

It is also important to distinguish between the behaviour of a heap allocator that is implemented as part of your application and a the operating system's memory allocator.

On any mainstream operating system, the memory manager will ensure that pages are scrubbed (i.e. cleared of previous data) when allocating them across a security boundary. This means that if a memory page was allocated to one process, filled with sensitive information, then freed and re-allocated to a different process, the memory manager must scrub the page. If the page is freed and re-allocated to the same process, it does not have to scrub the page, because the page is not transitioning across a security boundary.

A scrubbed page does not have to contain zeroes. It may contain some other constant value instead. Some memory manager systems scrub pages to some hexspeak value so that it is easy to tell when memory has not been explicitly zeroed. Refer to your OS's API documentation for details.

The same restriction exists for VMs. If the VM host is remapping a page into a guest VM's memory space, it must scrub that page first. Not only that, but the CPU cache must also be flushed when performing a context switch back into the execution context of the VM. This reduces the possibility for cache-sniffing attacks that leak host memory into a VM.

A heap allocator library (e.g. stdlib malloc, jemalloc, tcmalloc, ptmalloc) implements heap management inside an application, abstracting away the specifics of the platform's memory management APIs. Since the memory pages being allocated by these allocators are inherently kept within the process boundary, there is no situation in which a dirty memory page can cross a security boundary into another process and be re-allocated there without scrubbing.

In some threat models you may be concerned about sensitive data remaining in memory beyond the lifetime of the allocation or the process. While another process cannot directly access that sensitive data, since the OS memory manager will scrub it before reallocation across a security boundary, there are threats such as forensic memory acquisition or DMA attacks that bypass the allocation scrubbing policy by accessing physical memory pages directly. In such cases you would want to scrub the pages manually before freeing them.

One critically important detail here is that simply looping over the memory and setting it to zero (e.g. with memset) prior to freeing the memory is not guaranteed to be effective. The compiler may detect that the memory is not accessed after being written and eliminate the zeroing operation as an optimisation.

Available solutions for this scrub elimination problem are specific to the language, toolchain, and platform:

  • -fno-builtin-memset in GCC to turn off memset elimination
  • memset_s as a non-standard approach in C11
  • SecureZeroMemory on Windows (although that may be deprecated?)
  • Casting the target memory as volatile in your zeroing loop in C/C++ - this tells the compiler to assume that something outside its view might be modifying the memory, preventing the elimination optimisation.
  • SecureString in .NET applications (memory remanence is a complicated topic here due to string immutability and GC, and you have to be very careful with your usage even with SecureString)
  • GuardedString in Java applications (similar practical problems to .NET)
  • secstr or similar in Rust
  • sodium_memzero from libsodium, ideally alongside their other secure memory APIs.
2
  • Learned some useful things today. Would you add a little bit about directly reading another process memory, for example via ReadProcessMemory on Windows or read on Linux, when you talk about "forensic memory acquisition or DMA attacks". Thank you.
    – oleksii
    Mar 10 at 16:42
  • @oleksii ReadProcessMemory and read are not relevant here. If you have the privileges to use them on a target process, you can inject your own code into the process and access the secrets that way. You're not bypassing a privilege boundary because you're performing an action that affects a process you already have complete access to. Forensic memory acquisition is usually performed via PCI-e card or Thunderbolt, since you can use DMA to get access to physical memory without the OS being involved.
    – Polynomial
    Mar 11 at 15:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.