C/C++ 不推薦的編程習(xí)慣

2021-05-28 10:07 更新

2.1 【必須】switch中應(yīng)有default

switch 中應(yīng)該有 default,以處理各種預(yù)期外的情況。這可以確保 switch 接受用戶輸入,或者后期在其他開(kāi)發(fā)者修改函數(shù)后確保 switch 仍可以覆蓋到所有情況,并確保邏輯正常運(yùn)行。

  1. // Bad
  2. int Foo(int bar) {
  3. switch (bar & 7) {
  4. case 0:
  5. return Foobar(bar);
  6. break;
  7. case 1:
  8. return Foobar(bar * 2);
  9. break;
  10. }
  11. }

例如上述代碼 switch 的取值可能從 0~7,所以應(yīng)當(dāng)有 default:

  1. // Good
  2. int Foo(int bar) {
  3. switch (bar & 7) {
  4. case 0:
  5. return Foobar(bar);
  6. break;
  7. case 1:
  8. return Foobar(bar * 2);
  9. break;
  10. default:
  11. return -1;
  12. }
  13. }

關(guān)聯(lián)漏洞:

  • 中風(fēng)險(xiǎn)-邏輯漏洞

  • 中風(fēng)險(xiǎn)-內(nèi)存泄漏

2.2 【必須】不應(yīng)當(dāng)在 Debug 或錯(cuò)誤信息中提供過(guò)多內(nèi)容

包含過(guò)多信息的 Debug 消息不應(yīng)當(dāng)被用戶獲取到。Debug 信息可能會(huì)泄露一些值,例如內(nèi)存數(shù)據(jù)、內(nèi)存地址等內(nèi)容,這些內(nèi)容可以幫助攻擊者在初步控制程序后,更容易地攻擊程序。

  1. // Bad
  2. int Foo(int* bar) {
  3. if (bar && *bar == 5) {
  4. OutputDebugInfoToUser("Wrong value for bar %p = %d\n", bar, *bar);
  5. }
  6. }

而應(yīng)該:

  1. // Good
  2. int foo(int* bar) {
  3. #ifdef DEBUG
  4. if (bar && *bar == 5) {
  5. OutputDebugInfo("Wrong value for bar.\n", bar, *bar);
  6. }
  7. #endif
  8. }

關(guān)聯(lián)漏洞:

  • 中風(fēng)險(xiǎn)-信息泄漏

2.3 【必須】不應(yīng)該在客戶端代碼中硬編碼對(duì)稱加密秘鑰

不應(yīng)該在客戶端代碼中硬編碼對(duì)稱加密秘鑰。例如:不應(yīng)在客戶端代碼使用硬編碼的 AES/ChaCha20-Poly1305/SM1 密鑰,使用固定密鑰的程序基本和沒(méi)有加密一樣。

如果業(yè)務(wù)需求是認(rèn)證加密數(shù)據(jù)傳輸,應(yīng)優(yōu)先考慮直接用 HTTPS 協(xié)議。

如果是其它業(yè)務(wù)需求,可考慮由服務(wù)器端生成對(duì)稱秘鑰,客戶端通過(guò) HTTPS 等認(rèn)證加密通信渠道從服務(wù)器拉取。

或者根據(jù)用戶特定的會(huì)話信息,比如登錄認(rèn)證過(guò)程可以根據(jù)用戶名用戶密碼業(yè)務(wù)上下文等信息,使用 HKDF 等算法衍生出對(duì)稱秘鑰。

又或者使用 RSA/ECDSA + ECDHE 等進(jìn)行認(rèn)證秘鑰協(xié)商,生成對(duì)稱秘鑰。

  1. // Bad
  2. char g_aes_key[] = {...};
  3. void Foo() {
  4. ....
  5. AES_func(g_aes_key, input_data, output_data);
  6. }

