背景
在 Linux 系统编程中,“一切皆文件"是一个核心哲学。但有些场景下,你并不想真正落盘——你需要的只是一个存在于内存中的临时文件,用完即焚,不留痕迹。
传统做法是在 /tmp 下创建临时文件,用完 unlink。但这条路有隐患:残留在磁盘或 swap 中的数据可能被恢复;文件路径可在 /proc 中被窥探;而且涉及真实文件系统操作,有性能开销。
安全场景更麻烦。你的程序持有私钥、会话 Token 或加密内存中的敏感数据。即使进程空间被保护,内核依然可以通过 direct map 访问任何物理页面。如果内核被攻破,数据直接裸奔。
Linux 提供了两个内核特性来解决这两个层次的问题:
memfd_create()—— 纯内存匿名文件,不落盘,无路径暴露(自 Linux 3.17)memfd_secret()—— 秘密内存区域,连内核都看不到(自 Linux 5.14)
前者是"隐形文件”,后者是"内核都看不见的保险柜"。
核心原理
memfd_create:匿名的内存文件
memfd_create() 创建一个完全驻留在 RAM 中的匿名文件,返回一个文件描述符。它看起来、用起来都像普通文件——可以 ftruncate、mmap、write、read、splice——但它的存储后端是匿名页面,而非磁盘上的 inode。
关键特性:
- 自动清理:所有引用关闭后,文件自动释放,无需手动 unlink
- 进程间共享:可通过 UNIX domain socket 传递 fd,或在
fork后共享 - 文件封印(File Sealing):通过
MFD_ALLOW_SEALING标志启用,配合fcntl(F_ADD_SEALS)防止误修改 - HugeTLB 支持:从 Linux 4.14 起可用
MFD_HUGETLB使用大页
fd 在 /proc/self/fd/ 中显示为 memfd:<name> (deleted)——它已被 unlink,不再有文件系统路径。
memfd_secret:连内核都看不见的秘密内存
memfd_secret() 是 memfd_create() 在安全维度上的超集。它创建的文件描述符对应的物理页面会被从内核的 direct map 中移除。这意味着内核再也看不到这些页面、侧信道攻击读不到、get_user_pages() 拒绝返回这些页面的指针、DMA 也无法访问。而且这些页面的指针不能传给任何系统调用——内核根本不知道它们的虚拟地址。
实现原理:memfd_secret() 背后的 mm/secretmem.c 使用 set_direct_map_invalid_noflush() 在页面分配时解除内核 direct map 的映射。页面被锁定在内存中(类似 mlock()),防止被 swap 到磁盘。当存在活跃的 secret memory 用户时,休眠(hibernation)被自动禁用。
API 对比
| 特性 | memfd_create() | memfd_secret() |
|---|---|---|
| 引入内核版本 | 3.17 | 5.14 |
| 存储后端 | 匿名页面(RAM) | 匿名页面(RAM) |
| 内核可见性 | 内核可通过 direct map 访问 | 从内核 direct map 移除 |
| 文件封印 | 可选 (MFD_ALLOW_SEALING) | 不支持 |
| 页面锁定 | 无 | 自动 mlock(受限 RLIMIT_MEMLOCK) |
| Swap 保护 | 允许 swap | 禁止 swap |
| 休眠影响 | 无 | 有活动区域时禁用休眠 |
| glibc wrapper | 有(glibc 2.27+) | 无(需 syscall(SYS_memfd_secret)) |
| 启动启用 | 默认可用 | Linux 6.5 后默认启用;之前需 secretmem_enable=1 |
| 典型用途 | 文件描述符传递、共享内存、文件封印 | 密钥存储、敏感数据隔离、防内核泄漏 |
代码实战
示例 1:使用 memfd_create 创建匿名内存文件
| |
编译:gcc -o memfd_demo memfd_demo.c
示例 2:使用 memfd_secret 创建秘密内存区域
| |
编译:gcc -o secret_demo secret_demo.c
运行前确认内核支持:
| |
生态现状
使用 memfd 的知名项目
| 项目 | 用途 | 说明 |
|---|---|---|
| systemd | memfd_create() 实现日志传输、进程间文件传递 | systemd-journald 用 memfd 高效传递日志数据 |
| QEMU | memfd_create() 替代 tmpfs 文件用于 vhost-user 后端 | 减少文件系统操作,支持 MFD_HUGETLB 提升虚拟化性能 |
| Docker / containerd | memfd_create() 在容器启动时传递配置和文件 | 用 memfd 替代临时文件,避免在宿主机留下痕迹 |
| GNOME / Wayland | memfd_create() 用于 wl_shm 共享内存 | 替代传统的 shm_open 方式创建共享缓冲区 |
| OpenSSL (secret memory preloader) | memfd_secret() 保护私钥内存 | IBM 提供了 preloader 库,重定向 OPENSSL_malloc 到 secret memory |
| WebKit / Chromium | memfd_create() 用于隔离进程间内存共享 | 替代管道传递大块数据,减少拷贝 |
| 文件逃避型恶意软件 | memfd_create() 绕过文件扫描 | 恶意代码直接用 memfd 加载 payload,磁盘上不留痕迹——这是 memfd 被滥用的阴暗面 |
memfd 的两个面孔
memfd 是一把双刃剑。合法用是高性能 IPC 和内存安全工具。但在攻击者手中,memfd_create() 成为"无文件恶意软件"的理想载体——直接在内存中执行 SHELLCODE,绕过基于文件的 AV 扫描。Sysdig、bpflock 等工具已支持检测来自 memfd 的可执行文件。
memfd_secret() 则始终专注防御——它针对内核级攻击面,为私钥、密码、会话令牌提供硬件级内存隔离。Linux 6.5 后默认启用,采用率正在上升。
今日可执行动作
验证内核支持:
cat /proc/cmdline | grep -o secretmem_enable。内核 >= 6.5 默认启用,无需参数。检查/proc/config.gz中CONFIG_SECRETMEM=y。编译运行上面的
secret_demo.c:gcc -o secret_demo secret_demo.c && ./secret_demo。如果返回ENOSYS,说明内核未编译CONFIG_SECRETMEM。查看系统中有哪些进程在用 memfd:
ls -la /proc/*/fd/ 2>/dev/null | grep memfd
参考
- memfd_create(2) - Linux man page (man7.org)
- memfd_secret(2) - Linux man page (man7.org)
- mm: introduce memfd_secret system call (LWN.net, 2020)
- memfd_secret() in 5.14 (LWN.net, 2021)
- Linux 5.14 Can Create Secret Memory Areas With memfd_secret (Phoronix, 2021)
- Using Linux’s memfd_secret syscall from the JVM with JEP-419
- argv and memfd_secret
- Linux Kernel memfd_secret patches (lore.kernel.org)
- Fileless Malware Detection with Sysdig Secure
- eBPF: Block Linux Fileless Payload Execution with BPF LSM