Libseccomp: BUG:兼容openmp

创建于 2017-09-02  ·  19评论  ·  资料来源: seccomp/libseccomp

多线程的非确定性使得它很难调试,但我认为,我已经得到了一个相当可重现的测试用例seccomp_omp.c.gz 。 用-O0 -ggdb -fopenmp编译程序。

如果我们只用一个线程执行它并让它执行被禁止的系统调用,程序就会按预期死亡。

$ ./seccomp_omp -t 1 -k 0      
86195973
[1]    10103 invalid system call (core dumped)  ./seccomp_omp -t 1 -k 0

但是,如果有更多线程,它就会停止运行。

$ ./seccomp_omp -t 2 -k 0
86198868
86195973
86200317
^C

程序不会被杀死,它只是停止做任何事情。 我不知道 SIGSYS 信号发生了什么,为什么它没有得到处理?

这可能根本不是 libseccomp 问题,但我对 seccomp、信号和线程系统的一般情况不够精通,无法调试根本原因。 希望你能对此有所了解。

bug prioritmedium

所有19条评论

注意:我还没有看过你的测试用例,我只是根据你写得很好的描述猜测

考虑到它的多线程特性,您是否尝试在将过滤器加载到内核之前将SCMP_FLTATR_CTL_TSYNC过滤器属性设置为 true?

我没有设置SCMP_FLTATR_CTL_TSYNC因为我在拆分成线程之前添加了 seccomp 过滤器。 因此,内核应该将过滤器应用于所有线程,无论如何(显然它确实这样做了)。 将SCMP_FLTATR_CTL_TSYNC到测试用例没有任何区别(顺便说一句,它少于 100 行,带有迂腐的错误处理)。

好吧,我只想提一下; 听起来您正在做正确的事情(在产生新线程之前设置过滤器)。 我得仔细看看,但我可能不会很快有机会这样做。

确认我没有做公然错误的事情对我来说已经足够了。 慢慢来!

在单线程示例./seccomp_omp -t 1 -k 0 ,openmp 识别出只有一个线程将运行,因此 openmp 绕过了它通常在多线程 for 循环中执行的大部分同步。 我通过观察内核中 seccomp_run_filters() 中处理的系统调用来验证这一点。 正如人们所料,我看到对 __NR_write() 的调用和对 __NR_madvise() 的调用促使 seccomp 指示内核终止线程。

在多线程示例./seccomp_omp -t 2 -k 0 ,openmp 尝试并行化 for 循环。 因此,使用了更多的 openmp 库。 当再次通过 seccomp_run_filters() 观察系统调用时,这一点很明显。 __NR_mmap()、__NR_mprotect()、__NR_clone()、__NR_futex() 和更多系统调用在调用 madvise() 之前被调用。 最终,两个线程之一最终调用了 madvise() 并且 seccomp 正确地终止了该线程。 但是 openmp 具有线程之间的同步,并且在第二个线程可以调用 madvise() (并被杀死)之前,第二个线程调用 futex() 因为它正在等待来自死线程的某些东西。 这就是程序挂起的原因。 一个线程已被杀死,第二个线程正在等待永远不会到达的数据(来自死线程)。

我没有使用OpenMP很多工作,但似乎有一些讨论[ 123上并行块信号,...]。 最终它看起来像是一个你最好避免的难题。

似乎有几个潜在的简单解决方案:

  1. 不要使用SCMP_ACT_KILL作为默认处理程序。 相反,改用错误代码,例如SCMP_ACT_ERROR(5) 。 我在您的示例程序中进行了此更改,并验证它是否正确终止。

  2. 如果您不需要 openmp 的强大功能,另一种选择可能是切换到使用更常见的解决方案,例如 pthread_create() 或 fork()

@drakenclimber所以听起来问题真的只是 openmp 进行了额外的系统调用,而这些系统调用通常可能不是过滤器的一部分? 或者这是否缺少一个更大的点?

@drakenclimber非常感谢您投入时间。 等待死线程解释了症状。

