Libseccomp: RFE:区分未知的系统调用

创建于 2020-08-16  ·  18评论  ·  资料来源: seccomp/libseccomp

由 systemd-devel 上的讨论(六月八月)触发。

systemd-nspawn 选择为非白名单系统调用返回EPERM 。 但是,这在openat2这样的情况下会导致问题,其中 libc 检查ENOSYS并回退到不同的实现。

在我看来,“大部分正确”的解决方案可能是检查系统调用号是否在构建 seccomp 时存在的已定义系统调用的范围内。 我确定有一些极端情况(我知道一些拱门会做奇怪的事情),但是如果解析syscalls.csv等的工具可以为最大已知系统调用号生成一个简单的#define有用?

enhancement prioritmedium

最有用的评论

根据上面的讨论,这里的大多数(全部?)人似乎认为问题 #11 是解决这个问题的正确方法; 是否有人在关闭此问题以支持将未来讨论转移到原始问题(#11)时遇到问题?

所有18条评论

嗨@srd424。 我想确保我理解你在这个问题上的要求......在我看来,你基本上想知道 libseccomp 是否“知道”给定的系统调用,不管那个特定的系统调用是否在那个拱上实现/ABI,是吗?

如果是这样,我相信您应该能够使用seccomp_syscall_resolve_name(...)来获取您想要的信息。 如果返回值为__NR_SCMP_ERROR则系统调用对于 libseccomp 是未知的,如果为正则系统调用存在于本机架构/ABI 中,如果为负则系统调用不存在于本机架构/ ABI。 那对你有用吗?

这可能会使过滤器..冗长!

我希望做的是让过滤规则将系统调用编号与已知的最高编号进行比较,如果更大,则返回ENOSYS ,否则返回EPERM (假设已处理列入白名单的系统调用根据较早的规则。)

但是查看seccomp_rule_add的详细信息,我不确定这会起作用..系统调用号被特殊处理。 大概可以构建一个原始的 bpf 过滤器来执行此操作,但这意味着对 libseccomp 进行一些更具侵入性的更改——可能高于我的工资等级!

添加到 libseccomp 的功能可能是合理的,也可能不是合理的功能,因为我猜原始问题可能发生在库的多个用户身上。 看起来它(只是?)是一个生成稍微复杂的默认操作的问题。

这可能会使过滤器..冗长!

我不太确定你在这里的意思是什么......? 对seccomp_syscall_resolve_name(...)的调用实际上并不影响过滤器,它只是查询内部 libseccomp 系统调用 db 以进行系统调用解析。 您可以调用一次、一千次或从不调用它,您的过滤器将完全相同:)

我希望做的是让过滤规则将系统调用编号与已知的最高编号进行比较,如果更大,则返回 ENOSYS,否则返回 EPERM(假设白名单系统调用已由较早的规则处理。)

好的,我想我现在开始明白你的要求了。 如果系统调用对 libseccomp 未知,您希望过滤器本身而不是应用程序代码执行特定操作(在上面的示例中返回ENOSYS )? 基本上是这样,还是我又错过了什么?

上面提到的第二个线程中的这条评论让我笑了:+1:

我试图在 libseccomp 中打开关于 ENOSYS 处理的讨论
https://github.com/seccomp/libseccomp/issues/286 ,但我可能不是
很连贯。。

在阅读了你提到的线程后,我想我在同一页上。

