代码之家  ›  专栏  ›  技术社区  ›  jemfinch

为什么这不是qmail中的bug?

  •  7
  • jemfinch  · 技术社区  · 15 年前

    我在看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的返回值,所以我阅读了close(2)的手册页,看起来 可以 失败了 EINTR ,在这种情况下,适当的行为似乎是使用相同的参数再次调用close。

    由于这段代码是由在c和unix方面比我经验丰富得多的人编写的,而且在qmail中已经保持了十多年的不变,所以我认为一定是缺少了一些细微的差别才使这段代码正确。有人能向我解释一下这种细微差别吗?

    4 回复  |  直到 14 年前
        1
  •  1
  •   mark4o    15 年前

    当文件描述符被复制时,如 fd_copy dup2 函数,您将得到多个引用同一事物的文件描述符(即 struct file 在内核中)。关闭其中之一只会减少其引用计数。对基础对象不执行任何操作,除非它是 最后的 关闭。因此,诸如 EINTR EIO 不可能。

        2
  •  2
  •   msw    15 年前

    我有两个答案:

    1. 他试图说明分解公共代码的要点,而且通常这样的例子会忽略错误检查,以确保简洁明了。
    2. close(2)可以返回einter,但实际上是这样做的,如果是这样,您会合理地做什么?重试一次?重试直到成功?如果你得到eio怎么办?这可能意味着几乎任何事情,所以你真的没有合理的追索权,除了登录和继续。如果你在一个eio之后重试,你可能会得到ebadf,然后呢?假设描述符已关闭并继续?

    每个系统调用都可以返回eintr,特别是一个阻止read(2)等待慢人的系统调用。这是一个更可能的场景,一个好的“从终端获取输入”例程确实会检查这一点。这也意味着写入(2)可能会失败,即使在写入日志文件时也是如此。您是否尝试记录记录器生成的错误,还是应该放弃?

        3
  •  0
  •   Steve Jessop    15 年前

    另一种可能性是,他的函数只在一个应用程序(或其中的一部分)中使用,该应用程序已经做了一些事情来确保调用不会被信号中断。如果你不打算对信号做任何重要的事情,那么你就不必对它们做出响应,把它们全部屏蔽掉可能是有意义的,而不是把每个阻塞的系统调用都包在一个eintr重试中。当然,除了那些会杀了你的,所以sigkill,如果你通过退出来处理它,通常是sigpipe,还有sigsegv和类似的致命错误,这些错误在任何情况下都不会传递到正确的用户空间应用程序。

    不管怎样,如果他所说的只是安全问题,那么很可能他不必再试了 close . 如果关闭失败,他将无法重试,这将是一个永久性的失败。因此,对于他的程序的正确性来说 关闭 成功了。很可能他的程序的正确性不需要 关闭 也可以在Eintr上重试。

    通常,您希望您的程序尽最大努力获得成功,这意味着在eIntr上重试。但这与安全无关。如果您的程序设计为某个函数由于任何原因失败不是安全缺陷,那么特别是 发生 失败不是一个缺点,而不是因为一个永久的原因。众所周知,DJB相当固执己见,所以如果他证明了他不这么做的原因,我一点也不惊讶。 需要 重试,因此不麻烦,即使这样做可以让他的程序在某些情况下成功地刷新句柄,在某些情况下,句柄当前可能会失败(例如 kill 在关键时刻由用户决定)。

    编辑:在某些情况下,在eIntr上重试本身可能是一个安全缺陷。它为这段代码引入了一种新的行为:它可以无限期地循环以响应信号泛滥,在此之前它会尝试 关闭 然后回来。我不确定这会不会导致任何问题(毕竟, 关闭 它自己也不能保证它会很快回来)。但如果在一次尝试后放弃,确实使代码更容易分析,那么这可能是一个明智的举动。或不。

    您可能认为重试可以防止DoS缺陷,即信号导致虚假故障。但重试允许另一个(更困难的)DOS缺陷,即信号泛滥导致无限期暂停。以二进制形式“这个应用程序可以关闭吗?”,这是djb在编写qmail和djbdns时感兴趣的绝对安全问题,没有区别。如果某件事可以发生一次,那么通常这意味着它可以发生很多次。

        4
  •  0
  •   R.. GitHub STOP HELPING ICE    14 年前

    只有破碎的孤岛才会回来 EINTR 没有你明确的要求。理智的语义学 signal() 启用可重新启动的系统调用(“bsd样式”)。在具有sysv语义(中断信号)的系统上构建程序时,应始终替换对 信号() 打电话给 bsd_signal() ,你可以用 sigaction() 如果它不存在。

    值得注意的是 系统将返回 埃因特 收到信号时,除非您安装了信号处理程序。如果保留默认操作,或者将信号设置为“无操作”,则系统调用不可能中断。