K&R: C - 检测到堆栈溢出攻击

3

我的代码如下:

int find_test(int argc, char *argv[])
{
    char line[MAX_LINES];
    int c, except = 0, found = 0, number = 0;
    long lineno = 0;
    int i = 0;

    while(--argc > 0 && (*++argv)[0] == '-'){
        while(c = *++argv[0]){
            switch(c){
                case 'x':
                    except = 1;
                    break;
                case 'n':
                    number = 1;
                    break;
                default:
                    printf("find:illegal option %c\n", c);
                    argc = 0;
                    found = -1;
                    break;
            }
        }
    }
    if(argc != 1){
        printf("Usage:find -x -n pattern\n");
    }else{
        while(getline(line, MAX_LENGTH) > 0){
            lineno++;
            if((strstr(line, *argv) != NULL) != except){
                if(number)
                    printf("%ld:", lineno);
                printf("%s\n", line);
                found++;
            }
        }
    }
    return found;
}

使用getline函数:

int getline(char *line, int maxline)
{
    char *p = line;
    int c;

    while(maxline-- && (c = getchar()) != EOF && c != '\n'){
        *line++ = c;
    }
    if(maxline > 0)
        *line = '\0';

    return line - p;
}

当我使用gcc -Wall -O2 -g a.c -o a.out编译后,执行a.out -x -n 111<find_test,其中find_test是测试数据:
line1:111111111111111
line2:222222222222222
line3:222222222222222
line4:444444444444444
line2:kasdskskdk

我收到了以下错误信息:

*** stack smashing detected ***: ./a.out terminated
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)[0xb7ead138]
/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x0)[0xb7ead0f0]
./a.out[0x8048e95]
./a.out[0x8048ec2]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe0)[0xb7dd6450]
./a.out[0x8048641]
======= Memory map: ========
08048000-0804a000 r-xp 00000000 08:08 17488      /home/jyc/prgm/the_c_p_l/a.out
0804a000-0804b000 rw-p 00001000 08:08 17488      /home/jyc/prgm/the_c_p_l/a.out
0804b000-0806c000 rw-p 0804b000 00:00 0          [heap]
b7dbf000-b7dc0000 rw-p b7dbf000 00:00 0 
b7dc0000-b7f09000 r-xp 00000000 08:08 694644     /lib/tls/i686/cmov/libc-2.7.so
b7f09000-b7f0a000 r--p 00149000 08:08 694644     /lib/tls/i686/cmov/libc-2.7.so
b7f0a000-b7f0c000 rw-p 0014a000 08:08 694644     /lib/tls/i686/cmov/libc-2.7.so
b7f0c000-b7f0f000 rw-p b7f0c000 00:00 0 
b7f0f000-b7f32000 r-xp 00000000 08:08 694648     /lib/tls/i686/cmov/libm-2.7.so
b7f32000-b7f34000 rw-p 00023000 08:08 694648     /lib/tls/i686/cmov/libm-2.7.so
b7f3a000-b7f44000 r-xp 00000000 08:08 677855     /lib/libgcc_s.so.1
b7f44000-b7f45000 rw-p 0000a000 08:08 677855     /lib/libgcc_s.so.1
b7f45000-b7f49000 rw-p b7f45000 00:00 0 
b7f49000-b7f4a000 r-xp b7f49000 00:00 0          [vdso]
b7f4a000-b7f64000 r-xp 00000000 08:08 678556     /lib/ld-2.7.so
b7f64000-b7f66000 rw-p 00019000 08:08 678556     /lib/ld-2.7.so
bfa26000-bfa3b000 rw-p bffeb000 00:00 0          [stack]

但是,如果我使用 gcc -Wall -O2 -g -fno-stack-protector a.c -o a.out 并执行 a.out -x -n 111<find_test ,一切都正常。我找不到原因。有人可以帮忙吗?


