关于FD_ISSET引起进程崩溃的问题跟踪


Creative Commons LicenseCreative Commons LicenseCreative Commons License

1 进程崩溃现象

1)奔溃时候的栈如下:

1
2
3
4
5
6
7
8
9
10
11
#0 0x4069ce08 in raise () from /opt/work/gxe5100/lib/libc.so.6
#1 0x406a098c in abort () from /opt/work/gxe5100/lib/libc.so.6
#2 0x406d63f8 in __libc_message () from /opt/work/gxe5100/lib/libc.so.6
#3 0x40752a10 in __fortify_fail () from /opt/work/gxe5100/lib/libc.so.6
#4 0x40751994 in __chk_fail () from /opt/work/gxe5100/lib/libc.so.6
#5 0x40752988 in __fdelt_warn () from /opt/work/gxe5100/lib/libc.so.6
#6 0x439f3228 in handle_single_thread (arg=<optimized out>) at codec_svip.c:1134
#7 0x00242844 in dummy_start (data=<optimized out>) at utils.c:1237
#8 0x4048f3f8 in start_thread () from /opt/work/gxe5100/lib/libpthread.so.0
#9 0x40742048 in ?? () from /opt/work/gxe5100/lib/libc.so.6
Backtrace stopped: frame did not save the PC

2)定位代码的位置在调用FD_ISSET(fd, &fds)函数的地方

初步分析思路:

当时想FD_ISSET是一个很常见的C库函数,怎么可能在这个地方出现了奔溃,然后想是不是可能fd的值异常会导致经常奔溃,然后我尝试自己写代码测试强制把fd的值赋值成负数或者一个很大的整数,但是结果都不能把进程搞奔溃。但是看代码逻辑除了fd值出现异常其他地方都没有可能,猜测和实践结果产生了冲突,无奈只好把glibc中的FD_ISSET拿出来分析。《GLIBC库函数中FD_SET/FD_CLR/FD_ISSET/FD_ZERO》

2 根据堆栈跟踪GLIBC FD_ISSET函数

1)根据栈信息,跟踪代码走的流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
FD_ISSET --> __FD_ELT(d) ->__fdelt_warn ()/__fdelt_chk --> __chk_fail() -->__fortify_fail()-->__libc_message->abort()
其中:必须满足__USE_FORTIFY_LEVEL >0 和定义 __GNUC__
=====================================================
#if __USE_FORTIFY_LEVEL > 0 && defined __GNUC__
# include <bits/select2.h>
#endif
/* misc/bits/select2.h */
#define __FD_ELT(d) \
__extension__ \
({ long int __d = (d); \
(__builtin_constant_p (__d) \
? (0 <= __d && __d < __FD_SETSIZE \
? (__d / __NFDBITS) \
: __fdelt_warn (__d)) \
: __fdelt_chk (__d)); })
/* debug/fdelt_chk.c */

long int
__fdelt_chk (long int d)
{
if (d < 0 || d >= FD_SETSIZE)
__chk_fail ();

return d / __NFDBITS;
}

strong_alias (__fdelt_chk, __fdelt_warn)

这是一个多级的问号表达式,为了清楚的表达结构层次,我们可以把表达式简写成如下形式

1
2
3
4
5
6
({ (A ?  (B?C:D) :  E)})
A = __builtin_constant_p (__d)
B = 0 <= __d && __d < __FD_SETSIZE
C = (__d / __NFDBITS)
D = __fdelt_warn (__d)
E = __fdelt_chk (__d)

如果d是常量调用(B?C:D),否则直接调用E来判断
程序判断d的值不在区间[0~FD_SETSIZE]最终通过调用abort使整个进程异常退出:

1
__fdelt_warn ()/__fdelt_chk --> __chk_fail() -->__fortify_fail()-->__libc_message->abort()

2)编译参数的差异,导致FD_ISSET走的代码路径有差异

之前提到,直接把fd的参数赋值成负数都不会奔溃,进一步发现是我的测试程序和实际奔溃的进程存在编译参数上的差异。编译的参数里加了如下的参数导致程序走1的流程

1
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -g3 -O3

否则不会调用到fdelt_warn ()/fdelt_chk导致abort

3 编写测试代码验证

为了验证是-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -g3 -O3编译参数导致的奔溃,我们写个简单的测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* test.c */
#include <stdio.h>
#include <sys/select.h>
int main()
{
fd_set fds;
FD_SET(99, &fds);
if (FD_ISSET(98,&fds))

printf("fd 98 is set\n");

/* crash here */
FD_ISSET(1025, &fds);
printf("No crash\n");

return (0);
}

