【发布时间】:2011-07-14 09:16:56
【问题描述】:
POSIX 说 “系统总是零填充对象末尾的任何部分页面。此外,系统永远不会写出对象最后一页超出其末尾的任何修改部分。”,Linux 和 FreeBSD 文档的手册页中都有类似的措辞。
这表明尽管读取最后的尾随字节(因为它们超出映射范围)严格来说并不合法,但它仍然是经过良好定义和设计的,因此它可能发生而不会崩溃。即使是写到那个区域也是一种明确的定义。
另一方面,Windows 文档没有说明小于块大小范围内的尾随字节,并且确实警告说创建大于文件的映射会增加文件大小,不一定 将数据归零。
我倾向于相信这是错误的信息或历史性的(可能可以追溯到 Win95?)。 SetFileValidData 需要非标准用户权限,因为这可能会使以前删除的文件中的数据可见的安全问题。如果 Windows 内核开发人员允许任何人通过映射任何随机文件来轻松绕过这一点,他们将不得不非常愚蠢。
我对 Windows XP 的观察是,任何新页面显然都是从零池中提取的,对于空页面写回,文件要么默默地变得稀疏,要么写回以非常非常智能的方式完成(任何时候都没有明显的延迟时间,甚至在千兆字节范围内)。
那么问题是什么?
我需要计算(可能是数千个)文件的哈希值,以检测被修改的文件子集。可以假设 SHA-256 作为算法,但实际算法并不重要。
这当然不是什么大挑战,但就像每个软件一样,它应该立即运行并且不使用内存,等等。通常的现实期望,你懂的:-)
计算这种散列的正常方法是检查消息是否具有与散列函数的块大小一致的大小(例如 64 字节),如果不是,则将最后一个不完整的块填零。此外,哈希可能有对齐要求。
这通常意味着您必须制作消息的完整副本,或者编写一些特殊代码来散列除一个块外的所有块加上最后一个块的零填充副本。或类似的东西。哈希算法也经常默默地代表自己做这种事情。无论如何,这涉及到移动大量数据,并且比人们希望的复杂性要高。
现在有直接对内存映射文件进行哈希处理并依赖于文件映射必然依赖于内存页面这一事实的诱惑。因此,起始地址和物理映射长度都或多或少地保证为 4kB 的倍数(在某些系统上为 64kB)。这当然意味着它们也是 64、128 或散列可能具有的任何其他块大小的倍数。
并且出于安全原因,实际上没有操作系统可以为您提供包含陈旧数据的页面。
这意味着您可以天真地散列整个文件,而不必担心对齐、填充或其他任何事情,并避免复制数据。它可能读取了超出映射范围末尾的几个字节,但它必然仍然在同一页面内。
我当然知道这是技术上非法的。读取映射范围之外的最后一个字节有点类似于说malloc(5) 总是返回一个 8 字节块,因此使用额外的 3 个字节是安全的。
但是,除了显而易见的事情之外,我认为这将“正常工作”的假设是否合理,或者是否存在一些我在任何主要平台上都看不到的严重问题?
我对理论上或历史上的操作系统并不太感兴趣,但我希望保持某种程度的便携性。也就是说,我想确保它在您可能在台式计算机或“典型托管服务器”(因此,主要是 Windows、Linux、BSD、OSX)上遇到的任何东西上都能可靠地工作。
如果存在一个 1985 年的操作系统,它将最后一页标记为不可读,并在其故障处理程序中强制执行严格的字节范围,我可以接受。你不能(也不应该)让每个人都开心。
【问题讨论】: