【问题标题】:The last mapped page最后映射的页面
【发布时间】: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 年的操作系统,它将最后一页标记为不可读,并在其故障处理程序中强制执行严格的字节范围,我可以接受。你不能(也不应该)让每个人都开心。

【问题讨论】:

    标签: mmap memory-mapped-files


    【解决方案1】:

    计算这种散列的正常方法是检查消息是否具有与散列函数的块大小一致的大小(例如 64 字节),如果不是,则将最后一个不完整的块填零。

    不是真的。这样你就无法找出最后一个块的长度(那里是零还是来自填充)。填充的工作方式略有不同:在一个模式中,您总是附加一个 1,然后是 0s,直到块结束。

    如果您的数据在块边界上结束,则意味着需要另一个块。这个额外的块可能会落在额外的页面中。所以我认为它不会像你描述的那样起作用。

    它可能会读取超出映射范围末尾的几个字节,但它必然仍然在同一页面内。

    我认为这应该适用于英特尔/AMD,因为没有人可以反对它。 i386+ CPU 有段和页。段可以在任何字节边界上结束,但 AFAIK 当前没有操作系统使用它们。因此,只要您留在您的页面中,它就是您的全部。

    所以我认为它可以这样工作:

    • 如果最后一个块没有完整大小,请在适当位置进行填充
    • 否则,在准备好的常量块上运行最后一轮,例如1000000000000000

    【讨论】:

    • 添加单个 1 后跟零填充与使用零填充相比没有优势。顺便说一下,这是一个特定哈希的约定,但仅此而已。它也不允许您确定长度,除非 1 是非法字符。在任何情况下,长度都必须单独存储。我页面中的内容都是我的假设是可能的(如 Q 中所述),但不一定正确。这就是实际的问题所在。也就是说,我可以同时确认它在至少 3 个版本的 Windows 下可靠地工作(无论如何都由 POSIX 保证)。
    • 确定长度没有帮助,但你不需要“确定”它。您需要防止攻击,为此填充可能会或可能不会足够,具体取决于哈希(例如,对于 CubeHash 它是)。但我的观点是,你需要更多的空间,而不仅仅是街区末端的洞。
    • 我再说一遍,除非操作系统使用段寄存器,否则在您越过页面边界之前它没有任何变化可以干扰。 AFAIK,没有现代操作系统使用段寄存器,但这是您可以轻松检查的。
    • 您所做的假设是错误的,对不起。防止“攻击”不是优先事项,检测文件修改才是。但是,如果有的话,那么保存显式长度可以强化哈希以防止碰撞攻击,但即便如此,它也没有意义(仍然,我需要知道长度)。附加任何数量的已知明文对避免“攻击”没有任何帮助。使用已知值(包括全零)进行填充的原因是,对相同的非块大小的消息进行散列处理总是会得到相同的散列值。
    • 关于操作系统无法干预,例如,这可以通过将最后一页标记为只读并在故障处理程序中执行此操作来完成(理论上,如问题中所指出的那样),就像每个现代操作系统都会提交线程堆栈。这不太可能,因为它消耗 CPU 并没有太大的收获,就我能想到的 x86 而言。当时我感兴趣的是,是否有任何“真实”架构在偏执模式下运行,但情况可能并非如此(“真实”架构,例如不使用穿孔卡片和 7 位字节)。
    猜你喜欢
    • 1970-01-01
    • 2018-04-28
    • 2016-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多