解析/proc/file 是否安全?

我想解析 /proc/net/tcp/,但它安全吗?

我应该如何打开和读取文件从 /proc/和不害怕,其他一些进程(或操作系统本身)将改变它在同一时间?

16449 次浏览

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

剪辑

更多信息请参阅 Linux 内核文档中的 proc 文档,第1.4章网络 我找不到信息是如何随着时间演变的。我以为它被冻住了,但是没有确切的答案。

编辑2

根据 斯科博士(不是 linux,但我确信所有的 * nix 风味都是这样的)

虽然进程状态和 因此/proc 的内容 文件可以从即时更改为 即时,a/proc 的单个读(2) 文件保证返回一个 “理智”的国家代表 读数是原子级的 进程状态的快照。 这种保证不适用于 应用于 a/proc 的连续读取 正在运行的进程的 除此之外,原子性是特定的 申请的输入/输出不获保证 As (地址空间)文件; 任何进程地址的内容 空间可能会同时被修改 该过程或任何其他过程的长期可持续发展计划 程序。

虽然 /proc中的文件在用户空间中显示为常规文件,但它们不是真正的文件,而是支持来自用户空间的标准文件操作的实体(openreadclose)。请注意,这与磁盘上有一个由内核更改的普通文件完全不同。

内核所做的就是使用类似于 sprintf的函数将其内部状态打印到自己的内存中,并且当您发出 read(2)系统调用时,该内存将被复制到用户空间中。

内核处理这些调用的方式与处理常规文件的方式完全不同,这可能意味着您将读取的数据的整个快照在 open(2)时就已经准备好了,而内核确保并发调用是一致的和原子的。我还没在别的地方读到过,但是不这样做真的没有意义。

我的建议是看一下在你的 Unix 风格中 proc 文件的实现。这实际上是一个不受标准控制的实现问题(输出的格式和内容也是如此)。

最简单的示例是在 Linux 中实现 uptime proc 文件。注意整个缓冲区是如何在提供给 single_open的回调函数中产生的。

Linux 内核中的 procfs API 提供了一个接口,以确保读取返回一致的数据。阅读 __proc_file_read中的评论。第一项)在大注释块中解释了这个界面。

也就是说,正确使用这个接口以确保返回的数据是一致的,当然取决于特定 proc 文件的实现。因此,回答你的问题: 不,内核并不保证在读取过程中 proc 文件的一致性,但是它为这些文件的实现提供了提供一致性的方法。

除了未知的 bug 之外,/proc中没有竞态条件会导致读取损坏的数据或新旧数据的混合。从这个意义上说,它是安全的。然而,仍然存在竞争条件,即从 /proc读取的大部分数据可能在生成时就已经过时,甚至在读取/处理时更是如此。例如,进程可以在任何时候死亡,一个新进程可以被分配相同的 pid; 在没有竞态条件的情况下,您可以使用的唯一进程 id 是您自己的子进程的 id。网络信息(打开端口等)和 /proc中的大部分信息也是如此。我认为,依赖于 /proc中的任何数据都是准确的,除了关于您自己的进程和可能的子进程的数据之外,这种做法是糟糕和危险的。当然,将 /proc中的其他信息呈现给用户/管理员以获得信息/日志记录等目的仍然是有用的。

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

例如,第934行的文件... linux-2.6.27.8-lpc32xx/net/ipv4/raw.c包含

    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
...

在函数 raw_sock_seq_show()中,它是 环境保护署署长处理函数层次结构的一部分。直到对 /proc/net/tcp文件发出 read()请求才生成文本,这是一种合理的机制,因为 环境保护署署长读取肯定比更新信息少得多。

有些驱动程序(比如我的驱动程序)使用单个 sprintf()实现 proc _ read 函数。核心驱动程序实现的额外复杂性是处理可能非常长的输出,这些输出在单次读取期间可能不适合中间的内核空间缓冲区。

我用一个使用64K 读取缓冲区的程序测试了它,但是它在我的系统中为 proc _ read 返回数据产生了一个3072字节的内核空间缓冲区。需要多个带有前进指针的调用才能返回更多的文本。当需要多个 i/o 时,我不知道如何正确地使返回的数据保持一致。当然,/proc/net/tcp中的每个条目都是自我一致的。并排的行有可能是不同时间的快照。

当您读取/proc 文件时,内核正在调用一个事先注册的函数作为该 proc 文件的“ read”函数。参见 fs/proc/generic.c 中的 __proc_file_read函数。

