进程如何检测它位于子命名空间中?

进程如何检测它位于子命名空间中?

我研究了这个主题并在 Github 上找到了以下代码,其中写道:

// HasNamespace determines if a container is using a particular namespace or the
// host namespace.
// The device number of an unnamespaced /proc/1/ns/{ns} is 4 and anything else is
// higher.
// Only works from inside a container.

https://github.com/genuinetools/amicontained/blob/568b0d35e60cb2bfc228ecade8b0ba62c49a906a/vendor/github.com/jessfraz/bpfd/proc/proc.go#L461

然而,该评论已过时,这可以通过以下方式证明:

$ docker run -ti --rm --pid host debian
root@e29ab2d7176b:/# stat --format="%d" /proc/self/ns/net
58
root@e29ab2d7176b:/# stat --format="%d" /proc/self/ns/pid
58

如果该评论正确的话,这里的stat --format="%d" /proc/self/ns/pid结果应该是 4。

进程如何检测它是否位于子命名空间中?

答案1

我可以分享一下我前段时间自己研究这个主题时发现的情况。它当然不权威也不详尽,但可能会有所帮助。

技术说明

免责声明

我本人从未真正实施过我将要描述的任何方法。它们只是我前一段时间参与的一个自定义容器化项目的可能性,但后来决定完全放弃命名空间检测。如果有兴趣,请继续阅读。


这是一种通过利用内核观察到的行为来检测初始命名空间的方法。 (但注意这些行为不是官方 API)。这种方法可以在最常见和合理的设置下工作,尽管情况可能并不总是如此。

“启动”命名空间

从...开始内核v3.8到当前最新稳定版v5.11(和当前的 v5.12-rc),最初的IPC、UTS、用户、PID、cgroup 和时间命名空间始终具有特定的硬编码 ID,如下所示。因此我们可以有把握地假设对于那些命名空间类型任何大于固定 ID 的命名空间 ID 都可以被视为子命名空间:

IPC    = 0xEFFFFFFF
UTS    = 0xEFFFFFFE
USER   = 0xEFFFFFFD
PID    = 0xEFFFFFFC
CGROUP = 0xEFFFFFFB
TIME   = 0xEFFFFFFA

上面的列表取自 v5.12-rc6 源代码,但自 v3.8 以来这些值始终相同,当然 v3.8 中根本不存在的命名空间除外(“cgroup”已添加到v4.6,而 v5.6 中的“时间”)。

请注意这些初始命名空间是如何(多年来)随着值向下“增长”而添加的。相反,所有子命名空间都采用顺序(按需)值向上生长从0xF0000000

所以,对于没有祖先进程覆盖初始命名空间的设置,这些固定值可以非常巧妙地解决这些名称空间的“检测任务”。

然而,让我重申这些价值观是一点也不暴露给用户空间的任何官方 API 的一部分(甚至不包括内核空间 AFAICT)因此它们将来可能会发生变化

内核开发人员甚至可能选择使它们全部动态甚至随机。事实上,您可能会注意到挂载和网络命名空间不在该列表中,这是因为全部网络和挂载命名空间,包括最初的那些已经是完全动态的,并且总是0xF0000000像任何子命名空间一样从 ID 开始。因此,对于挂载和网络初始命名空间,即使在最友好的条件下,我们仍然必须进行一些启发式操作。

挂载命名空间

根据我迄今为止的经验,我注意到初始挂载命名空间 ID 始终获取第一个动态值 ( 0xF0000000)。推测这是由于初始 PID 命名空间实例化了通用proc文件系统,因此也拉入了第一个挂载命名空间。无论如何,初始挂载命名空间的 ID 似乎很容易预测,即使在动态范围内,实际上也是固定的。

网络命名空间

另一方面,当配置更改影响动态生成的 inode 编号序列时,初始网络命名空间 ID 会获得相距甚远的值,甚至可能与具有相同操作系统的同一台计算机的先前启动不同。因此,检测初始网络命名空间可能成为真正的彩票。你通常可以“获胜”,但这需要假设一些事情,尽管它们在常见的理智设置中成立,但并不一定总是成立。

第一个网络命名空间是作为自系统启动以来请求第一个网络操作的任何进程(通常是 PID 1)的结果而实例化的。因此,/proc/net/目录变得可用,并在其中创建文件/目录,每个文件/目录都有自己的索引节点号,这些索引节点号是从用于名称空间 ID 的相同(动态)值分配的。碰巧(根据我在撰写本文时迄今为止的经验),其中创建的第一个名称是目录stat。因此该目录采用最后的生成的索引节点号就在之前网络命名空间的实例化。因此,网络命名空间自己的 ID 是/proc/net/statinode 号 + 1。

当然/proc/net/stat,目录实际上是一个“命名空间”名称本身,如任意进程所见,不一定指的是最初的网络命名空间。当进程访问该目录时,它确实指的是初始网络名称空间生活在初始命名空间中(即,它是非容器化进程),但在容器化环境中,更可能引用该进程所属的专用网络命名空间,而不是初始网络命名空间。

