如何记录烟囱的金丝雀污染?


11

标志GCC -fstack-protector标志启用堆栈金丝雀用于堆栈溢出保护。默认情况下,默认情况下此标志的用法日趋突出。

如果使用-fstack-protector编译了程序包,并且我们在程序中溢出了缓冲区,则很可能会出现如下错误:

*** buffer overflow detected ***: /xxx/xxx terminated

但是,“谁”负责这些错误消息?这些消息在哪里记录?syslog守护程序会选择这些消息吗?

Answers:


10

会检测到堆栈粉碎libssp,它是的一部分gcc。它非常努力地将消息输出到终端,并且只有在失败的情况下,它才会记录到系统日志中-因此在实践中,您将在守护程序和GUI应用程序的日志中看到缓冲区溢出消息。

输出消息后,请libssp尝试多种退出方法,包括使应用程序崩溃;异常退出记录器之一可能会捕获到该错误,但这不能保证。


1
让我举一个具体的例子,作为进一步探讨这一解释的一种方式。让我们为这个示例选择nginx。我已经用堆栈金丝雀编译了nginx。当我运行nginx时,它会启动一个进程,但不会向shell输出任何内容。而是,任何消息都记录在其多个日志文件中。如果nginx检测到堆栈崩溃,libssp将通过nginx使用的stderr输出输出其消息。然后,libssp可能会尝试退出该进程(或nginx的子进程)。如果“不需要”使应用程序崩溃,那么异常的退出记录器将不会接管。这是正确的解释吗?
aedcv

不太-它尝试应用程序崩溃,使用__builtin_trap()第一,如果失败,试图挑起段冲突,并且只如果失败,状态为127退出
斯蒂芬·基特

与通过核心产生方法(例如abort())退出相比,消息部分的打印没有更好的保证成功。
maxschlepzig

7

默认情况下,诸如CentOS / Fedora之类的现代Linux发行版默认设置了崩溃处理守护程序(例如systemd-coredumpabortd)。

因此,当您的程序以异常方式(segfault,未捕获的异常,中止,非法指令等)终止时,该守护进程将注册并记录该事件。因此,您可以在系统日志中找到一些消息,并可能找到带有一些其他详细信息(例如核心文件,日志等)的目录引用。

$ cat test_stack_protector.c 
#include <string.h>

int f(const char *q)
{
  char s[10];
  strcpy(s, q);
  return s[0] + s[1];
}

int main(int argc, char **argv)
{
  return f(argv[1]);
}

编译:

$ gcc -Wall -fstack-protector test_stack_protector.c -o test_stack_protector

执行:

$ ./test_stack_protector 'hello world'
*** stack smashing detected ***: ./test_stack_protector terminated
======= Backtrace: =========
/lib64/libc.so.6(+0x7c8dc)[0x7f885b4388dc]
/lib64/libc.so.6(__fortify_fail+0x37)[0x7f885b4dfaa7]
/lib64/libc.so.6(__fortify_fail+0x0)[0x7f885b4dfa70]
./test_stack_protector[0x400599]
./test_stack_protector[0x4005bd]
/lib64/libc.so.6(__libc_start_main+0xea)[0x7f885b3dc50a]
./test_stack_protector[0x40049a]
======= Memory map: ========
00400000-00401000 r-xp 00000000 00:28 1151979                            /home/juser/program/stackprotect/test_stack_protector
00600000-00601000 r--p 00000000 00:28 1151979                            /home/juser/program/stackprotect/test_stack_protector
00601000-00602000 rw-p 00001000 00:28 1151979                            /home/juser/program/stackprotect/test_stack_protector
0067c000-0069d000 rw-p 00000000 00:00 0                                  [heap]
7f885b1a5000-7f885b1bb000 r-xp 00000000 00:28 1052100                    /usr/lib64/libgcc_s-7-20170915.so.1
7f885b1bb000-7f885b3ba000 ---p 00016000 00:28 1052100                    /usr/lib64/libgcc_s-7-20170915.so.1
7f885b3ba000-7f885b3bb000 r--p 00015000 00:28 1052100                    /usr/lib64/libgcc_s-7-20170915.so.1
7f885b3bb000-7f885b3bc000 rw-p 00016000 00:28 1052100                    /usr/lib64/libgcc_s-7-20170915.so.1
7f885b3bc000-7f885b583000 r-xp 00000000 00:28 945348                     /usr/lib64/libc-2.25.so
7f885b583000-7f885b783000 ---p 001c7000 00:28 945348                     /usr/lib64/libc-2.25.so
7f885b783000-7f885b787000 r--p 001c7000 00:28 945348                     /usr/lib64/libc-2.25.so
7f885b787000-7f885b789000 rw-p 001cb000 00:28 945348                     /usr/lib64/libc-2.25.so
7f885b789000-7f885b78d000 rw-p 00000000 00:00 0 
7f885b78d000-7f885b7b4000 r-xp 00000000 00:28 945341                     /usr/lib64/ld-2.25.so
7f885b978000-7f885b97b000 rw-p 00000000 00:00 0 
7f885b9b0000-7f885b9b3000 rw-p 00000000 00:00 0 
7f885b9b3000-7f885b9b4000 r--p 00026000 00:28 945341                     /usr/lib64/ld-2.25.so
7f885b9b4000-7f885b9b6000 rw-p 00027000 00:28 945341                     /usr/lib64/ld-2.25.so
7ffc59966000-7ffc59987000 rw-p 00000000 00:00 0                          [stack]
7ffc5999c000-7ffc5999f000 r--p 00000000 00:00 0                          [vvar]
7ffc5999f000-7ffc599a1000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
zsh: abort (core dumped)  ./test_stack_protector 'hello world'

