不止一次在网上看到一篇名为《12个有趣的C语言问答》的博文被郑重其事地转来转去( google了一下,居然有154,000条结果,其中不乏一些知名的技术网站),感到非常滑稽。因为那明摆着是一篇垃圾文,质量低下,漏洞比比皆是。其中基本上没有多少技术营养,倒是有很多技术毒素。
这篇垃圾文被转反复载的原因可能有两个:一是标题取的好,其中有“有趣”二字,不少很傻很天真的人就以为真的很有趣;第二个原因可能是这是一篇翻译文章,原文为12 Interesting C Interview Questions and Answers,有些人潜意识里可能以为外文的东西会很有技术含量。但实际上洋文中也有垃圾,洋人中也有很多外行,正如国外也有老谭《C语言程序设计》那种门外汉写得畅销垃圾书(譬如邮电社翻译的《写给大家看的C语言书》,参见劣质代码评析——《写给大家看的C语言书(第2版)》附录B之21点程序(一) )一样。对国外的东西同样不能盲从轻信,不能根据畅销程度或转发多少更不能仅仅根据其名字来判断技术价值。
下面对这篇《12个有趣的C语言问答》垃圾文,参照其出处(因为翻译本有很多错误),简要地评析一下。希望对垃圾文的不断扩散多少能起到点遏制的作用。
0. gets() 方法
Q:以下代码有个被隐藏住的问题,你能找到它吗?int main(void) { char buff[10]; memset(buff,0,sizeof(buff)); gets(buff); printf(" The buffer entered is [%s] ",buff); return 0; }A:这个不显眼的问题就是使用了 gets() 方法。此方法接受一个string类型参数,但是却没有检测此数值是否 有足够的空间来拷贝数据。所以这里我们一般用 fgets() 方法将来的更好。
Answer: The hidden problem with the code above is the use of the function gets(). This function accepts a string from stdin without checking the capacity of buffer in which it copies the value. This may well result in buffer overflow. The standard function fgets() is advisable to use in these cases.
评:
翻译很成问题。根据原文,是“gets()函数”,不是“gets()方法”;接受一个string参数,不是“string类型参数”(C语言中根本没有这种类型)。其余部分的翻译也有问题,但对原意影响不大,就不多说了。
Answer中说使用gets()函数可能导致buffer的overflow,这一点没什么疑问。因为这个缘故,C语言现在已经废弃了gets()函数。
问题在于代码中的
memset(buff,0,sizeof(buff));
这句,这句很无聊得很愚蠢。它的效果是在buff中填充0,但其实根本用不着这样调用函数来实现,只需要简单地
char buff[10] = { ' ' };
就足够了。更重要的是,从后面对buff的使用来看,根本没必要在buff中填充0。
1,strcpy() 方法
Q:
密码防护是很基本的功能,看看能否搞定下面这段代码#include<stdio.h> int main(int argc, char *argv[]) { int flag = 0; char passwd[10]; memset(passwd,0,sizeof(passwd)); strcpy(passwd, argv[1]); if(0 == strcmp("LinuxGeek", passwd)) { flag = 1; } if(flag) { printf(" Password cracked "); } else { printf(" Incorrect passwd "); } return 0; }
评:
晕!Answer压根没翻译。那么多转来转去的人居然对此视而不见! 从这里就不难看出哪些转这篇垃圾的人究竟有没有认真看,究竟有没有自己的头脑。这也同样能够解释,为什么垃圾能传播很广,以及为什么那些说谭浩强的书发行量大就一定好的看法是无脑人的见解。
根据原文,解答是这样的:
Answer: Yes. The authentication logic in above password protector code can be compromised by exploiting the loophole of strcpy() function. This function copies the password supplied by user to the ‘passwd’ buffer without checking whether the length of password supplied can be accommodated by the ‘passwd’ buffer or not. So if a user supplies a random password of such a length that causes buffer overflow and overwrites the memory location containing the default value ’0′ of the ‘flag’ variable then even if the password matching condition fails, the check of flag being non-zero becomes true and hence the password protection is breached.
For example :
$ ./psswd aaaaaaaaaaaaa
Password cracked
So you can see that though the password supplied in the above example is not correct but still it breached the password security through buffer overflow.
To avoid these kind of problems the function strncpy() should be used.Note from author : These days the compilers internally detect the possibility of stack smashing and so they store variables on stack in such a way that stack smashing becomes very difficult. In my case also, the gcc does this by default so I had to use the the compile option ‘-fno-stack-protector’ to reproduce the above scenario.
不翻译了。这个解答的意思就是通过输入较长的argv[1],借助数组越界来改变flag,实现“break”这个程序的目的(Can you break it without knowing the password)。所以应该使用strncpy()。
这种“break”多少有点歪门邪道的意味,而且作者也提到了,有些编译器可以防止这种情况,所以这种方法其实意义不大。
这里想说的依然是代码风格的问题——那个flag极其丑陋,不仅丑陋,没有必要存在,还进行了不必要的初始化。那个
同样画蛇添足。
甚至连passwd这个数组都没必要,只要直接比较"LinuxGeek"和argv[1]指向的字符串就可以了。代码可以这样写:
#include<stdio.h> int main(int argc, char *argv[]) { //char passwd[10]; //strncpy(passwd, argv[1], 10); if( strcmp("LinuxGeek", argv[1] ) == 0 ) //if( strcmp("LinuxGeek", passwd) == 0 ) { printf(" Password cracked "); } else { printf(" Incorrect passwd "); } return 0; }
简洁又自然。
(未完待续)