为什么在PT_GNU_STACK程序头上设置执行位时,进程的所有段都变成可执行的
When setting execution bit on PT_GNU_STACK program header, why do all segments of the process become executable
尝试控制段上的可执行位,我发现加载程序如何使用 PT_GNU_STACK
有一个巨大的怪癖。
根据 elf(5)
联机帮助页,PT_GNU_STACK
用作:
GNU extension which is used by the Linux kernel to control the state of the stack via the flags set in the p_flags member.
execstack
联机帮助页,也支持这个:
... ELF binaries and shared libraries now can
be marked as requiring executable stack or not requiring it. This
marking is done through the p_flags field in the PT_GNU_STACK program
header entry.
但是,除了设置堆栈可执行文件外,当我设置该位时,几乎 所有 段都变成可执行文件。
例如,当我运行 sleep
,我得到这个内存映射
sleep 100 & cat /proc/$!/maps
[1] 1260
561460d8d000-561460d94000 r-xp 00000000 08:01 524383 /bin/sleep
561460f94000-561460f95000 r--p 00007000 08:01 524383 /bin/sleep
561460f95000-561460f96000 rw-p 00008000 08:01 524383 /bin/sleep
561462eca000-561462eeb000 rw-p 00000000 00:00 0 [heap]
7f02b08b9000-7f02b0b97000 r--p 00000000 08:01 1966102 /usr/lib/locale/locale-archive
7f02b0b97000-7f02b0d7e000 r-xp 00000000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f02b0d7e000-7f02b0f7e000 ---p 001e7000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f02b0f7e000-7f02b0f82000 r--p 001e7000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f02b0f82000-7f02b0f84000 rw-p 001eb000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f02b0f84000-7f02b0f88000 rw-p 00000000 00:00 0
7f02b0f88000-7f02b0faf000 r-xp 00000000 08:01 655375 /lib/x86_64-linux-gnu/ld-2.27.so
7f02b11a3000-7f02b11a5000 rw-p 00000000 00:00 0
7f02b11af000-7f02b11b0000 r--p 00027000 08:01 655375 /lib/x86_64-linux-gnu/ld-2.27.so
7f02b11b0000-7f02b11b1000 rw-p 00028000 08:01 655375 /lib/x86_64-linux-gnu/ld-2.27.so
7f02b11b1000-7f02b11b2000 rw-p 00000000 00:00 0
7ffc74d95000-7ffc74db6000 rw-p 00000000 00:00 0 [stack]
7ffc74dfa000-7ffc74dfd000 r--p 00000000 00:00 0 [vvar]
7ffc74dfd000-7ffc74dff000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
但是如果我先设置 PT_GNU_STACK
执行位,我会得到
execstack -s `which sleep` ; sleep 100 & cat /proc/$!/maps
[1] 1282
55b27b14f000-55b27b156000 r-xp 00000000 08:01 537509 /bin/sleep
55b27b356000-55b27b357000 r-xp 00007000 08:01 537509 /bin/sleep
55b27b357000-55b27b358000 rwxp 00008000 08:01 537509 /bin/sleep
55b27bae6000-55b27bb07000 rwxp 00000000 00:00 0 [heap]
7f99b5359000-7f99b5637000 r-xp 00000000 08:01 1966102 /usr/lib/locale/locale-archive
7f99b5637000-7f99b581e000 r-xp 00000000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f99b581e000-7f99b5a1e000 ---p 001e7000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f99b5a1e000-7f99b5a22000 r-xp 001e7000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f99b5a22000-7f99b5a24000 rwxp 001eb000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f99b5a24000-7f99b5a28000 rwxp 00000000 00:00 0
7f99b5a28000-7f99b5a4f000 r-xp 00000000 08:01 655375 /lib/x86_64-linux-gnu/ld-2.27.so
7f99b5c43000-7f99b5c45000 rwxp 00000000 00:00 0
7f99b5c4f000-7f99b5c50000 r-xp 00027000 08:01 655375 /lib/x86_64-linux-gnu/ld-2.27.so
7f99b5c50000-7f99b5c51000 rwxp 00028000 08:01 655375 /lib/x86_64-linux-gnu/ld-2.27.so
7f99b5c51000-7f99b5c52000 rwxp 00000000 00:00 0
7fffc6a4d000-7fffc6a6e000 rwxp 00000000 00:00 0 [stack]
7fffc6b36000-7fffc6b39000 r--p 00000000 00:00 0 [vvar]
7fffc6b39000-7fffc6b3b000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
除 [vvar]
之外的每个段和 libc
支持的一个段现在都是可执行的。
我已经尝试过我的 Arch 设置、Ubuntu 服务器虚拟机 (Bionic) 和 Mint 桌面 (Tessa) - 所有结果都相同。
loader为什么要这样做?
这只是 Linux 内核(不是用户空间)在某些架构上所做的事情。来自内核源代码中的 fs/binfmt_elf.c
(executable_stack
是基于 PT_GNU_STACK
保护标志设置的):
if (elf_read_implies_exec(*elf_ex, executable_stack))
current->personality |= READ_IMPLIES_EXEC;
在 x86 上,我们有这个(来自 arch/x86/include/asm/elf.h
):
/*
* An executable for which elf_read_implies_exec() returns TRUE will
* have the READ_IMPLIES_EXEC personality flag set automatically.
*/
#define elf_read_implies_exec(ex, executable_stack) \
(executable_stack != EXSTACK_DISABLE_X)
所以PT_GNU_STACK
不直接控制堆栈的保护标志,而是READ_IMPLIES_EXEC
个人。
我假设内核这样做是因为许多体系结构(amd64、i386、s390x)最初都是 read-implies-exec,因此可以想象人们拥有依赖于此行为的旧二进制文件。通过标记此类旧二进制文件的方法,人们仍然可以 运行 他们的系统增强内存保护实施,仅根据需要选择退出旧二进制文件的子集。
如果你 运行 带有显式加载程序调用的二进制文件,你应该只看到一个可执行堆栈,例如使用适当修补的 cat
程序:
$ /lib64/ld-linux-x86-64.so.2 ./cat /proc/self/maps
555555cc7000-555555ce8000 rw-p 00000000 00:00 0 [heap]
7fbdc90cb000-7fbdc90ed000 rw-p 00000000 00:00 0
7fbdc90ed000-7fbdd60d2000 r--p 00000000 fd:02 1688290164 /usr/lib/locale/locale-archive
7fbdd60d2000-7fbdd60f7000 r--p 00000000 fd:02 690045363 /usr/lib64/libc-2.30.so
7fbdd60f7000-7fbdd6246000 r-xp 00025000 fd:02 690045363 /usr/lib64/libc-2.30.so
7fbdd6246000-7fbdd6290000 r--p 00174000 fd:02 690045363 /usr/lib64/libc-2.30.so
7fbdd6290000-7fbdd6291000 ---p 001be000 fd:02 690045363 /usr/lib64/libc-2.30.so
7fbdd6291000-7fbdd6294000 r--p 001be000 fd:02 690045363 /usr/lib64/libc-2.30.so
7fbdd6294000-7fbdd6297000 rw-p 001c1000 fd:02 690045363 /usr/lib64/libc-2.30.so
7fbdd6297000-7fbdd629d000 rw-p 00000000 00:00 0
7fbdd62bb000-7fbdd62bd000 r--p 00000000 00:24 3253068 /tmp/cat
7fbdd62bd000-7fbdd62c2000 r-xp 00002000 00:24 3253068 /tmp/cat
7fbdd62c2000-7fbdd62c5000 r--p 00007000 00:24 3253068 /tmp/cat
7fbdd62c5000-7fbdd62c6000 r--p 00009000 00:24 3253068 /tmp/cat
7fbdd62c6000-7fbdd62c7000 rw-p 0000a000 00:24 3253068 /tmp/cat
7fbdd62c7000-7fbdd62c9000 r--p 00000000 fd:02 690045355 /usr/lib64/ld-2.30.so
7fbdd62c9000-7fbdd62e9000 r-xp 00002000 fd:02 690045355 /usr/lib64/ld-2.30.so
7fbdd62e9000-7fbdd62f1000 r--p 00022000 fd:02 690045355 /usr/lib64/ld-2.30.so
7fbdd62f2000-7fbdd62f3000 r--p 0002a000 fd:02 690045355 /usr/lib64/ld-2.30.so
7fbdd62f3000-7fbdd62f4000 rw-p 0002b000 fd:02 690045355 /usr/lib64/ld-2.30.so
7fbdd62f4000-7fbdd62f5000 rw-p 00000000 00:00 0
7ffdabe6a000-7ffdabe89000 rwxp 00000000 00:00 0 [stack]
7ffdabe89000-7ffdabe8b000 rw-p 00000000 00:00 0
7ffdabfed000-7ffdabff1000 r--p 00000000 00:00 0 [vvar]
7ffdabff1000-7ffdabff3000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
尝试控制段上的可执行位,我发现加载程序如何使用 PT_GNU_STACK
有一个巨大的怪癖。
根据 elf(5)
联机帮助页,PT_GNU_STACK
用作:
GNU extension which is used by the Linux kernel to control the state of the stack via the flags set in the p_flags member.
execstack
联机帮助页,也支持这个:
... ELF binaries and shared libraries now can be marked as requiring executable stack or not requiring it. This marking is done through the p_flags field in the PT_GNU_STACK program header entry.
但是,除了设置堆栈可执行文件外,当我设置该位时,几乎 所有 段都变成可执行文件。
例如,当我运行 sleep
,我得到这个内存映射
sleep 100 & cat /proc/$!/maps
[1] 1260
561460d8d000-561460d94000 r-xp 00000000 08:01 524383 /bin/sleep
561460f94000-561460f95000 r--p 00007000 08:01 524383 /bin/sleep
561460f95000-561460f96000 rw-p 00008000 08:01 524383 /bin/sleep
561462eca000-561462eeb000 rw-p 00000000 00:00 0 [heap]
7f02b08b9000-7f02b0b97000 r--p 00000000 08:01 1966102 /usr/lib/locale/locale-archive
7f02b0b97000-7f02b0d7e000 r-xp 00000000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f02b0d7e000-7f02b0f7e000 ---p 001e7000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f02b0f7e000-7f02b0f82000 r--p 001e7000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f02b0f82000-7f02b0f84000 rw-p 001eb000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f02b0f84000-7f02b0f88000 rw-p 00000000 00:00 0
7f02b0f88000-7f02b0faf000 r-xp 00000000 08:01 655375 /lib/x86_64-linux-gnu/ld-2.27.so
7f02b11a3000-7f02b11a5000 rw-p 00000000 00:00 0
7f02b11af000-7f02b11b0000 r--p 00027000 08:01 655375 /lib/x86_64-linux-gnu/ld-2.27.so
7f02b11b0000-7f02b11b1000 rw-p 00028000 08:01 655375 /lib/x86_64-linux-gnu/ld-2.27.so
7f02b11b1000-7f02b11b2000 rw-p 00000000 00:00 0
7ffc74d95000-7ffc74db6000 rw-p 00000000 00:00 0 [stack]
7ffc74dfa000-7ffc74dfd000 r--p 00000000 00:00 0 [vvar]
7ffc74dfd000-7ffc74dff000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
但是如果我先设置 PT_GNU_STACK
执行位,我会得到
execstack -s `which sleep` ; sleep 100 & cat /proc/$!/maps
[1] 1282
55b27b14f000-55b27b156000 r-xp 00000000 08:01 537509 /bin/sleep
55b27b356000-55b27b357000 r-xp 00007000 08:01 537509 /bin/sleep
55b27b357000-55b27b358000 rwxp 00008000 08:01 537509 /bin/sleep
55b27bae6000-55b27bb07000 rwxp 00000000 00:00 0 [heap]
7f99b5359000-7f99b5637000 r-xp 00000000 08:01 1966102 /usr/lib/locale/locale-archive
7f99b5637000-7f99b581e000 r-xp 00000000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f99b581e000-7f99b5a1e000 ---p 001e7000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f99b5a1e000-7f99b5a22000 r-xp 001e7000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f99b5a22000-7f99b5a24000 rwxp 001eb000 08:01 655384 /lib/x86_64-linux-gnu/libc-2.27.so
7f99b5a24000-7f99b5a28000 rwxp 00000000 00:00 0
7f99b5a28000-7f99b5a4f000 r-xp 00000000 08:01 655375 /lib/x86_64-linux-gnu/ld-2.27.so
7f99b5c43000-7f99b5c45000 rwxp 00000000 00:00 0
7f99b5c4f000-7f99b5c50000 r-xp 00027000 08:01 655375 /lib/x86_64-linux-gnu/ld-2.27.so
7f99b5c50000-7f99b5c51000 rwxp 00028000 08:01 655375 /lib/x86_64-linux-gnu/ld-2.27.so
7f99b5c51000-7f99b5c52000 rwxp 00000000 00:00 0
7fffc6a4d000-7fffc6a6e000 rwxp 00000000 00:00 0 [stack]
7fffc6b36000-7fffc6b39000 r--p 00000000 00:00 0 [vvar]
7fffc6b39000-7fffc6b3b000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
除 [vvar]
之外的每个段和 libc
支持的一个段现在都是可执行的。
我已经尝试过我的 Arch 设置、Ubuntu 服务器虚拟机 (Bionic) 和 Mint 桌面 (Tessa) - 所有结果都相同。
loader为什么要这样做?
这只是 Linux 内核(不是用户空间)在某些架构上所做的事情。来自内核源代码中的 fs/binfmt_elf.c
(executable_stack
是基于 PT_GNU_STACK
保护标志设置的):
if (elf_read_implies_exec(*elf_ex, executable_stack))
current->personality |= READ_IMPLIES_EXEC;
在 x86 上,我们有这个(来自 arch/x86/include/asm/elf.h
):
/*
* An executable for which elf_read_implies_exec() returns TRUE will
* have the READ_IMPLIES_EXEC personality flag set automatically.
*/
#define elf_read_implies_exec(ex, executable_stack) \
(executable_stack != EXSTACK_DISABLE_X)
所以PT_GNU_STACK
不直接控制堆栈的保护标志,而是READ_IMPLIES_EXEC
个人。
我假设内核这样做是因为许多体系结构(amd64、i386、s390x)最初都是 read-implies-exec,因此可以想象人们拥有依赖于此行为的旧二进制文件。通过标记此类旧二进制文件的方法,人们仍然可以 运行 他们的系统增强内存保护实施,仅根据需要选择退出旧二进制文件的子集。
如果你 运行 带有显式加载程序调用的二进制文件,你应该只看到一个可执行堆栈,例如使用适当修补的 cat
程序:
$ /lib64/ld-linux-x86-64.so.2 ./cat /proc/self/maps
555555cc7000-555555ce8000 rw-p 00000000 00:00 0 [heap]
7fbdc90cb000-7fbdc90ed000 rw-p 00000000 00:00 0
7fbdc90ed000-7fbdd60d2000 r--p 00000000 fd:02 1688290164 /usr/lib/locale/locale-archive
7fbdd60d2000-7fbdd60f7000 r--p 00000000 fd:02 690045363 /usr/lib64/libc-2.30.so
7fbdd60f7000-7fbdd6246000 r-xp 00025000 fd:02 690045363 /usr/lib64/libc-2.30.so
7fbdd6246000-7fbdd6290000 r--p 00174000 fd:02 690045363 /usr/lib64/libc-2.30.so
7fbdd6290000-7fbdd6291000 ---p 001be000 fd:02 690045363 /usr/lib64/libc-2.30.so
7fbdd6291000-7fbdd6294000 r--p 001be000 fd:02 690045363 /usr/lib64/libc-2.30.so
7fbdd6294000-7fbdd6297000 rw-p 001c1000 fd:02 690045363 /usr/lib64/libc-2.30.so
7fbdd6297000-7fbdd629d000 rw-p 00000000 00:00 0
7fbdd62bb000-7fbdd62bd000 r--p 00000000 00:24 3253068 /tmp/cat
7fbdd62bd000-7fbdd62c2000 r-xp 00002000 00:24 3253068 /tmp/cat
7fbdd62c2000-7fbdd62c5000 r--p 00007000 00:24 3253068 /tmp/cat
7fbdd62c5000-7fbdd62c6000 r--p 00009000 00:24 3253068 /tmp/cat
7fbdd62c6000-7fbdd62c7000 rw-p 0000a000 00:24 3253068 /tmp/cat
7fbdd62c7000-7fbdd62c9000 r--p 00000000 fd:02 690045355 /usr/lib64/ld-2.30.so
7fbdd62c9000-7fbdd62e9000 r-xp 00002000 fd:02 690045355 /usr/lib64/ld-2.30.so
7fbdd62e9000-7fbdd62f1000 r--p 00022000 fd:02 690045355 /usr/lib64/ld-2.30.so
7fbdd62f2000-7fbdd62f3000 r--p 0002a000 fd:02 690045355 /usr/lib64/ld-2.30.so
7fbdd62f3000-7fbdd62f4000 rw-p 0002b000 fd:02 690045355 /usr/lib64/ld-2.30.so
7fbdd62f4000-7fbdd62f5000 rw-p 00000000 00:00 0
7ffdabe6a000-7ffdabe89000 rwxp 00000000 00:00 0 [stack]
7ffdabe89000-7ffdabe8b000 rw-p 00000000 00:00 0
7ffdabfed000-7ffdabff1000 r--p 00000000 00:00 0 [vvar]
7ffdabff1000-7ffdabff3000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]