关注了就能看到更多这么棒的文章哦~
Hiding a process's executable from itself
By Jonathan Corbet
January 23, 2023
DeepL assisted translation
https://lwn.net/Articles/920384/
早在 2019 年的时候,由于一个重要的容器(container)漏洞 (https://lwn.net/Articles/781013/)而引发了一些复杂的 workaround 和疯狂的 fix。虽然这个问题本身被 fix 了,但这一事件非常严重,于是有安全意识的开发人员们一直在寻找方法来防止未来出现类似的漏洞。Giuseppe Scrivano 的这组 patch 就采取了一个相当简单的方法来解决这个问题。
2019 年的事件被称为 CVE-2019-5736,其中牵涉到一连串的步骤,最终做到了从容器内把 runc 这个容器运行时(container runtime)二进制文件覆盖掉了。该二进制文件甚至完全不应该被容器内看到的,更不用提进行写操作了,但这样的障碍对于一个坚定的攻击者来说就像是一个挑战。在这个例子里,攻击者能够通过 /proc/self/exe 来访问到该二进制文件,这根文件总是指向当前进程的二进制可执行文件。
具体来说,攻击者打开 runc 进程的 /proc/self/exe 文件,在容器内为这个目标二进制文件创建一个只读的文件描述符,而目标二进制文件就在该容器之外。一旦 runc 退出,攻击者就能重新打开该文件描述符从而进行写入操作了;该描述符后面就可以用来把 runc 二进制文件覆盖修改。由于 runc 在 container runtime 之外是采用特权权限执行的,这就变成了对整个 host 系统的攻击了;详情见上述链接的文章。
解决这个漏洞,是通过让 runc 将其二进制文件复制到 memfd 区域并 deal 起来;在进入容器之前会将控制权传递给该二进制程序。seal 动作可以防止恶意对它修改改镜像,但即使这个 seal 保护失败了也不用担心,container 是从一个独立的二进制拷贝中运行的,这个二进制拷贝不会被再次使用,所以将它覆盖写入也不会再有什么风险。这是一个有点复杂的 workaround,但当时确实可以堵住这个漏洞。
Scrivano 又提出了另一个解决方案:直接禁止对/proc/self/exe 的访问,作为阻止这种类型的二进制文件覆盖攻击的一种方式。具体来说,它增加了一个新的 prctl() 命令,叫做 PR_SET_HIDE_SELF_EXE,可以用来禁止对 /proc/self/exe 的访问。一旦这个选项被启用了,那么任何试图在属主进程中来打开该文件的尝试都会得到 ENOENT 错误,就像该文件根本不存在一样。启用操作是一个单向的操作。一旦被打开了,就不能再被禁用,直到下一个 execve()调用才会将这个选项重置为禁用状态。
这种行为是缺省关闭的(opt-in),也就是需要特别选择了才能打开。任何程序如果想让它的可执行程序文件能受到这种保护,就必须明确进行调用。不过,目标是让这个简单的调用来取代目前为防止这种攻击而需要的更复杂的 workaround。如果 prctl() 能够去除掉每次启动新容器时都创建新的可执行镜像动作,系统就可以不用再付出这个代价了。
因此,这个新选项是阻止这种特定攻击的一个简单方案,但它导致了一些相关的问题。隐藏容器运行时的二进制文件似乎不如确保这个二进制文件不可以被修改,无论它在容器中是否可见,这都是一个令人满意的解决方案。这似乎是关闭了一个特定的攻击途径,而没有解决根本问题。
更重要的是,也许取决于内核开发者想增加多少动作来防止访问一些可能被滥用的资源的问题。可以想象,对于一个想尽办法要攻破系统的攻击者来说,可以利用到的文件(在/proc 或其他什么地方)是无穷无尽的。为每一个这样的文件都添加一个新的 prctl()选项以及修改相关内核代码,长远来看可能会弄得越来越乱。在某种程度上来说,实现一个安全模块(security module)来实现这种保护策略可能更好。
不管开发社区是不是这么想的,反正它并没有这么说出来,其实什么反馈也没有。这组 patch 已经发布了三次,但都没有收到任何实质性的反馈意见。最终也许需要进行一下讨论,来决定这种机制是否是保护系统免受 /proc/self/exe 攻击的最佳方式。不过就目前而言,这一改动似乎还需要等待更多的人注意到它。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~