因此,proc read 的安全性只有在内核调用函数来满足读请求时才是安全的。如果该函数正确地锁定了它接触的所有数据,并在缓冲区中返回给您,那么使用该函数进行读取是完全安全的。由于 proc 文件(比如用于满足对/proc/net/tcp 的读请求的 proc 文件)已经存在了一段时间,并经过了仔细的审查,因此它们是您所能要求的最安全的文件。事实上,许多常见的 Linux 实用程序依赖于从 proc 文件系统读取和以不同的方式格式化输出。(我第一时间想到的是‘ ps’和‘ netstat’这两个词)。

像往常一样,你不必相信我的话,你可以看着源头来平息你的恐惧。下面来自 proc _ net _ tcp.txt 的文档告诉您/proc/net/tcp 的“ read”函数位于何处,因此您可以查看从 proc 文件读取时运行的实际代码,并自己验证是否存在锁定危险。

本文档描述了接口 /proc/net/tcp 和/proc/net/tcp6。
请注意,这些接口是 弃用以支持 tcp _ diag。 这些/proc 接口提供有关当前活动 TCP 的信息 连接,并由 Net/ipv4/tcp _ ipv4. c 中的 tcp4 _ seq _ show () 中的 tcp6 _ seq _ show () Net/ipv6/tcp _ ipv6.c.

一般来说,没有。所以这里的大部分答案都是错误的它 也许吧是安全的,这取决于你想要什么属性。但是,如果对 /proc中文件的一致性假设太多,那么很容易在代码中出现 bug。例如,请参见 这个错误来自于假设 /proc/mounts是一个一致的快照

例如:

  • /proc/uptime 完全原子化,正如有人在另一个答案中提到的那样——而是 从 Linux 2.6.30开始,它还不到两岁。因此,即使是这个微小的文件,在此之前也受到竞态条件的影响,并且仍然存在于大多数企业内核中。有关当前源,请参见 fs/proc/uptime.c使它成为原子的承诺。在2.6.30之前的内核上,你可以 open文件,read一点点,然后如果你以后再来 read,你得到的部分将与第一部分不一致。(我只是演示了这一点——你自己也可以尝试一下。)

  • 因此,如果您同时将整个文件 read,您将获得系统上挂载点的一个一致的快照。但是,如果使用多个 read系统调用——如果文件很大,那么如果使用普通的 I/O 库并且不特别注意这个问题,就会发生这种情况——您将受到竞争条件的影响。你不仅不会得到一致的快照,而且在你开始之前就已经存在并且从未停止存在的装载点可能会在你所看到的东西中丢失。要查看它对于一个 read()是原子的,请查看 read1并查看它获取一个保护挂载点列表的信号量,这个信号量一直保持到 m_stop(),当 read()完成时调用 m_stop()。要查看可能出现的问题,请参阅 read2(与上面我链接的同一个) ,否则高质量的软件,愉快地阅读 /proc/mounts

  • /proc/net/tcp ,也就是你实际问到的那个,甚至比这个更不一致。是 只能在表的每一行中使用原子。要查看这一点,请查看下面同一文件中的 ABC1在 net/ipv4/tcp_ipv4.cestablished_get_next(),并依次查看它们在每个条目上取出的锁。我手边没有 repro 代码来演示行与行之间缺乏一致性,但是没有任何锁(或其他东西)可以使其保持一致。如果您仔细想想,这是有意义的——网络通常是系统中超级繁忙的部分,因此不值得在这个诊断工具中提供一致的视图。

在每一行中保持 /proc/net/tcp原子性的另一部分是 seq_read()中的缓冲区,您可以读取 fs/seq_file.c。这样可以确保在 read()占据一行的一部分时,整行的文本保存在缓冲区中,以便下一个 read()在启动新行之前获得该行的其余部分。在 /proc/mounts中使用了相同的机制,即使您执行多个 read()调用,也可以使每一行保持原子状态,这也是较新内核中的 /proc/uptime使用的保持原子状态的机制。这种机制会对整个文件进行 没有缓冲,因为内核对内存使用非常谨慎。

/proc中的大多数文件至少和 /proc/net/tcp一样一致,无论它们提供什么信息,每一行都是一个条目的一致图片,因为它们中的大多数使用相同的 seq_file抽象。但是,正如 /proc/uptime示例所示,直到2009年,一些文件仍然在迁移以使用 seq_file; 我敢打赌,仍然有一些文件使用较老的机制,甚至没有那种级别的原子性。这些警告很少被记录下来。对于给定的文件,唯一的保证是读取源代码。

对于 /proc/net/tcp,您可以毫无畏惧地阅读和解析每一行。但是,如果您试图一次从多行中得出任何结论——请注意,其他进程和内核 在您阅读它时会更改它,而且您可能正在创建一个 bug。