> The significant increase in virtual memory usage is usually not an issue, however security sensitive programs often lock their memory, causing a far greater performance degradation on low-spec computer hosts.
> The significant increase in virtual memory usage is usually not an issue, however security sensitive programs often lock their memory, causing a far greater performance degradation on low-spec computer hosts.
Operating systems usually cap the amount of locked pages to prevent the system from being DoS'd; on Linux it can be quite low (16kb).
In all the code I've written using locked memory, these limitations have forced me to use separate arenas for the locked/sensitive memory because of its scarcity. In general, it would be incompatible with Go's garbage collected heap unless the GC heap has the concept of "sensitive" objects and pools them in locked memory (which, AFAICT, it doesn't); or the heap limited itself to the locked memory limit (impractical)
If you allocate and maintain unmanaged locked memory yourself in Go it shouldn't matter if the 1.11 runtime uses more virtual memory since you've separated yourself from the problem by going your own route.
what does locking memory mean in this context? For what purpose do security sensitive programs lock their memory? what is the performance degradation that happens with low-spec computers?
You should always lock memory if you're going to be storing crypto keys, etc. since once the pages are swapped to disk you're vulnerable to someone pulling the swap partition out and reading it.
1. You don't have to lock all your memory (although it may be hard to capture all the intermediate buffers if you don't).
2. You still need to clear the memory buffers after they're no longer going to be used, otherwise other processes can read /proc/kcore, etc (or cool your RAM and extract it and put it another system)
3. It is possible to encrypt the swap partition with a randomly generated key at boot
I think the person is referring to the mlock() and mlockall() functions (or equivalents on other OS), which keep pages resident / prevents pages from being paged out. Forces them to remain in RAM.
It can be used to, e.g., prevent an encryption key or password from being swapped out to disk, where it might then be recoverable. (Personally, this is why I encrypt swap.)
> what is the performance degradation that happens with low-spec computers?
Locking a larger portion of RAM means less room for the OS to page out unused pages and free up the space for other programs.
While one can try to selectively lock buffers with sensitive data with mlock(), you have to be sure they aren't copied into other buffers that aren't locked (and could thus be subsequently paged out). If you're writing a UI program that displays or receives those in a widget, this might be harder (you might not have access to the internal buffer of the widget, as it is an "implementation detail" of your library), and locking the entire process might be a simpler solution (albeit being a bigger hammer).
But, in the context of this overall discussion, I think it's important to keep in mind that when a process allocates a large amount of virtual memory, it does not automatically allocate any physical memory. So a process allocating a lot of virtual memory up front should not impact other processes which have locked some of their memory into physical memory.
It will, if you mlock() it, I believe. The manual page notes "real-time processes" as a main user of mlock() (the other being the cryptographic uses I hinted at); it cites their use case as locking the page to avoid delays due to paging during critical sections. In order for that to work, the OS would need to bring the pages in, at the time of locking; so at that point, a large virtual allocation becomes equivalent to a physical one.
What it does is keep a canonical version of the page in memory. That's useful being able to deterministically touch a piece of memory, but it doesn't really help you as far as making sure the page never touches disk.
> Memory locking has two main applications: real-time algorithms and high-security data processing. Real-time applications require deterministic timing, and, like scheduling, paging is one major cause of unexpected program execution delays. Real-time applications will usually also switch to a real-time scheduler with sched_setscheduler(2). Cryptographic security software often handles critical bytes like passwords or secret keys as data structures. As a result of paging, these secrets could be transferred onto a persistent swap store medium, where they might be accessible to the enemy long after the security software has erased the secrets in RAM and terminated. (But be aware that the suspend mode on laptops and some desktop computers will save a copy of the system's RAM to disk, regardless of memory locks.)
I would be rather disappointed if a hypervisor swapped out my guest (at least, in a context like AWS; I suppose if you're just running qemu on your laptop, that's a different matter), but I hadn't considered that either, and it is certainly possible.
> All pages that contain a part of the specified address range are guaranteed to be resident in RAM when the call returns successfully; the pages are guaranteed to stay in RAM until later unlocked.
So it doesn't matter if the memory is dirty, as long as it's marked as locked.
The current Linux man page gives a bit more insight:
mlockall() and munlockall()
mlockall() locks all pages
mapped into the address space
of the calling process. This
includes the pages of the code,
data and stack segment, as well
as shared libraries, user space
kernel data, shared memory, and
memory-mapped files. All
mapped pages are guaranteed to
be resident in RAM when the
call returns successfully; the
pages are guaranteed to stay in
RAM until later unlocked.
The flags argument is con‐
structed as the bitwise OR of
one or more of the following
constants:
MCL_CURRENT Lock all pages
which are currently
mapped into the
address space of
the process.
MCL_FUTURE Lock all pages
which will become
mapped into the
address space of
the process in the
future. These
could be, for
instance, new pages
required by a grow‐
ing heap and stack
as well as new mem‐
ory-mapped files or
shared memory
regions. $ cat wheres-the-ram.c
#! /home/rkeene/bin/c
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char **argv) {
unsigned char *buffer;
int mla_ret;
mla_ret = mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT);
if (mla_ret != 0) {
perror("mlockall");
return(1);
}
buffer = malloc(1024LLU * 1024LLU * 1024LLU * 32LLU);
if (!buffer) {
perror("malloc");
return(1);
}
buffer[0] = 1;
buffer[1] = buffer[0];
buffer[2] = buffer[3];
puts("Success !");
return(0);
}
$ sudo ./wheres-the-ram.c
Success !
$ free -g
total used free shared buff/cache available
Mem: 14 1 5 0 7 13
Swap: 0 0 0
$