抱歉,代码有点多,每次发布时总是提示我“您的帖子没有足够的上下文来解释代码部分,请更清楚地解释您的情境。” - jiych.guru
+1 @rld 截图上的字体太小了,我似乎找不到查看更大分辨率截图的方法...为什么要这么麻烦呢?编辑:如果代码不相关,您无需粘贴完整代码,重点是它失败的地方。(即您调用getline的位置...) - user1551592
为什么你在使用 MAX_LENGTH 时写成了 while(getline(line, MAX_LENGTH) > 0){,但是你在栈上声明了 char line[MAX_LINES]?请注意 MAX_LINES - user1551592
关于堆栈保护器:它的目的是使堆栈溢出难以忽略,也就是说,错误仍然存在,但如果没有它,你只能依靠纯运气来察觉到它。 - loreb
2个回答

5
你似乎混淆了 MAX_LINESMAX_LENGTH。看起来你为前者分配了空间,但你读取的是后者。
int find_test(int argc, char *argv[])
{
    char line[MAX_LINES];            <-------------
    int c...

    ....

    while(getline(line, MAX_LENGTH) > 0){  <-------

顺便问一下,为什么不用fgets()而使用getline()

更新

但是,如果我使用gcc -Wall -O2 -g -fno-stack-protector a.c -o a.out,并执行a.out -x -n 111 < find_test,一切都很好。

不是的。绝对不是一切都很好。你仍然会覆盖内存区域;现在覆盖可能是“基本上无害”的,且在这个特定平台上,但它仍然有潜在致命风险。在另一种情况下,除非有一些保护措施(幸运的是,现在经常采取--但你不能指望运气!),否则相同的错误可能会让远程攻击者控制您的计算机。如果您尝试更长的行,很有可能没有堆栈保护程序的“OK”程序将再次段错误(或者对于更复杂的程序,返回不正确的结果甚至导致系统损坏)。


2
当你的行达到MAX_LENGTH时,你的strstr可能会扫描超出缓冲区,因为缓冲区没有一个'\0'字符。
编辑:我想再提出另一个观点。这是有争议的,因为两种可能性都有争论。在我看来,最好使用无符号的缓冲区索引。这将强制处理循环的不同方式,这样更容易做到正确。此外,当使用unsigned索引数组时,如果下溢,则更有可能出现segmentation fault,而不是使用带符号的整数。 我们最近在我们的项目中遇到了这种情况(该系统自1998年以来一直在生产中),我将代码中的一个变量改为size_t并捕获了2个从一开始就被忽略的下溢错误。
编辑2:我想再提出另一个风格上的观点。避免在循环表达式中使用后置(增|减)运算符,它们往往会导致微妙的错误(如上面所述)。前置(增|减)运算符不是问题。后置(增|减)运算符成为问题的原因是,当你在脑海中解析表达式的条件(继续或退出)时,它立即无效,即在表达式的末尾,你用于查看结果的值不再存在。也许只有我这样认为,在我编写C程序的25年中,我犯了很多后置(增|减)运算符的错误,而这些错误总是通过前置(增|减)或将增量提取出布尔表达式来纠正的。

哎呀,我没看到Iserni的回答。我不会删除我的回复,因为它涉及另一个问题。 - Patrick Schlüter
我之前没有看到你报告的问题,这可能会导致不同的崩溃。 +1 证明了 Linus 定律。 - LSerni
哪一个是Linus法则?你有参考资料吗?我很好奇,也喜欢他的见解。 - Patrick Schlüter
“足够多的眼睛,所有的错误都变得浅显易见。”这是埃里克·S·雷蒙德(Eric S. Raymond)在《大教堂与集市》中提到的,但他称其为“林纳斯之法”。 - LSerni
@tristopia,感谢您的分享。我不理解EDIT2:为什么后置递增/递减比前置递增/递减更好?您能写一个示例代码吗?谢谢。 - jiych.guru
这一切都关乎于代码阅读。在阅读代码时,我倾向于在脑海中评估表达式。使用 pre-(dec|inc) 时,计算的值是用于布尔表达式的值,并且是循环体中变量包含的值。使用 post-(inc|dec) 时,表达式中使用的值和循环体中存储在变量中的值不同。在我的看法中,这在心理上有点更加复杂。当然,在一个简单的例子中,这并不是什么大问题,但是当你像我一样阅读大量的代码时,它更容易出错。 - Patrick Schlüter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接