不止一次在網上看到一篇名為《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("\n The buffer entered is [%s]\n",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] = { '\0' };
就足夠了。更重要的是,從后面對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("\n Password cracked \n"); } else { printf("\n Incorrect passwd \n"); } 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("\n Password cracked \n"); } else { printf("\n Incorrect passwd \n"); } return 0; }
簡潔又自然。
(未完待續)