提供更多上下文:我编写科学代码,因此我喜欢使用 OpenMP 保持简单。 但是,我也想保护我的用户免受他们自己的伤害。 因此,如果我的程序做了任何不寻常的事情,就直接取消它。 我猜,我最终想要实现的只是用一个有点合理的错误消息终止进程(#96)。

我应该为SCMP_ACT_ERROR errno 使用什么值来密切模仿SECCOMP_RET_KILL_PROCESS ? 是否有一些可以在所有系统调用中正常工作的东西?

@pcmoore - 有点。 但我认为更大的问题是 openmp 不能在其并行结构内部优雅地处理信号。 我猜这是设计使然。

@kloetzl - 不用担心 - 您可以完全坚持使用 OpenMP。 看起来 OpenMP 社区中的其他人正在执行以下操作:

ctx = seccomp_init(SCMP_ACT_ERRNO(ENOTBLK));
...
bool kill_process = false;
int rc;
#pragma omp parallel for num_threads(THREADS)
  for (int i = 0; i < THREADS * 2; i++) {
    fprintf(stderr, "%u\n", func(i));
    if (i == K) {
      rc = madvise(NULL, 0, 0); 
      if (rc < 0)
        kill_process = true;
      }   
    // sleep(10);
  }

  if (kill_process)
    goto error;

  seccomp_release(ctx);
  return 0;
error:
  seccomp_release(ctx);
  return -1; 
}

至于 errno 应该SCMP_ACT_ERRNO()返回什么,这真的取决于你。 SCMP_ACT_ERRNO()将适用于所有系统调用。 您的程序将负责处理它并将错误返回给用户,因此您可以选择最适合您的 errno。 事实上,选择一个不起眼的 - 例如ENOTBLK - 可能是您知道错误来自 seccomp 阻止调用的一种方式。

tl;dr - 检测并行循环中的问题,但要等到循环完成后再进行处理。 由于系统调用失败,您可能需要在循环中添加额外的防御性编码。

@kloetzl - 我将研究问题 #96,看看它与 OpenMP 的配合效果如何。 这也可能是另一种解决方案。 谢谢!

您的程序将负责处理它并将错误返回给用户,因此您可以选择最适合您的 errno。 事实上,选择一个不起眼的人——比如 ENOTBLK——可能是你知道错误来自 seccomp 阻止调用的一种方式。

问题是, madvisemalloc深处被称为。 因此,我不是处理错误的人,glibc 是。 所以问题是,glibc(以及所有其他执行系统调用的库)是否知道系统调用可以返回其手册页中给出的其他错误并对其进行适当处理?

问题是,madvise 在 malloc 的深处被称为。 因此,我不是处理错误的人,glibc 是。 所以问题是,glibc(以及所有其他执行系统调用的库)是否知道系统调用可以返回其手册页中给出的其他错误并对其进行适当处理?

啊……明白了。 我必须查看 glibc 代码才能确定,但​​我愿意冒险进行以下猜测:

  • 如果madvise返回错误,glibc _肯定_也会返回错误
  • glibc 绝对有可能返回与 seccomp 返回的错误不同的错误,因此如果您期望像ENOTBLK这样的“自定义”返回代码,则应谨慎使用

长话短说,glibc 不应该抑制 _any_ 错误代码,但它可以返回不同的错误代码

我正在努力跟上你们的步伐,但我担心我缺乏 OpenMP 的背景会让我有点落后......根据上面的评论它看起来KILL_PROCESS在这里可能是一个可行的解决方案? 如果是这样,我们可以提高该问题的优先级,尽管它在我在 v2.4 发布之前要解决的问题列表中。

@pcmoore - 是的,我相信KILL_PROCESS是解决此错误的可行解决方案。 我使用KILL_PROCESS操作测试了上面@kloetzl的测试程序,并且不再发生挂起。

我已经实现了,但目前在 python 自动测试中遇到了一些障碍。 下周我应该有一个补丁。

@drakenclimber哦,补丁? 我喜欢补丁:)

谢谢你们。

我可以确认KILL_PROCESS解决了这个问题:我也在 Arch 上破解了本地版本的 libseccomp 并且测试程序按预期失败。 然而,提供可靠的测试并在不同的内核上运行它们可能是更大的挑战。

感谢@kloetzl 的确认。

是的,编写适当的测试可能很乏味,但很重要。 就像它测试恕我直言的代码一样重要。

我期待着来自@drakenclimber的 PR,一旦代码发布,我们可以再讨论一些事情。

我正在做一些COVID-19春季大扫除,我认为我们已经解决了您的问题,@kloetzl 是否正确? 我将关闭此问题,但如果我错了并且您仍然看到问题,请告诉我们,我们将重新打开它!

谢谢提醒,我最后测试了一下。

这个问题大部分是固定的,有一个小问题。 当遇到无效的系统调用时,程序不再挂起,是的! 使用SCMP_ACT_TRAP甚至可以生成坏系统调用的名称。 但是,OpenMP 会覆盖我的信号处理程序,因此一旦产生多个线程,它就会回退到默认错误消息。 从用户体验的角度来看,我不喜欢这种行为,但认为它是最好的。

啊,是的,我不确定关于信号处理程序在 libseccomp 中被覆盖的问题,我们对此很抱歉。

感谢您让我们知道它的其余部分有效!

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