问:那么,一个进程如何尝试一般性地猜测其网络名称空间是否实际上是第一个网络名称空间呢?

A:通过枚举递归地全部可见非pid在其目录中的 files/dirs 中/proc查找 inode 编号,直到0xF0000001遇到第一的的洞至少2缺少索引节点号。

许多非 pid 文件/目录/proc(迄今为止)对于所有 PID 命名空间都是通用的,因为它们与内核的核心功能(例如 irq 统计信息等)相关。它们的索引节点号中的漏洞必须是至少2邻近的数字,因为一个用于/proc/net/stat为初始网络命名空间创建的目录,另一个用于初始网络命名空间本身(也假设两者之间的原子分配)。在这样的第一个洞里是初始网络命名空间的 ID。将该 ID(漏洞)与进程自己的(或其他任意)网络命名空间的 ID 进行比较,(在最常见的情况下)您终于一切就绪。

然而,即使对于常见情况,很明显我们也依赖于那些非 pid 名称总是可见于全部PID命名空间与命名空间的 ID 共享相同的编号处于(近乎)完美的顺序索引节点号/proc/net/*与命名空间自己的 ID 一起自动分配。所有这些假设可能现在确实如此,但将来很可能不再如此,因为这种行为根本不是官方 API。

另外,只是为了进一步指出这件事有多么棘手,请注意,下面查看的/proc总是进程的 PID 命名空间已安装具体的 /proc目录,因此不一定是进程的 PID 命名空间阅读那个/proc目录。在理智的实践中,“安装者”/proc和“读者”之间的差异/proc不太可能发生,但仍然完全有可能,并且很容易导致不一致的分析。


一些固执己见的考虑

除了最初的用户命名空间,其检测非常容易1并且也是官方 API 的一部分,检测命名空间是一个需要花费大量精力来解决的问题(如果可能的话),因为没有真正且全面的 API 支持它(据说这是是为了更充分的隔离而故意的)。几年前有几个ioctl(2)操作已添加到命名空间列表中,但它们仍然非常有限,我无法理解使用它们的任何方式(甚至是疯狂的方式)确定的检测目的。

确实还有一些其他简单的技巧来检测 PID 命名空间,但它们也不是官方 API。看看systemd人们的例子最近也讨论过为他们的工具。显然,他们也探索过设备编号为“3 或 4”的问题proc,但放弃了这个想法,因为他们注意到它并不能容纳那么多(也许它只在“阳光明媚的日子”条件下举行,无论多么常见他们可能是)。他们还探索了 PID 2 始终[kthreadd]和/或总体存在内核线程,这将是初始 PID 命名空间无可争议的标志,但他们也放弃了这个想法,因为使用 挂载prochidepid=[12]完全违反该检查。

我想说,检测名称空间的根本问题是它们本质上是任意的,并且可以完全被其他名称空间取代。对于所有名称空间类型,内核确实具有所谓的“初始”名称空间,但第一个 PID 1 进程(甚至是 中的进程initramfs)可能会选择unshare(2)在启动之前通过简单地对所有(甚至只是其中几个)进行 -ing来覆盖它们任何其他过程。显然,在这种(不太)假设条件下,检测初始名称空间的探索失去了任何有用的意义,因为相关的是“主机”名称空间。这些是名称空间操作系统(即主要的PID 1init进程)运作一旦引导,即使这样的“主机”命名空间可能已经是孩子就内核而言,命名空间。我并不是说init进程真的总是会覆盖初始命名空间,但原则上它们可以,这足以削弱任何命名空间检测工具。

在我看来,事情还在于,对于大多数实际用例,您并不是真的有兴趣随意的命名空间。几乎可以肯定,您对 UTS、IPC、cgroup 和时间命名空间根本不感兴趣,甚至可能对用户和 PID 命名空间也不感兴趣。如果有的话,您可能只对挂载和网络命名空间感兴趣,因为这些命名空间与访问数据和连接相关。 PID 命名空间经常被大量查找,只是因为 PID 命名空间(比用户命名空间要多得多)通常暗示更广泛意义上的容器,而“更广泛”的容器只是带来有趣的挂载和网络命名空间。不幸的是,后者是最难追捕的,这可能就是为什么检测工具更喜欢寻找 PID 命名空间,希望在它与挂载/网络命名空间之间建立松散绑定但良好的关系。

总而言之,所有这些“如果”、“但是”和警告,问题在于尝试检测“初始或子”名称空间的努力是否值得。我敢说通常不会,您可能根本不检测它们,或者只检测您对特定明确定义用例的“主机”命名空间的缩小定义,情况可能会更好。

华泰


1. 只需读取/proc/self/uid_map文件并查看其报告是否0 0 4294967295准确

相关内容