可以考慮在線為每個(gè)用戶獲取不同的密鑰:

  1. // Good
  2. char* g_aes_key;
  3. void Foo() {
  4. ....
  5. AES_encrypt(g_aes_key, input_data, output_data);
  6. }
  7. void Init() {
  8. g_aes_key = get_key_from_https(user_id, ...);
  9. }

關(guān)聯(lián)漏洞:

  • 中風(fēng)險(xiǎn)-信息泄露

2.4 【必須】返回棧上變量的地址

函數(shù)不可以返回棧上的變量的地址,其內(nèi)容在函數(shù)返回后就會(huì)失效。

  1. // Bad
  2. char* Foo(char* sz, int len){
  3. char a[300] = {0};
  4. if (len > 100) {
  5. memcpy(a, sz, 100);
  6. }
  7. a[len] = '\0';
  8. return a; // WRONG
  9. }

而應(yīng)當(dāng)使用堆來(lái)傳遞非簡(jiǎn)單類型變量。

  1. // Good
  2. char* Foo(char* sz, int len) {
  3. char* a = new char[300];
  4. if (len > 100) {
  5. memcpy(a, sz, 100);
  6. }
  7. a[len] = '\0';
  8. return a; // OK
  9. }

對(duì)于 C++ 程序來(lái)說(shuō),強(qiáng)烈建議返回 stringvector 等類型,會(huì)讓代碼更加簡(jiǎn)單和安全。

關(guān)聯(lián)漏洞:

  • 高風(fēng)險(xiǎn)-內(nèi)存破壞

2.5 【必須】有邏輯聯(lián)系的數(shù)組必須仔細(xì)檢查

