我有一个数据结构,出于性能原因分配连续的内存块。 但后来,这个内存中出现了一些漏洞,不会被使用、初始化或触及。
由于运行时索引很容易溢出, 至少在调试模式下,我希望使该内存“不可访问”,即,使其访问行为尽可能接近分段错误。
我的想法是,分配并立即将其中的一部分标记为在所分配的内存的剩余生命周期内不可访问会很棒。
我读到了有关可以通过地址消毒剂检测到的“内存中毒”的内容。 例如,使用
https://github.com/google/sanitizers/wiki/AddressSanitizerManualPoisoning中的
ASAN_POISON_MEMORY_REGION
但是,我不想给项目添加依赖;似乎(?)我需要使用地址清理程序来运行和编译。
我可以使用什么方法通过在分配后“标记”内存来检测内存的意外使用?
最初,我正在考虑模拟分割,但欢迎其他方法。
(可以在 Linux 上使用 GCC 和 clang 运行的东西。)
检测越界内存访问的一种方法是使用操作 系统的内存保护设施将内存页标记为 无法访问。对这些页面的访问将触发内存访问 违规(unix 语言中的“分段错误”)。主要限制 这种方式是保护只能在页面控制 粒度,通常为 4 kiB 或 8 kiB。
电围栏工具可以做到这一点 自动为所有
malloc
ed 内存设置不可访问的防护
每个分配块之前和之后的页面,并创建 free
d 内存
无法访问。不过,我已经很久没有使用它了,而且
不知道目前维护情况如何。
要直接完成此操作,无需任何第三方工具,在Linux下,我们可以使用
mprotect
至
使页面无法访问。我们可以使用 POSIX
sysconf
检测页面大小,并使用C11
aligned_alloc
分配页对齐数据。
这是一个完整的示例程序,它分配四个页面,然后使 两端的页面都无法访问。这个想法是中间的两个页面 旨在由程序使用,而对结束页面的任何访问 将触发段错误:
// mprotect.c
// Demonstrate using 'mprotect' to make some allocated memory inaccessible.
#include <stdio.h> // printf, perror
#include <stdlib.h> // aligned_alloc
#include <sys/mman.h> // mprotect
#include <unistd.h> // sysconf, _SC_PAGESIZE
int main(int argc, char **argv)
{
// Get the system page size, which is the granularity at which
// 'mprotect' operates. 'sysconf' is a POSIX feature.
long pageSize = sysconf(_SC_PAGESIZE);
printf("pageSize: %ld\n", pageSize);
// Allocate a page-aligned region of four pages. 'aligned_alloc' is a
// C11 feature.
char *entireRegion = aligned_alloc(pageSize, pageSize * 4);
if (!entireRegion) {
fprintf(stderr, "aligned_alloc failed\n");
return 2;
}
printf("entireRegion: %p\n", entireRegion);
// Deny access to the first page. 'mprotect' is a Linux feature.
if (mprotect(entireRegion, pageSize, PROT_NONE) < 0) {
perror("mprotect #1");
return 2;
}
// Deny access to the last page.
if (mprotect(entireRegion + pageSize*3, pageSize, PROT_NONE) < 0) {
perror("mprotect #2");
return 2;
}
// Fully overwrite the two middle pages just to show they remain
// accessible in the usual way.
for (char *p = entireRegion + pageSize; p < entireRegion + pageSize*3; ++p) {
*p = 'x';
}
// If called with one argument, try reading the byte just before the
// middle pages.
if (argc == 2) {
char c = entireRegion[pageSize-1]; // Segfaults.
printf("c: %d\n", (int)c); // Not reached.
}
// If called with two arguments, try reading the byte just after.
if (argc == 3) {
char c = entireRegion[pageSize*3]; // Segfaults.
printf("c: %d\n", (int)c); // Not reached.
}
// Note: To clean up properly, we would need to undo the effect of the
// 'mprotect' calls and then 'free' the 'entireRegion'. That is
// omitted from this demonstration program for brevity.
printf("done\n");
return 0;
}
// EOF
示例在 Linux/x64 下运行:
$ ./mprotect
pageSize: 4096
entireRegion: 0x56551b83c000
done
$ ./mprotect 1
pageSize: 4096
entireRegion: 0x55aff00cb000
Segmentation fault (core dumped)
Exit 139 # this is printed by my shell
$ ./mprotect 1 2
pageSize: 4096
entireRegion: 0x556ecf87c000
Segmentation fault (core dumped)
Exit 139
在
gdb
下运行确认它们按预期出现段错误
地点。