如果有人(libseccomp、nspawn、任何人)可以返回ENOSYS ,那么 glibc 将尝试从较新的系统调用(例如openat2 )回退到较旧的系统调用(例如openat 。 返回EPERM给 glibc 只会让 glibc 认为调用不被允许,glibc 会放弃。 这是对本期最初评论的公平改写吗?

我认为这个要求是合理的。 如果 libseccomp 能够满足这些需求,我还需要考虑更多,但我目前没有异议。 这里肯定有改善最终用户体验的机会。

感谢 RFE。

如果有人(libseccomp、nspawn、任何人)可以返回ENOSYS ,那么 glibc 将尝试从较新的系统调用(例如openat2 )回退到较旧的系统调用(例如openat 。 返回EPERM给 glibc 只会让 glibc 认为调用不被允许,glibc 会放弃。 这是对本期最初评论的公平改写吗?

是的,听起来不错。 systemd 人员的意见是 EPERM 在大多数情况下对于列入黑名单的系统调用是合理的,大概是因为它向最终用户/管理员传达了“不允许”。 因此,区分“新”和“旧”系统调用并为任何无法识别的事物执行 ENOSYS 的想法。 我假设出于性能原因我们不想枚举和测试 BPF 中的每一个系统调用,因此跟踪每个架构的已知系统调用数的高水位标记似乎是一种“尽力而为”的方式。

知道 docker、podman、lxc 等使用 seccomp 过滤做什么会很有趣,看看它们是否会受益。 与此同时,我为 nspawn 发布了一个补丁,该补丁将允许记录 seccomp 事件,这将使调试更容易一些。

我认为这个要求是合理的。 如果 libseccomp 能够满足这些需求,我还需要考虑更多,但我目前没有异议。 这里肯定有改善最终用户体验的机会。

感谢 RFE。

我同意@drakenclimber ,这个要求听起来很合理,我想我只需要更多时间来考虑可能的解决方案:)

在一个非常基本的层面上,这类似于 RFE #11,最终这可能是以一种对应用程序来说并不可怕的方式实现它的最简单方法:应用程序可以指定最大支持的内核 API 版本,例如v5.8(显然是标记化的),以及任何超出的给定操作,然后 libseccomp 处理其余部分。 这对你们有用吗@srd424?

嗨,这也在https://github.com/systemd/systemd/pull/16739 中进行了讨论

应用程序可以指定最大支持的内核 API 版本,例如 v5.8(显然是标记化的),以及任何超出的给定操作,然后 libseccomp 处理其余部分。

这会很好用。 在 systemd/systemd-nspawn 中,我们希望为任何明确的允许列表和拒绝列表的系统调用返回自定义 errnos,为“支持的内核 API 版本”中的任何其他系统调用返回 EPERM,并为任何新的系统调用返回 ENOSYS。

我认为实现不会太复杂。 例如对于 amd64,“已知”系统调用可以表示为n <= 181 || 186 <= n <= 235 || 237 <= n <= 334 || 424 <= n <= 439 。 并且这些表达式可以很容易地从系统调用表中以编程方式生成。

94也可能是相关的。

今天早上我的咖啡因含量不足,但是让 ENOSYS 处理然后让我们有可能将大型允许名单变成小型拒绝名单,以获得可能的性能胜利吗?

我认为实现不会太复杂。 例如对于 amd64,“已知”系统调用可以表示为 n <= 181 || 186 <= n <= 235 || 237 <= n <= 334 || 424 <= n <= 439。这样的表达式可以很容易地从系统调用表中以编程方式生成。

正如您所提到的,实际的 BPF 将是 arch/ABI 和内核版本特定的。 在上面的 x86_64 示例中,BPF 不会太糟糕,但对于其他架构/版本,我们就没有那么幸运了。 无论如何,这现在是两个有效地要求同一件事的问题,所以我认为这是我们想要做的事情......我只是不会开始跳来跳去谈论它会变得多么容易; )

94也可能是相关的。

有点是,有点不是。 它涉及范围,但 #94 是关于调用者指定的参数范围(这仍然是我认为我们想要做的事情,PR 刚刚出现在一个糟糕的时间,我认为 API 需要一些调整)而我们正在谈论的是由库本身生成的隐式创建的系统调用范围。

今天早上我的咖啡因含量不足,但是让 ENOSYS 处理然后让我们有可能将大型允许名单变成小型拒绝名单,以获得可能的性能胜利吗?

从应用程序的角度来看,例如 systemd,如果您试图阻止“新”系统调用,那么是的......假设我们谈论的是同一件事:)

更具体地说..目前任何试图_安全地_有效阻止某些系统调用的人都必须加入白名单,因为您无法确定较新的内核可能会添加哪些系统调用。 如果我们可以请求 libseccomp 自动阻止未知的系统调用,这意味着我们可以安全地切换到一个小的拒绝名单吗?

更具体地说..目前任何试图_安全地_有效阻止某些系统调用的人都必须加入白名单,因为您无法确定较新的内核可能会添加哪些系统调用。 如果我们可以请求 libseccomp 自动阻止未知的系统调用,这意味着我们可以安全地切换到一个小的拒绝名单吗?

我真诚地希望我们能做到这一点,因为那将是一个非常棒的功能。 例如,Docker 目前正在使用一个允许列表,他们的

