iTranslated by AI
Experimenting with the mlock System Call to Prevent Memory Swap-Out
What is the mlock system call?
The mlock system call prevents memory data from being swapped out. As far as I know, its use cases include:
- Performance improvement.
- Security.
- When plaintext sensitive data is temporarily stored in memory, this prevents the plaintext data from remaining on the disk if it gets swapped out.
-
IOURING_REGISTER_BUFFERS.- This appears to be a feature for registering fixed buffers in
io_uring, though I have not used it and am not very familiar with it. (It sounds interesting, so I would like to try it separately.) - From a quick look, it seems to relate to "asynchronous I/O operations for storage devices."
- This appears to be a feature for registering fixed buffers in
The interface is simple, requiring only the memory address to be locked and its size.
#include <sys/mman.h>
int mlock(const void *addr, size_t len);
int munlock(const void *addr, size_t len);
Background for my investigation
I am developing a password manager, and I was looking for ways to prevent plaintext data expanded in memory from being swapped out and saved as sensitive plaintext data on the disk.
Limitations of mlock
Paging is essentially a technique for efficiently utilizing limited memory; if you indiscriminately mlock everything, the OS will be unable to use memory, leading to instability.
Therefore, there is an upper limit on the size that can be mlock'd. In my environment (Ubuntu 24.04 Desktop, 64GB RAM), it was about 8GB.
$ ulimit -l
8179008
Additionally, the process calling mlock must have the CAP_IPC_LOCK capability to use it.
Trying out the mlock system call
The environment is Ubuntu 24.04 desktop LTS.
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("please specify malloc size\n");
return 0;
}
char buf[255];
const size_t mem_size = atoi(argv[1]);
printf("memory size: %ld byte\n", mem_size);
void *mlock_mem = malloc(mem_size);
int mlock_ret = mlock(mlock_mem, mem_size);
printf("mlock return: %d\n", mlock_ret);
if (mlock_ret != 0) {
perror("mlock");
return 1;
}
printf("wait until input...\n");
fgets(buf, sizeof(buf), stdin);
int munlock_ret = munlock(mlock_mem, mem_size);
printf("munlock return: %d\n", munlock_ret);
free(mlock_mem);
return 0;
}
(It has been so long since I wrote C/C++ that I was a bit confused.)
Compile it.
cc mlock.c
Try running it.
./a.out `expr 10 \* 1024`
Verify that it has been locked correctly (<PID> is the process ID of a.out).
$ grep VmLck /proc/<PID>/status
VmLck: 12 kB
In the execution example above, I specified 10KB, but VmLck shows 12KB. This is likely because pages are allocated in 4KB increments, so it was rounded up to 12KB.
Next, let's set a lower limit using ulimit -l.
$ ulimit -l 64
$ ./a.out `expr 100 \* 1024`
memory size: 102400 byte
mlock return: -1
mlock: Cannot allocate memory
As expected, it resulted in an error.
Conclusion
In this article, I tried out the mlock system call.
As for whether I will use it in my password manager, I think I will not.
The reason is that it has many limitations, making it difficult to use in a standard desktop application.
Furthermore, swap files themselves generally cannot be read without root privileges. However, if an attacker has already obtained root privileges, they could perform a process memory dump anyway...
Therefore, the basic policy will likely be to not hold sensitive data in plaintext in the process unless absolutely necessary, and to zeroize plaintext data in memory after use.
Discussion