在x86和x64的同一页面中读取缓冲区的末尾是否安全?

如果允许高性能算法中发现的许多方法在输入缓冲区的末尾读取少量内容,则可以简化这些方法。在此,“少量”通常表示W - 1末尾最多字节,其中W的字长是算法字节数(例如,对于以64位块为单位的输入处理算法,最多为7个字节)。


显然,一般来说,从输入缓冲区的末尾写入数据永远都不安全,因为您可能会破坏缓冲区1之外的数据。还很清楚,由于缓冲区的末尾可能无法读取,因此将超出缓冲区末尾的内容读入另一页可能会触发分段错误/访问冲突。


但是,在读取对齐值的特殊情况下,至少在x86上似乎无法出现页面错误。在该平台上,页面(以及因此的内存保护标志)具有4K的粒度(可以使用更大的页面,例如2MiB或1GiB,但它们是4K的倍数),因此对齐的读取操作将仅访问同一页面中的字节作为有效字节缓冲区的一部分。


这是一些循环的规范示例,该循环对齐其输入并从缓冲区末尾读取最多7个字节:


int processBytes(uint8_t *input, size_t size) {


    uint64_t *input64 = (uint64_t *)input, end64 = (uint64_t *)(input + size);

    int res;


    if (size < 8) {

        // special case for short inputs that we aren't concerned with here

        return shortMethod();

    }


    // check the first 8 bytes

    if ((res = match(*input)) >= 0) {

        return input + res;

    }


    // align pointer to the next 8-byte boundary

    input64 = (ptrdiff_t)(input64 + 1) & ~0x7;


    for (; input64 < end64; input64++) {

        if ((res = match(*input64)) > 0) {

            return input + res < input + size ? input + res : -1;

        }

    }


    return -1;

}

内部函数int match(uint64_t bytes)未显示,但是它是寻找与特定模式匹配的字节的东西,如果找到则返回最低位置(0-7),否则返回-1。


首先,为了简化说明,将大小小于8的案例当成了另一个函数。然后对前8个(未对齐的字节)进行一次检查。然后,对剩余floor((size - 7) / 8)的8个字节2的块进行循环。该循环最多可以读取缓冲区末尾的7个字节(当时为7字节input & 0xF == 1)。但是,返回调用有一个检查,该检查排除了超出缓冲区末尾的任何虚假匹配。


实际上,这样的功能在x86和x86-64上安全吗?


这些类型的过度读取在高性能代码中很常见。避免这种过度读取的特殊尾码也很常见。有时,您会看到后者取代了前者,从而使valgrind等工具静音。有时您会看到进行此类替换的建议,但由于成语安全且工具错误(或过于保守)而被拒绝3。


浮云间
浏览 537回答 3
3回答

呼唤远方

如果您允许考虑使用非CPU设备,那么一个潜在的不安全操作示例就是访问PCI映射的内存页的越界区域。不能保证目标设备使用与主内存子系统相同的页面大小或对齐方式。例如,[cpu page base]+0x800如果设备处于2KiB页面模式,则尝试访问地址可能会触发设备页面错误。这通常会导致系统错误检查。

哔哔one

uint32_t foo = *(uint32_t*)aligned_pointer将编译为32位负载。一次只测试一个字节并不重要foo。如果代码的行为取决于终止之后字节中的内容0,那是一个错误,但是完全加载它们可能会导致问题。访问检查在加载/存储上进行;寄存器无法跟踪有关数据来源的信息。glibc的出色实现甚至通过ALU馈入整个64B,从而将其组合成可以分支的一件事。
打开App,查看更多内容
随时随地看视频慕课网APP