如此庞大的列表对性能的影响可能令人望而却步。 请注意,使用我们在 v2.5 中添加的二叉树功能可以在一定程度上缓解这种情况。

更具体地说..目前任何试图_安全地_有效阻止某些系统调用的人都必须加入白名单,因为您无法确定较新的内核可能会添加哪些系统调用。 如果我们可以请求 libseccomp 自动阻止未知的系统调用,这意味着我们可以安全地切换到一个小的拒绝名单吗?

我不明白这是怎么回事。 libseccomp 不知道和黑名单作者不知道通常意味着不同的东西。 这意味着即使 libseccomp 对内部支持的系统调用有更清晰的了解,概念问题也不会消失。

好点 - 我想我们需要由内核版本标记的定义明确的集合才能工作,这似乎正在讨论一下。

我认为实现不会太复杂。 例如对于 amd64,“已知”系统调用可以表示为 n <= 181 || 186 <= n <= 235 || 237 <= n <= 334 || 424 <= n <= 439。这样的表达式可以很容易地从系统调用表中以编程方式生成。

正如您所提到的,实际的 BPF 将是 arch/ABI 和内核版本特定的。 在上面的 x86_64 示例中,BPF 不会太糟糕,但对于其他架构/版本,我们就没有那么幸运了。 无论如何,这现在是两个有效地要求同一件事的问题,所以我认为这是我们想要做的事情......我只是不会开始跳来跳去谈论它会变得多么容易; )

这些表是相当连续的:

>>> l = {int(s[1]):s[0] for s in (s.split() for s in open('syscalls-x86_64').readlines()) if len(s)>1}; x = np.array(sorted(l.keys())); np.diff(x)
array([ 1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  5,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1, 90,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1])
>>> l = {int(s[1]):s[0] for s in (s.split() for s in open('syscalls-alpha').readlines()) if len(s)>1}; x = np.array(sorted(l.keys())); np.diff(x)
array([ 1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  3,  1,  2,  1,  1,
        1,  1,  1,  2,  1,  1,  1,  3, 12,  3,  3,  1, 11,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        2,  1,  1,  1,  1,  1,  1,  5,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1, 39,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  1,  1,  1,  1,  3,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        2,  1,  1,  1])
>>> l = {int(s[1]):s[0] for s in (s.split() for s in open('syscalls-arm').readlines()) if len(s)>1}; x = np.array(sorted(l.keys())); np.diff(x)
array([1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 2, 1, 2, 3, 4,
       1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, 3, 1, 1,
       1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 1, 1, 1, 3,
       1, 1, 1, 1, 1, 1, 2, 1, 3, 1, 1, 1, 1, 1, 3, 3, 1, 1, 2, 1, 1, 1,
       1, 2, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
>>> l = {int(s[1]):s[0] for s in (s.split() for s in open('syscalls-riscv64').readlines()) if len(s)>1}; x = np.array(sorted(l.keys())); np.diff(x)
array([  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   2,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,  16,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1, 130,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1])

我在https://github.com/systemd/systemd/pull/16819 中为 systemd-nspawn 实现了“已知”系统调用的过滤器https://github.com/systemd/systemd/pull/16819/commits/158e30ffd9355a7640a7276276eb9219b6c87914有一些 libseccomp 生成的程序的转储。 那些转储很长,所以我不会在这里重复它们,但是 SCMP_FLTATR_CTL_OPTIMIZE 使程序更高效,但也更长。 通过使用范围比较,事情可以缩短约 50 倍。

我只是刚刚找到这个线程,只是插话说我一直在考虑类似的问题,这绝对是 Docker/runc 想要解决的问题。 使用最高内核版本可能是最好的方法,因为这意味着配置文件编写器(和容器运行时)不需要跟踪乱序添加的系统调用或在撰写本文时最新的系统调用是什么个人资料。

根据上面的讨论,这里的大多数(全部?)人似乎认为问题 #11 是解决这个问题的正确方法; 是否有人在关闭此问题以支持将未来讨论转移到原始问题(#11)时遇到问题?

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

cgwalters picture cgwalters  ·  14评论

pcmoore picture pcmoore  ·  20评论

pcmoore picture pcmoore  ·  14评论

pcmoore picture pcmoore  ·  10评论

erdumbledore picture erdumbledore  ·  3评论