Linux 上执行内存中的脚本和程序
在 Linux 中可以不需要有脚本或者二进制程序的文件在文件系统上实际存在,只需要有对应的数据在内存中,就有办法执行这些脚本和程序。
原理其实很简单,Linux 里有办法把某块内存映射成文件描述符,对于每一个文件描述符,Linux 会在/proc/self/fd/<文件描述符>
这个路径上创建一个对应描述符的实体,这个路径可以当成普通的文件来用,能正常从中读出数据,因此只要有可执行权限,就可以加载后运行。
其中第一步是创建内存到文件描述符的映射,这一步可以靠memfd_create
这个系统调用实现。这个系统调用会返回一个文件描述符,关联到一块内存上,默认大小是 0,大多数对普通文件描述符可行的操作对这个描述符也都可用,比如 read,write,ftruncate,close。write 数据进去的时候系统会自动分配合适长度的内存。当所有引用这块内存的 fd 被 close 之后,这块内存会被自动释放。
总之memfd_create
提供了像操作文件一样操作内存的能力,是一切皆文件理念的体现之一。
而且memfd_create
创建的页面默认有可执行权限,在 proc 底下的对应的描述符文件也有可执行权限。
所以我们只要把脚本或者二进制程序的数据写进memfd_create
返回的描述符就已经做完前两步了。其中对于脚本有一些要求,需要带有 Shebang(类似#!/usr/bin/env python3
这种)。
有一点需要注意,虽然/proc/self/fd/<文件描述符>
有描述符文件存在,但实际上这就是个软链接,而我们的数据全在内存里。
写入成功后可以利用 execve 执行 proc 下的描述符文件,也可以通过fexecve
系统调用直接调用文件描述符。golang 没提供 fexecve,所以示例用exec.Cmd
。
例子:
golang 的话还以配合 embed 把二进制程序的数据提前嵌入程序内,这样写入的时候会比较方便。
安全性:memfd_create 创建的东西默认有可执行权限,同时默认也是可写的,很可能会被恶意程序利用,所以目前内核也在推进解决这个问题已经添加了 flag 可以让不添加可执行权限,这里建议是遵守权限最小化的原则。
memfd 原本的用途:用来在内存中创建文件(比如不想在存储器上创建文件时可以用这个),并可以在父子进程间传递(最好配合 file sealing api 使用,防止数据被意外修改);或者干脆当匿名共享内存用。执行内存中的程序是附带效果。
文章转载自:apocelipes
评论