退出状态为134,即128 + 6,即128加中止信号编号。

系统日志:

Oct 16 20:57:59 example.org audit[17645]: ANOM_ABEND auid=1000 uid=1000 gid=1000 ses=3 subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 pid=17645 comm="test_stack_prot" exe="/home/juser/program/stackprotect/test_stack_protector" sig=6 res=1
Oct 16 20:57:59 example.org systemd[1]: Started Process Core Dump (PID 17646/UID 0).
Oct 16 20:57:59 example.org audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=systemd-coredump@21-17646-0 comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
Oct 16 20:57:59 example.org systemd-coredump[17647]: Process 17645 (test_stack_prot) of user 1000 dumped core.

                           Stack trace of thread 17645:
                           #0  0x00007f885b3f269b raise (libc.so.6)
                           #1  0x00007f885b3f44a0 abort (libc.so.6)
                           #2  0x00007f885b4388e1 __libc_message (libc.so.6)
                           #3  0x00007f885b4dfaa7 __fortify_fail (libc.so.6)
                           #4  0x00007f885b4dfa70 __stack_chk_fail (libc.so.6)
                           #5  0x0000000000400599 f (test_stack_protector)
                           #6  0x00000000004005bd main (test_stack_protector)
                           #7  0x00007f885b3dc50a __libc_start_main (libc.so.6)
                           #8  0x000000000040049a _start (test_stack_protector)
Oct 16 20:57:59 example.org audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=systemd-coredump@21-17646-0 comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
Oct 16 20:58:00 example.org abrt-notification[17696]: Process 17645 (test_stack_protector) crashed in __fortify_fail()

这意味着您可以从日志auditd审计守护进程,并systemd-coredump崩溃处理。

要验证是否配置了崩溃处理守护程序,可以检查/proc,例如:

$ cat /proc/sys/kernel/core_pattern
|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %e

(在Fedora 26,x86-64上进行的所有测试)


1
我很高兴您发布了这个示例。金丝雀由gcc放置。(如果我错了,请指正。)我假设发生的事情是这样的:gcc在程序中添加“额外的代码”以实现canary功能;在执行期间和函数返回之前,将检查该值;如果被污染,程序将输出消息“检测到堆栈粉碎”并引发错误。操作系统会拾取此错误,识别出分段错误并打印您发布的回溯和内存映射。最后,操作系统杀死应用程序,生成核心转储,并记录到sys日志
aedcv

@aedcv,这几乎就是故事了-更准确地说:堆栈粉碎检查代码调用abort()会产生中止信号,即没有分段错误。只是中止/分段故障等的默认信号处理程序会产生相同的动作:写入内核并以不等于0的退出状态退出进程,该退出状态也对信号号进行编码。核心编写工作由内核完成,其行为可通过进行配置/proc/.../core_pattern。在上面的示例中,配置了一个用户空间助手,因此将其调用。内核还触发审核。
maxschlepzig

@maxschlepzig不太完全abort(),SSP代码使用了__builtin_trap()(但是效果是一样的)。
史蒂芬·基特

1
@StephenKitt,好,看看上面示例中的堆栈跟踪。在那里,您可以清楚地看到如何abort()调用。
maxschlepzig

1
@maxschlepzig是的,当然可以,但这是一个实现细节(GCC代码__builtin_trap()用来避免对的显式依赖abort())。其他发行版具有不同的堆栈跟踪。
斯蒂芬·基特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.