10

假设应用程序在取消点被阻塞,例如read,接收到信号并调用信号处理程序。Glibc/NPTL 通过在系统调用期间启用异步取消来实现取消点,据我所知,异步取消将在信号处理程序的整个持续时间内保持有效。这当然是非常错误的,因为有很多函数不是异步取消安全的,但需要安全地从信号处理程序调用。

这给我留下了两个问题:

  • 我错了还是 glibc/NPTL 行为真的有这么危险的破坏?如果是这样,这种危险行为是否符合要求?
  • 根据 POSIX,如果在进程执行作为取消点的函数时调用信号处理程序会发生什么?

编辑:我几乎说服自己,任何作为潜在目标的线程都pthread_cancel必须确保在该线程的上下文中永远不能从信号处理程序调用作为取消点的函数:

一方面,可以在可能被取消的线程中调用的任何信号处理程序以及使用任何 async-cancel-unsafe 函数的任何信号处理程序都必须在调用作为取消点的任何函数之前禁用取消。这是因为,从被信号中断的代码的角度来看,任何这样的取消都将等同于异步取消。另一方面,信号处理程序不能禁用取消,除非在调用信号处理程序时将运行的代码仅使用异步信号安全函数,因为pthread_setcancelstate它不是异步信号安全的。

4

3 回答 3

4

回答我自己问题的前半部分:glibc 确实表现出我预测的行为。在取消点阻塞时运行的信号处理程序在异步取消下运行。要看到这种效果,只需创建一个调用取消点的线程,该取消点将永远(或很长时间)阻塞,等待片刻,向其发送信号,再次等待片刻,然后取消并加入它。信号处理程序应该以某种方式摆弄一些易失性变量,使其清楚地表明它在异步终止之前运行了不可预测的时间量。

至于 POSIX 是否允许这种行为,我仍然不能 100% 确定。POSIX 状态:

每当线程启用了可取消性并且以该线程为目标发出了取消请求,然后该线程调用了作为取消点的任何函数(例如 pthread_testcancel() 或 read()),则应执行取消请求在函数返回之前。如果线程启用了可取消性,并且在线程在取消点挂起时以线程为目标发出取消请求,则应唤醒该线程并对取消请求进行处理。未指定是否对取消请求进行操作,或者取消请求是否保持未决状态并且线程在以下情况下恢复正常执行:

  • 线程在取消点挂起,并且它正在等待的事件发生

  • 指定的超时已过期

在取消请求被执行之前。

据推测,执行信号处理程序不是被“暂停”的情况,所以我倾向于将 glibc 的行为解释为不符合标准。

于 2011-04-20T04:18:32.360 回答
1

富有的,

我在进行 Alex Oliva 为 glibc 进行的 AC-safe 文档审查时遇到了这个问题。

我认为 GNU C 库实现(基于 nptl)没有被破坏。虽然确实围绕阻塞系统调用(必须是取消点)启用了异步取消,但这种行为仍然应该是一致的。

同样,在启用异步取消后获取的信号将导致信号处理程序在启用异步取消的情况下运行。在那个处理程序中做任何不是异步取消安全的事情也是很危险的。

同样,如果另一个线程调用 pthread_cancel 并将信号运行线程作为目标,则这种取消将立即生效。这仍然符合“函数返回之前”的 POSIX 措辞(在这种情况下 read 没有返回并且目标线程在信号处理程序中)。

信号的问题在于它会导致线程同时处于两个状态,一个是永久处于取消点,一个是执行指令。如果取消请求到达,我认为立即对其采取行动是符合要求的。尽管奥斯汀集团可能会澄清。

glibc 实现的问题在于它需要所有信号处理程序,由要取消的线程执行。只调用异步取消安全函数。这是一个不明显的要求,并非源于标准,但不会使其不符合要求。

关于解决信号处理程序脆弱性的潜在解决方案:

  • 不要为阻塞系统调用启用异步取消,而是在取消实现中启用一个新的 IN_SYSCALL 位。

  • 当调用 pthread_cancel 并且目标线程设置了 IN_SYSCALL 时,然后像通常为异步取消所做的那样向线程发送 SIGCANCEL,但 SIGCANCEL 处理程序什么也不做(除了中断系统调用的副作用)。

  • 系统调用的包装器将查找已发送的取消,并在包装​​器返回之前取消线程。

虽然在堆栈溢出上发布此内容很有趣,但我不知道还有其他人会阅读此内容并可以详细回答您的问题。

我认为任何进一步的讨论都应该在 Austin Group 邮件列表上作为 POSIX 标准讨论的一部分进行,或者应该在 libc-alpha 上作为 glibc 实现讨论的一部分进行。

于 2014-01-09T22:54:12.950 回答
0

我认为您正在寻找的是两件事的结合:

一些系统调用可能会被信号中断,从而导致EINTR错误返回。这是正常的行为,但我一直不清楚如果,例如,如果您处于 - 的中间,会发生read什么 - 没有从流中读取?也许有人可以对此发表评论以帮助澄清。

不应中断的系统调用,如您担心的那些,应包含在对sigprocmask(或pthread_sigmask线程)的调用中,以防止它们被中断。重新启用信号后,任何在被阻止时收到的信号都将被传递。但是,与中断一样,如果阻塞时间过长,您可能会由于覆盖而错过一些(多次接收相同的信号计为一个未决信号)。

于 2011-03-23T19:25:28.383 回答