我正在用这个把我的头撞到墙上。
在我的项目中,使用mmap
映射(/proc/self/maps
)分配内存时,尽管我只请求了可读内存,但它仍是一个可读且可执行的区域。
在研究了strace(看起来不错)和其他调试之后,我能够确定似乎唯一可以避免这个奇怪问题的东西:从项目中删除程序集文件,只保留纯C。(什么?!)
所以这是我一个奇怪的例子,我正在使用Ubunbtu 19.04和默认的gcc。
如果使用ASM文件(为空)编译目标可执行文件,则将mmap
返回一个可读和可执行区域,如果构建时没有该区域,则它将正常运行。请参阅/proc/self/maps
示例中已嵌入的输出。
example.c
#include#include #include int main() { void* p; p = mmap(NULL, 8192,PROT_READ,MAP_ANONYMOUS|MAP_PRIVATE,-1,0); { FILE *f; char line[512], s_search[17]; snprintf(s_search,16,"%lx",(long)p); f = fopen("/proc/self/maps","r"); while (fgets(line,512,f)) { if (strstr(line,s_search)) fputs(line,stderr); } fclose(f); } return 0; }
example.s:是一个空文件!
产出
附带ASM版本
VirtualBox:~/mechanics/build$ gcc example.c example.s -o example && ./example 7f78d6e08000-7f78d6e0a000 r-xp 00000000 00:00 0
没有ASM随附的版本
VirtualBox:~/mechanics/build$ gcc example.c -o example && ./example 7f1569296000-7f1569298000 r--p 00000000 00:00 0
Joseph Sible.. 90
Linux有一个称为的执行域READ_IMPLIES_EXEC
,这将使所有分配给它的页面PROT_READ
也被给出PROT_EXEC
。该程序将向您显示是否已启用该功能:
#include
#include
int main(void) {
printf("Read-implies-exec is %s\n", personality(0xffffffff) & READ_IMPLIES_EXEC ? "true" : "false");
return 0;
}
如果将其与一个空.s
文件一起编译,则会看到该文件已启用,但没有文件,则将其禁用。此值的初始值来自您Binary中的ELF元信息。做readelf -Wl example
。在没有空.s
文件的情况下进行编译时,您将看到以下行:
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
但是,当您使用它进行编译时:
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10
请注意,RWE
而不仅仅是RW
。这样做的原因是,链接器假定您的汇编文件需要read-implies-exec,除非明确告知它们不需要,并且如果程序的任何部分都需要read-implies-exec,那么它将为您的整个程序启用。GCC编译的汇编文件通过以下代码告诉它不需要此文件(如果使用编译,您将看到此文件-S
):
.section .note.GNU-stack,"",@progbits
将这一行放入example.s
,它将告诉链接器它也不需要它,然后您的程序将按预期运行。
Linux有一个称为的执行域READ_IMPLIES_EXEC
,这将使所有分配给它的页面PROT_READ
也被给出PROT_EXEC
。该程序将向您显示是否已启用该功能:
#include
#include
int main(void) {
printf("Read-implies-exec is %s\n", personality(0xffffffff) & READ_IMPLIES_EXEC ? "true" : "false");
return 0;
}
如果将其与一个空.s
文件一起编译,则会看到该文件已启用,但没有文件,则将其禁用。此值的初始值来自您Binary中的ELF元信息。做readelf -Wl example
。在没有空.s
文件的情况下进行编译时,您将看到以下行:
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
但是,当您使用它进行编译时:
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10
请注意,RWE
而不仅仅是RW
。这样做的原因是,链接器假定您的汇编文件需要read-implies-exec,除非明确告知它们不需要,并且如果程序的任何部分都需要read-implies-exec,那么它将为您的整个程序启用。GCC编译的汇编文件通过以下代码告诉它不需要此文件(如果使用编译,您将看到此文件-S
):
.section .note.GNU-stack,"",@progbits
将这一行放入example.s
,它将告诉链接器它也不需要它,然后您的程序将按预期运行。
除了使用特定于GNU的section指令变体来修改程序集文件之外,您还可以添加-Wa,--noexecstack
到命令行中以构建程序集文件。例如,看看我是如何在musl的configure
:
https://git.musl-libc.org/cgit/musl/commit/configure?id=adefe830dd376be386df5650a09c313c483adf1a
我相信至少某些带有集成汇编程序的clang版本可能要求将其作为--noexecstack
(不带-Wa
)进行传递,因此您的configure脚本可能应该同时检查两者并查看哪个被接受。
您也可以-Wl,-z,noexecstack
在链接时间(中使用LDFLAGS
)获得相同的结果。这样做的缺点是,如果您的项目生成.a
供其他软件使用的静态()库文件,则无济于事,因为当其他程序使用链接时选项时,您将无法对其进行控制。