解析/proc/文件是安全的吗?

162

我想解析/proc/net/tcp/,但是这样做安全吗?

如何打开和读取/proc/中的文件,而不用担心其他进程(或操作系统本身)会在同一时间内更改它?


31
那是一个非常好的问题。我只希望我有答案,但我期待着找到答案,因为我以前经常这样做过。注意:英文中“damn”有时用于强调语气,翻译时可以根据具体情境使用相应的词汇进行转换。 - paxdiablo
1
我非常确定仅仅阅读它就会给你一个连接列表,以及每个连接的 UID,就像当你打开它时一样。然而,我找不到相关文档,所以现在只能将其作为注释。 - Tim Post
3
简单回答显然是肯定的,因为它不是一个文件,读取它应该总是安全的。每次读取时答案可能会不一致,但是它仍然是安全的。 - Rory Alsop
这就是为什么你应该使用sysctl。(它也会减少系统调用) - Good Person
@GoodPerson - 例如,sysctl如何帮助我解析/proc/net/tcp/文件呢? - Kiril Kirov
@KirilKirov - 你应该查找你想要的sysctl,而不是使用/proc。 - Good Person
7个回答

123

一般而言,不行。 (所以这里的大多数答案都是错的。) 它可能是安全的,这取决于您想要的属性。但是如果您过于假定 /proc 中的文件一致性,很容易在代码中出现 bug。例如,参见这个因为假定 /proc/mounts 是一致快照而导致的 bug

