使用fseek和ftell来确定文件的大小有漏洞吗?

我已经阅读过post,展示了如何使用fseek和ftell来确定文件的大小。

FILE *fp; long file_size; char *buffer; fp = fopen("foo.bin", "r"); if (NULL == fp) { /* Handle Error */ } if (fseek(fp, 0 , SEEK_END) != 0) { /* Handle Error */ } file_size = ftell(fp); buffer = (char*)malloc(file_size); if (NULL == buffer){ /* handle error */ } 

我即将使用这种技术但后来遇到了描述潜在漏洞的链接 。

该链接建议使用fstat。 任何人都可以评论这个吗?

该链接是CERT的许多无意义的C编码建议之一。 他们的理由是基于C标准允许实现的自由,但POSIX不允许这样做,因此在你有fstat替代的所有情况下都fstat

POSIX要求:

  1. fopen"b"修饰符没有效果,即文本和二进制模式的行为相同。 这意味着他们对在文本文件上调用UB的担忧是无稽之谈。

  2. 这些文件具有由写操作和截断操作设置的字节分辨率大小。 这意味着他们对文件末尾的空字节随机数的关注是无稽之谈。

可悲的是,他们发布了所有这些废话,很难知道哪些CERT出版物需要认真对待。 这是一种耻辱,因为很多都是严肃的。

如果你的目标是找到一个文件的大小,你肯定应该使用fstat()或它的朋友。 这是一个更直接和更具表现力的方法 – 你实际上要求系统告诉你文件的统计数据,而不是更环绕的fseek / ftell方法。

奖金提示:如果您只想知道文件是否可用,请使用access()而不是打开文件甚至是对其进行统计。 这是一个更简单的操作,许多程序员都不知道。

使用fstat的原因是fstat是POSIX,但fopenftellfseek是C标准的一部分。

可能有一个系统实现C标准而不是POSIX。 在这样的系统上, fstat根本不起作用。

我倾向于同意他们的基本结论,即你通常不应该直接在代码的主流中使用fseek / ftell代码 – 但你可能也不应该使用fstat 。 如果你想要一个文件的大小,你的大多数代码都应该使用像filesize这样清晰,直接的名称。

现在,最好使用fstat如果可用)和(例如)Windows上的FindFirstFilefstat通常不可用的最明显的平台)来实现它。

故事的另一方面是fseek对二进制文件的许多(大多数?)限制实际上源于CP / M,它没有明确地将文件的大小存储在任何地方。 文本文件的末尾由控件-Z发出信号。 但是,对于二进制文件,您真正知道的是用于存储文件的扇区。 在最后一个扇区中,您有一些经常(但不总是)零填充的未使用数据。 不幸的是,可能存在重要的零和/或非零的非重要值。

如果整个C标准是在批准之前编写的(例如,如果它是在1988年开始并在1989年完成的话),他们可能完全忽略了CP / M. 然而,无论好坏,他们开始在1982年左右开始研究C标准,当时CP / M的使用范围仍然广泛,不容忽视。 当CP / M消失时,许多决定已经做出,我怀疑是否有人想重新审视它们。

然而,对于今天的大多数人来说,没有任何意义 – 如果没有大量工作,大多数代码都不会移植到CP / M; 这是一个相对较小的问题需要处理。 对于代码和数据来说,使现代程序仅在48K(左右)的内存中运行是一个更严重的问题(大容量存储最大为兆字节左右将是另一个严重的问题)。

CERT确实有一个好处:你可能不应该(通常这样做)找到文件的大小,分配那么多的空间,然后假设文件的内容适合那里。 尽管fseek / ftell会使用现代系统为您提供正确的大小,但是当您实际读取数据时,这些数据可能会过时,因此无论如何您都可能超出缓冲区。

根据C标准,§7.21.3 :

将文件位置指示器设置为文件结束,与fseek(file, 0, SEEK_END) ,具有二进制流的未定义行为(因为可能是尾随空字符)或具有不依赖于状态的编码的任何流确定在初始class次状态结束。

一个法律信函的人可能认为通过计算文件大小可以避免这个UB:

 fseek(file, -1, SEEK_END); size = ftell(file) + 1; 

但C标准也说明了这一点:

二进制流不需要有意义地支持具有SEEK_END值的fseek调用。

因此,关于fseek / SEEK_END,我们无法解决这个问题。 不过,我更喜欢fseek / ftell而不是特定于操作系统的API调用。