(1) 不带编译参数

1
2
3
4
5
$ gcc -o test2 test2.c
$ ./test2
fd 98 is set

No crash

(2) 带编译参数-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -g3 -O3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$ gcc -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -g3 -O3 -o test2 test2.c
$ ./test2
fd 98 is set

*** buffer overflow detected ***: ./test2 terminated
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x7338f)[0x7fc945a7338f]
/lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x5c)[0x7fc945b0ac9c]
/lib/x86_64-linux-gnu/libc.so.6(+0x109b60)[0x7fc945b09b60]
/lib/x86_64-linux-gnu/libc.so.6(+0x10abe7)[0x7fc945b0abe7]
./test2[0x4004c8]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5)[0x7fc945a21ec5]
./test2[0x400502]

======= Memory map: ========
00400000-00401000 r-xp 00000000 08:10 17715240 /home/javen/test2
00600000-00601000 r--p 00000000 08:10 17715240 /home/javen/test2
00601000-00602000 rw-p 00001000 08:10 17715240 /home/javen/test2
01aa7000-01ac8000 rw-p 00000000 00:00 0 [heap]
7fc9457ea000-7fc945800000 r-xp 00000000 08:07 1048595 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fc945800000-7fc9459ff000 ---p 00016000 08:07 1048595 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fc9459ff000-7fc945a00000 rw-p 00015000 08:07 1048595 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fc945a00000-7fc945bbb000 r-xp 00000000 08:07 1049244 /lib/x86_64-linux-gnu/libc-2.19.so
7fc945bbb000-7fc945dba000 ---p 001bb000 08:07 1049244 /lib/x86_64-linux-gnu/libc-2.19.so
7fc945dba000-7fc945dbe000 r--p 001ba000 08:07 1049244 /lib/x86_64-linux-gnu/libc-2.19.so
7fc945dbe000-7fc945dc0000 rw-p 001be000 08:07 1049244 /lib/x86_64-linux-gnu/libc-2.19.so
7fc945dc0000-7fc945dc5000 rw-p 00000000 00:00 0
7fc945dc5000-7fc945de8000 r-xp 00000000 08:07 1049238 /lib/x86_64-linux-gnu/ld-2.19.so
7fc945fbd000-7fc945fc0000 rw-p 00000000 00:00 0
7fc945fe3000-7fc945fe7000 rw-p 00000000 00:00 0
7fc945fe7000-7fc945fe8000 r--p 00022000 08:07 1049238 /lib/x86_64-linux-gnu/ld-2.19.so
7fc945fe8000-7fc945fe9000 rw-p 00023000 08:07 1049238 /lib/x86_64-linux-gnu/ld-2.19.so
7fc945fe9000-7fc945fea000 rw-p 00000000 00:00 0
7ffe42c83000-7ffe42ca4000 rw-p 00000000 00:00 0 [stack]
7ffe42ccb000-7ffe42ccd000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Aborted (core dumped)

Gdb看到堆栈的信息:

1
2
3
4
5
6
7
8
9
10
11
(gdb) bt
#0 0x00007f28e572ecc9 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#1 0x00007f28e57320d8 in __GI_abort () at abort.c:89
#2 0x00007f28e576b394 in __libc_message (do_abort=do_abort@entry=2, fmt=fmt@entry=0x7f28e587752b "*** %s ***: %s terminated\n")
at ../sysdeps/posix/libc_fatal.c:175
#3 0x00007f28e5802c9c in __GI___fortify_fail (msg=<optimized out>,
msg@entry=0x7f28e58774c2 "buffer overflow detected") at fortify_fail.c:37
#4 0x00007f28e5801b60 in __GI___chk_fail () at chk_fail.c:28
#5 0x00007f28e5802be7 in __fdelt_chk (d=<optimized out>) at fdelt_chk.c:25
#6 0x0000000000400657 in ?? ()
#7 0x0000000000000000 in ?? ()

4 总结

导致进程奔溃的原因归结于以下两点

1)FD_ISSET(fd, &fds)传递的fd参数的范围不在[0, FD_SETSIZE]

2)开启了增强的应用安全机制,使用编译参数-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -O3

可以看出这种增强的安全机制可以暴露出代码的一些不易被发现的问题,如果不开启这个,及时附上了一个错误的fd值程序在大多情况下还是可以正常工作。

-------------本文结束感谢您的阅读-------------
如果文章对您有帮助,也可以打赏支持喔!