例如:

  • /proc/uptime完全原子的,正如其他回答中提到的那样——但仅自从 Linux 2.6.30 以来,也就是不到两年前。因此即使是这个微小的、琐碎的文件,在此之前都存在竞争条件,而且在大多数企业内核中仍然存在。请参见当前源码的fs/proc/uptime.c,或使其原子化的提交。在 2.6.30 之前的内核上,您可以 open 文件,read 一点内容,然后如果稍后再次 read,则获取的片段将与第一个片段不一致。(我刚刚演示了这一点——您也可以自行尝试。)

  • /proc/mounts 是在单个 read 系统调用内是原子性的。因此,如果您一次读取整个文件,您将获得系统上挂载点的单个一致快照。但是,如果您使用多个 read 系统调用--如果文件很大,则使用普通I/O库并且不特别注意此问题,这正是会发生的--您将受到竞争条件的影响。您不仅不会获得一致的快照,而且在您开始之前存在且从未停止存在的挂载点可能会在您看到的内容中消失。要查看它对于一个 read() 是原子的,请查看fs/namespace.c中的m_start()并查看它获取了保护挂载点列表的信号量,直到m_stop()被调用为止,后者在完成read()时被调用。要了解可能出现的问题,请参见去年的这个错误(与我上面链接的相同),这是一种高质量软件,它轻率地读取了/proc/mounts

  • /proc/net/tcp是你实际询问的文件,它与其他文件相比甚至更不一致。 它仅在每个表格行内是原子的。 要了解这一点,请查看net / ipv4 / tcp_ipv4.c 中的 listening_get_next()和下面的established_get_next(),并查看它们在逐个条目上获取的锁。 我没有方便的复制代码来演示从一行到另一行的不一致性,但是那里没有锁定(或其他任何东西)可以使其一致。 如果您考虑一下,这是有道理的-网络通常是系统中繁忙的部分,因此在此诊断工具中呈现一致的视图并不值得。

  • 使/proc/net/tcp在每个行内保持原子的另一个要素是seq_read()中的缓冲区,您可以通过fs / seq_file.c了解其中内容。 这确保了一旦读取一个行的一部分,整个行的文本都会保存在缓冲区中,以便下一个read()将在开始新行之前获取该行的其余部分。 同样的机制也在/proc/mounts中用于使每行原子,即使您进行多个read()调用,这也是较新内核中/proc/uptime使用的机制。 该机制不会缓存整个文件,因为内核对内存使用非常谨慎。

    /proc目录下的大多数文件至少与/proc/net/tcp一样一致,每行都是提供信息条目的一张图片,因为它们大多使用相同的seq_file抽象。然而,正如/proc/uptime例子所示,一些文件直到2009年仍在迁移使用seq_file,我敢打赌仍有一些使用较旧机制且连那种程度的原子性都没有。这些注意事项很少有文档记录。对于给定的文件,你唯一的保证就是阅读源代码。

    /proc/net/tcp的情况下,您可以阅读并解析每个行而不担心。但是,如果您试图从多个行中得出任何结论,请注意,其他进程和内核正在您阅读时更改它,您可能会创建一个bug。


    2
    readdir原子性怎么样?比如读取/proc/self/fd?这样安全吗? - socketpair
    并不是回答问题,但是关于如何检查正常运行时间,您可以使用 clock_gettime(2) 和 *CLOCK_MONOTONIC*(尽管我可能不知道这里是否有技术细节,但我个人只看到它自启动以来的时间)。对于Linux,您还可以选择使用 *sysinfo(2)*。 - Pryftan
    由于内部seq_file.c缓冲区默认为一页大小,并且一旦填满,读取就会结束,因此无法使用单个巨大的读取来一致地读取/proc/mounts。因此,可以一致地读取的/proc/mounts最大块是尽可能多的条目适合4096字节,除了一些非常不寻常的情况(第一行超过4096字节)。 - OhJeez
    https://linux.die.net/man/2/openat能解决竞争条件问题吗? - Vencat
    strace -e verbose=all cat /proc/net/tcp - Vencat

    45
    尽管/proc目录下的文件在用户空间中看起来像普通文件,但它们实际上不是文件,而是支持用户空间标准文件操作(openreadclose)的实体。请注意,这与内核更改磁盘上的普通文件非常不同。
    内核所做的只是使用类似于sprintf函数将其内部状态打印到自己的内存中,每当您发出read(2)系统调用时,该内存就会被复制到用户空间。
    内核处理这些调用的方式与普通文件完全不同,这意味着您将读取的整个数据快照可能已经在您open(2)它的时候准备好了,而内核确保并发调用是一致和原子的。我没有在任何地方读到过这一点,但否则就没有意义。
    我的建议是查看您特定Unix版本中proc文件的实现。这真的是一个实现问题(输出的格式和内容也是如此),没有标准规定。
    最简单的例子是Linux中uptime proc文件的实现。请注意,整个缓冲区是在提供给single_open的回调函数中生成的。

    3
    我会尽力进行翻译,请问需要翻译的内容是:“@Ignacio: 我只是指出这个方向给原帖作者,因为我对他的印象是他认为proc文件是内核可写的普通文件。” - Blagovest Buyukliev
    4
    你提供的建议查看特定文件的实现是好的。不幸的是,猜测它全部在 open() 处快照是错误的,尤其是对于 OP 关心的 /proc/net/tcp 文件。这是有道理的,如果你考虑提供这些语义的成本--你必须做一些像锁定记录所有这些 TCP 连接的内部数据结构的事情,即使你只持有它足够长的时间来扫描并将数据格式化到缓冲区,对于一个繁忙的系统来说也是灾难性的。请参阅我的回答,了解实际发生的情况的详细信息。 - Greg Price

    17

    /proc是一个虚拟文件系统:实际上,它只是提供了一个方便查看内核内部的视图。它肯定是安全的(这就是为什么它在这里),但从长远来看可能存在风险,因为这些虚拟文件的内部可能随着新版本的内核而发生变化。

    编辑

    有关更多信息,请参见Linux内核文档中的proc文档,第1.4章网络。我无法找到信息如何随时间演变。我认为它在开放时被冻结,但不能得到明确的答案。

    编辑2

    根据Sco文档(不是linux,但我很确定所有*nix都是如此行为)

     

    尽管进程状态和因此/ proc  文件的内容可以从瞬间到  瞬间更改,但单个read(2)/ proc  文件保证返回  进程的``健全''表示,  也就是说,阅读将是原子性的  该进程的状态快照。  不能保证对连续读取的/ proc  文件适用任何这样的保证,对于正在运行的进程。  此外,特别不保证原子性  对as(地址空间)文件使用的任何I / O;该  任何进程的地址内容  空间可能会被同时修改  由该进程或任何其他进程  在系统中。


    3
    “我认为” 得到一个明确的答案会很好 :) - static_rtti
    鉴于内核中/proc的实现,这同样适用于Linux。如果您在单个读取调用中读取procfs文件,则它是一致的 - 当然,假设您读取的proc文件已经在内核端正确实现。 - Erik
    8
    我认为找不到比SCO更糟糕的信息来源了,尝试将proc视为在不同内核之间有类似行为的东西(甚至认为它存在 - 在Unix系统中可能没有)会给你带来痛苦。 - Nicholas Knight
    1
    @Nicholas:嗯,在内核文档中找不到明确的答案,如果您知道,请随意指出。 - Bruce
    2
    有趣的是SCO文档这样说。不幸的是,在Linux中并不总是如此,特别是对于/proc/net/tcp来说并不是这样,这是OP最关心的问题。相反,只有输出中的每个单独行是原子的。有关详细信息,请参见我的答案。 - Greg Price
    不是Linux,但我非常确定所有*nix的变种都会表现出这样的行为。无论此是否直接相关,我都不知道,但SCO并不是一个体面的组织(即不值得信任等)。 https://en.wikipedia.org/wiki/SCO/Linux_controversies 浮现在脑海中。但事实仍然是他们似乎有许多错误的信念和想法... - Pryftan

    15

    Linux内核中的procfs API提供了一个接口,用于确保读取返回一致的数据。请参阅__proc_file_read中的注释。大块注释中的第1项解释了该接口。

    话虽如此,当然具体的proc文件的实现需要正确使用这个接口来确保返回的数据是一致的。因此,回答你的问题:内核不保证在读取期间proc文件的一致性,但它提供了实现这些文件以提供一致性的手段。


    4
    很遗憾,在“/proc”目录中许多文件并未提供一致性。详情请参考我的回答。 - Greg Price
    3
    此外,__proc_file_read() 已被 seq_file 取代,详见长注释上方林纳斯声音有些恼怒的评论。 - Greg Price

    6

    我目前在嵌入式ARM目标上进行驱动程序开发,因此手头有Linux 2.6.27.8的源代码。

    例如,文件linux-2.6.27.8-lpc32xx/net/ipv4/raw.c中的第934行包含以下内容:

        seq_printf(seq, "%4d: %08X:%04X %08X:%04X"
                " %02X %08X:%08X %02X:%08lX %08X %5d %8d %lu %d %p %d\n",
                i, src, srcp, dest, destp, sp->sk_state,
                atomic_read(&sp->sk_wmem_alloc),
                atomic_read(&sp->sk_rmem_alloc),
                0, 0L, 0, sock_i_uid(sp), 0, sock_i_ino(sp),
                atomic_read(&sp->sk_refcnt), sp, atomic_read(&sp->sk_drops));
    

    输出

    [wally@zenetfedora ~]$ cat /proc/net/tcp
      sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                                                     
       0: 017AA8C0:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 15160 1 f552de00 299
       1: 00000000:C775 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 13237 1 f552ca00 299
    ...
    

    在处理procfs的函数层次结构中,raw_sock_seq_show()函数是其中之一。直到对/proc/net/tcp文件发出read()请求时才生成文本,这是一个合理的机制,因为procfs读取肯定比更新信息要少得多。
    一些驱动程序(例如我的)使用单个sprintf()实现proc_read函数。核心驱动程序实现中的额外复杂性在于处理可能非常长的输出,在单个读取期间可能无法适应中间内核空间缓冲区。
    我使用64K读取缓冲区的程序进行了测试,但在我的系统中,它会导致3072字节的内核空间缓冲区返回数据给proc_read。需要使用前进指针进行多次调用以获取超过该数量的文本。我不知道在需要多个i/o时如何使返回的数据一致。当然,/proc/net/tcp中的每个条目都是自洽的。有一些可能性是并排的行在不同的时间快照。

    非常抱歉,我不是很明白。那么,您的意思是,如果我使用 ifstream,它会不安全,但如果我使用 read,它就会安全?还是说 ifstream 内部使用了 read?您有什么建议呢? - Kiril Kirov
    @Kiril:抱歉让你感到困惑。这是关于如何格式化/proc/net/tcp数据的解释,与任何人如何读取它完全无关。 - wallyk
    1
    没错!而且你猜的没错,不同的行(在“/proc/net/tcp”中)并不来自同一个快照。请参阅我的答案以了解一些解释。 - Greg Price

    3
    除了未知的漏洞外,在/proc中没有竞态条件会导致读取损坏的数据或新旧数据混合。从这个意义上说,它是安全的。然而,仍存在竞态条件,即你从/proc读取的大部分数据在生成后就已经过时,并且在你读取/处理它之前更加过时。例如,进程可以随时死亡,新进程可以被分配相同的pid;你只能使用自己的子进程的进程ID而没有竞争条件。对于网络信息(开放端口等)和实际上/proc中的大多数信息也是如此。我认为依赖/proc中的任何数据准确无误都是不好的和危险的做法,除了关于你自己的进程和潜在的子进程的数据。当然,将/proc中的其他信息呈现给用户/管理员以进行信息记录/日志记录等目的可能仍然很有用。

    我这样做是为了获取和使用一些信息用于我的进程(使用 getpid() 获取我的PID),所以必须保证安全。 - Kiril Kirov
    1
    是的,我认为那完全安全。 - R.. GitHub STOP HELPING ICE
    我不同意子进程比其他进程更有规矩的说法。就“/proc”接口而言,它们都有相同的优点和缺点。无论如何,OP询问的是与设备驱动程序相关的信息,而不是进程。 - wallyk
    1
    如果pid N是您的子进程,则可以确保pid N仍然指向同一(可能已终止)进程,直到您对其调用wait系列函数。这可确保不存在竞争。 - R.. GitHub STOP HELPING ICE
    这些-1的洪水是怎么回事?为什么没有解释? - R.. GitHub STOP HELPING ICE
    @R.. 简单明了。人们往往在这方面表现得很懦弱;这种情况在全世界都存在。这是批评而不是建设性的批评。这很悲哀,但这就是现实,无论你的回答是什么 - 或者任何回答是完全错误的、大部分错误的、完全正确的或其他任何东西 - 我都感同身受。这非常令人沮丧,它不允许改进答案;不仅如此,你还可以学到更多。虽然我只有不到7岁,但我想在这里给你支持。 - Pryftan

    2
    当你读取/proc文件时,内核会调用事先注册为该proc文件的“读”函数的函数。请参见fs/proc/generic.c中的__proc_file_read函数。
    因此,proc读取的安全性只有内核调用以满足读取请求的函数的安全性。如果该函数正确锁定了它所涉及的所有数据并在缓冲区中返回给您,则使用该函数读取是完全安全的。由于像用于满足对/proc/net/tcp的读取请求的文件这样的proc文件已经存在一段时间并经过仔细审核,因此它们几乎是您可以要求的最安全的。实际上,许多常见的Linux实用程序依赖于从proc文件系统读取并以不同的方式格式化输出。(我想起来的是'ps'和'netstat')
    与往常一样,您不必相信我的话;您可以查看源代码以消除您的恐惧。以下来自proc_net_tcp.txt的文档告诉您/proc/net/tcp的“读”函数位于何处,因此您可以查看实际运行的代码以验证自己是否存在锁定隐患。

    本文档描述了接口/proc/net/tcp和/proc/net/tcp6。
    请注意,这些接口已被tcp_diag取代。这些/proc接口提供有关当前活动的TCP连接的信息,并由net/ipv4/tcp_ipv4.c中的tcp4_seq_show()和net/ipv6/tcp_ipv6.c中的tcp6_seq_show()分别实现。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接