例如下列程序?qū)⒆址D(zhuǎn)換為 week day,但是兩個(gè)數(shù)組并不一樣長(zhǎng),導(dǎo)致程序可能會(huì)越界讀一個(gè) int。

  1. // Bad
  2. int nWeekdays[] = {1, 2, 3, 4, 5, 6};
  3. const char* sWeekdays[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
  4. for (int x = 0; x < ARRAY_SIZE(sWeekdays); x++) {
  5. if (strcmp(sWeekdays[x], input) == 0)
  6. return nWeekdays[x];
  7. }

應(yīng)當(dāng)確保有關(guān)聯(lián)的 nWeekdays 和 sWeekdays 數(shù)據(jù)統(tǒng)一。

  1. // Good
  2. const int nWeekdays[] = {1, 2, 3, 4, 5, 6, 7};
  3. const char* sWeekdays[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
  4. assert(ARRAY_SIZE(nWeekdays) == ARRAY_SIZE(sWeekdays));
  5. for (int x = 0; x < ARRAY_SIZE(sWeekdays); x++) {
  6. if (strcmp(sWeekdays[x], input) == 0) {
  7. return nWeekdays[x];
  8. }
  9. }

關(guān)聯(lián)漏洞:

  • 高風(fēng)險(xiǎn)-內(nèi)存破壞

2.6 【必須】避免函數(shù)的聲明和實(shí)現(xiàn)不同

在頭文件、源代碼、文檔中列舉的函數(shù)聲明應(yīng)當(dāng)一致,不應(yīng)當(dāng)出現(xiàn)定義內(nèi)容錯(cuò)位的情況。

錯(cuò)誤:

foo.h

  1. int CalcArea(int width, int height);

foo.cc

  1. int CalcArea(int height, int width) { // Different from foo.h
  2. if (height > real_height) {
  3. return 0;
  4. }
  5. return height * width;
  6. }

正確: foo.h

  1. int CalcArea(int height, int width);

foo.cc

  1. int CalcArea (int height, int width) {
  2. if (height > real_height) {
  3. return 0;
  4. }
  5. return height * width;
  6. }

關(guān)聯(lián)漏洞:

  • 中風(fēng)險(xiǎn)-邏輯問(wèn)題

2.7 【必須】檢查復(fù)制粘貼的重復(fù)代碼(相同代碼通常代表錯(cuò)誤)

當(dāng)開(kāi)發(fā)中遇到較長(zhǎng)的句子時(shí),如果你選擇了復(fù)制粘貼語(yǔ)句,請(qǐng)記得檢查每一行代碼,不要出現(xiàn)上下兩句一模一樣的情況,這通常代表代碼哪里出現(xiàn)了錯(cuò)誤:

  1. // Bad
  2. void Foobar(SomeStruct& foobase, SomeStruct& foo1, SomeStruct& foo2) {
  3. foo1.bar = (foo1.bar & 0xffff) | (foobase.base & 0xffff0000);
  4. foo1.bar = (foo1.bar & 0xffff) | (foobase.base & 0xffff0000);
  5. }

如上例,通??赡苁牵?/p>

  1. // Good
  2. void Foobar(SomeStruct& foobase, SomeStruct& foo1, SomeStruct& foo2) {
  3. foo1.bar = (foo1.bar & 0xffff) | (foobase.base & 0xffff0000);
  4. foo2.bar = (foo2.bar & 0xffff) | (foobase.base & 0xffff0000);
  5. }

最好是把重復(fù)的代碼片段提取成函數(shù),如果函數(shù)比較短,可以考慮定義為 inline 函數(shù),在減少冗余的同時(shí)也能確保不會(huì)影響性能。

關(guān)聯(lián)漏洞:

  • 中風(fēng)險(xiǎn)-邏輯問(wèn)題

2.8 【必須】左右一致的重復(fù)判斷/永遠(yuǎn)為真或假的判斷(通常代表錯(cuò)誤)

這通常是由于自動(dòng)完成或例如 Visual Assistant X 之類的補(bǔ)全插件導(dǎo)致的問(wèn)題。

  1. // Bad
  2. if (foo1.bar == foo1.bar) {
  3. }

可能是:

  1. // Good
  2. if (foo1.bar == foo2.bar) {
  3. }

關(guān)聯(lián)漏洞:

  • 中風(fēng)險(xiǎn)-邏輯問(wèn)題

2.9 【必須】函數(shù)每個(gè)分支都應(yīng)有返回值

函數(shù)的每個(gè)分支都應(yīng)該有返回值,否則如果函數(shù)走到無(wú)返回值的分支,其結(jié)果是未知的。

  1. // Bad
  2. int Foo(int bar) {
  3. if (bar > 100) {
  4. return 10;
  5. } else if (bar > 10) {
  6. return 1;
  7. }
  8. }

上述例子當(dāng) bar<10 時(shí),其結(jié)果是未知的值。

  1. // Good
  2. int Foo(int bar) {
  3. if (bar > 100) {
  4. return 10;
  5. } else if (bar > 10) {
  6. return 1;
  7. }
  8. return 0;
  9. }

開(kāi)啟適當(dāng)級(jí)別的警告(GCC 中為 -Wreturn-type 并已包含在 -Wall 中)并設(shè)置為錯(cuò)誤,可以在編譯階段發(fā)現(xiàn)這類錯(cuò)誤。

關(guān)聯(lián)漏洞:

  • 中風(fēng)險(xiǎn)-邏輯問(wèn)題

  • 中風(fēng)險(xiǎn)-信息泄漏

2.10 【必須】不得使用棧上未初始化的變量

在棧上聲明的變量要注意是否在使用它之前已經(jīng)初始化了

  1. // Bad
  2. void Foo() {
  3. int foo;
  4. if (Bar()) {
  5. foo = 1;
  6. }
  7. Foobar(foo); // foo可能沒(méi)有初始化
  8. }

最好在聲明的時(shí)候就立刻初始化變量,或者確保每個(gè)分支都初始化它。開(kāi)啟相應(yīng)的編譯器警告(GCC 中為 -Wuninitialized),并把設(shè)置為錯(cuò)誤級(jí)別,可以在編譯階段發(fā)現(xiàn)這類錯(cuò)誤。

  1. // Good
  2. void Foo() {
  3. int foo = 0;
  4. if (Bar()) {
  5. foo = 1;
  6. }
  7. Foobar(foo);
  8. }

關(guān)聯(lián)漏洞:

  • 中風(fēng)險(xiǎn)-邏輯問(wèn)題

  • 中風(fēng)險(xiǎn)-信息泄漏

2.11 【建議】不得直接使用剛分配的未初始化的內(nèi)存(如realloc)

一些剛申請(qǐng)的內(nèi)存通常是直接從堆上分配的,可能包含有舊數(shù)據(jù)的,直接使用它們而不初始化,可能會(huì)導(dǎo)致安全問(wèn)題。例如,CVE-2019-13751。應(yīng)確保初始化變量,或者確保未初始化的值不會(huì)泄露給用戶。

  1. // Bad
  2. char* Foo() {
  3. char* a = new char[100];
  4. a[99] = '\0';
  5. memcpy(a, "char", 4);
  6. return a;
  7. }

  1. // Good
  2. char* Foo() {
  3. char* a = new char[100];
  4. memcpy(a, "char", 4);
  5. a[4] = '\0';
  6. return a;
  7. }

在 C++ 中,再次強(qiáng)烈推薦用 string、vector 代替手動(dòng)內(nèi)存分配。

關(guān)聯(lián)漏洞:

  • 中風(fēng)險(xiǎn)-邏輯問(wèn)題

  • 中風(fēng)險(xiǎn)-信息泄漏

2.12 【必須】校驗(yàn)內(nèi)存相關(guān)函數(shù)的返回值

與內(nèi)存分配相關(guān)的函數(shù)需要檢查其返回值是否正確,以防導(dǎo)致程序崩潰或邏輯錯(cuò)誤。

  1. // Bad
  2. void Foo() {
  3. char* bar = mmap(0, 0x800000, .....);
  4. *(bar + 0x400000) = '\x88'; // Wrong
  5. }

如上例mmap如果失敗,bar的值將是0xffffffff (ffffffff),第二行將會(huì)往0x3ffffff寫入字符,導(dǎo)致越界寫。

  1. // Good
  2. void Foo() {
  3. char* bar = mmap(0, 0x800000, .....);
  4. if(bar == MAP_FAILED) {
  5. return;
  6. }
  7. *(bar + 0x400000) = '\x88';
  8. }

關(guān)聯(lián)漏洞:

  • 中風(fēng)險(xiǎn)-邏輯問(wèn)題

  • 高風(fēng)險(xiǎn)-越界操作

2.13 【必須】不要在if里面賦值

if里賦值通常代表代碼存在錯(cuò)誤。

  1. // Bad
  2. void Foo() {
  3. if (bar = 0x99) ...
  4. }

通常應(yīng)該是:

  1. // Good
  2. void Foo() {
  3. if (bar == 0x99) ...
  4. }

建議在構(gòu)建系統(tǒng)中開(kāi)啟足夠的編譯器警告(GCC 中為 -Wparentheses 并已包含在 -Wall 中),并把該警告設(shè)置為錯(cuò)誤。

關(guān)聯(lián)漏洞:

  • 中風(fēng)險(xiǎn)-邏輯問(wèn)題

2.14 【建議】確認(rèn)if里面的按位操作

if里,非bool類型和非bool類型的按位操作可能代表代碼存在錯(cuò)誤。

  1. // Bad
  2. void Foo() {
  3. int bar = 0x1; // binary 01
  4. int foobar = 0x2; // binary 10
  5. if (foobar & bar) // result = 00, false
  6. ...
  7. }

上述代碼可能應(yīng)該是:

  1. // Good
  2. void foo() {
  3. int bar = 0x1;
  4. int foobar = 0x2;
  5. if (foobar && bar) // result : true
  6. ...
  7. }

關(guān)聯(lián)漏洞:

  • 中風(fēng)險(xiǎn)-邏輯問(wèn)題
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)