为什么这不是qmail中的错误?

时间:2010-04-03 06:11:08

标签: c unix qmail

我正在阅读DJB的"Some thoughts on security after ten years of Qmail 1.0",他列出了这个函数来移动文件描述符:

int fd_move(to,from)
int to;
int from;
{
  if (to == from) return 0;
  if (fd_copy(to,from) == -1) return -1;
  close(from);
  return 0;
}

我突然发现这段代码没有检查close的返回值,所以我读了man页面close(2),似乎可以失败EINTR ,在这种情况下,适当的行为似乎是使用相同的参数再次调用close。

由于此代码是由在C和UNIX中经验丰富的人编写的,并且在qmail中已经保持了十多年不变,我认为必须有一些细微差别,我错过了这个代码正确。任何人都可以向我解释这种细微差别吗?

4 个答案:

答案 0 :(得分:2)

我有两个答案:

  1. 他试图提出一个关于分解公共代码的观点,并且为了简洁和清晰,这些例子通常会省略错误检查。
  2. close(2)可能会返回EINTER,但在实践中会这样做,如果是这样,你会合理地做什么?重试一次?重试直到成功?如果你得到EIO怎么办?这几乎可能意味着什么,所以除了记录和继续前进之外,你真的没有合理的追索权。如果您在EIO之后重试,您可能会获得EBADF,那么什么?假设描述符已关闭并继续前进?
  3. 每个系统调用都可以返回EINTR,特别是一个像read(2)一样阻塞等待慢速人类的系统。这是一种更可能的情况,一个好的“从终端获取输入”例程确实会检查这一点。这也意味着即使在编写日志文件时write(2)也会失败。您是否尝试记录记录器生成的错误或者您应该放弃?

答案 1 :(得分:1)

当文件描述符被复制时,就像它在fd_copydup2函数中一样,最终会有多个文件描述符引用相同的东西(即相同的{内核中的{1}}。关闭其中一个将简单地减少其引用计数。除非 last 关闭,否则不会对基础对象执行任何操作。因此,struct fileEINTR等条件无法实现。

答案 2 :(得分:0)

另一种可能性是他的功能仅用于应用程序(或其中一部分),它已经做了一些事情来确保呼叫不会被信号中断。如果你不打算对信号做任何重要的事情,那么你就不必对它们做出响应,并且将它们全部掩盖它们可能是有意义的,而不是在EINTR重试中包装每个阻塞系统调用。除了会杀死你的那些,所以SIGKILL和SIGPIPE如果你通过退出来处理它,以及SIGSEGV和类似的致命错误,无论如何都不会被传递到正确的用户空间应用程序。

无论如何,如果他所谈论的只是安全性,那么他很可能不必重试close。如果关闭EIO失败,那么他将无法重试,这将是一个永久性的失败。因此,close成功的程序的正确性没有必要。很可能,他的程序的正确性也不一定要在EINTR上重试close

通常您希望您的程序尽最大努力取得成功,这意味着重试EINTR。但这是安全方面的一个单独问题。如果你的程序被设计成某些功能因任何原因而失败并不是安全漏洞,那么特别是它发生以使EINTR失败,而不是永久性的原因,不是一个缺陷。众所周知,DJB是相当自以为是的,所以如果他已经证明了为什么他不需要重试,我就不会感到惊讶,因此即使这样做也不会打扰允许他的程序在某些可能当前失败的情况下成功刷新句柄(比如在关键时刻用户kill显式发送无害信号)。

编辑:在我看来,在某些条件下重试EINTR可能本身就是一个安全漏洞。它为该段代码引入了一种新行为:它可以无限循环以响应信号泛滥,之前它将尝试close然后返回。我不确定这会导致qmail出现任何问题(毕竟,close本身并不能保证它会多久返回)。但是,如果在一次尝试之后放弃确实使代码更容易分析,那么它可能是明智的举动。或者不是。

您可能认为重试会阻止DoS漏洞,其中信号会导致虚假故障。但是重试允许另一个(更难的)DoS缺陷,其中信号泛滥导致无限期失速。在二进制“可以这个应用程序是DoSed吗?”这是DJB在编写qmail和djbdns时感兴趣的绝对安全问题,它没有任何区别。如果某事可能发生一次,那么通常这意味着它可能会发生多次。

答案 3 :(得分:0)