C/C++ 指針操作

2021-05-28 10:22 更新

8.1 【建議】檢查在pointer上使用sizeof

除了測(cè)試當(dāng)前指針長(zhǎng)度,否則一般不會(huì)在pointer上使用sizeof。

正確:

  1. size_t pointer_length = sizeof(void*);

可能錯(cuò)誤:

  1. size_t structure_length = sizeof(Foo*);

可能是:

  1. size_t structure_length = sizeof(Foo);

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

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

8.2 【必須】檢查直接將數(shù)組和0比較的代碼

錯(cuò)誤:

  1. int a[3];
  2. ...;
  3. if (a > 0)
  4. ...;

該判斷永遠(yuǎn)為真,等價(jià)于:

  1. int a[3];
  2. ...;
  3. if (&a[0])
  4. ...;

可能是:

  1. int a[3];
  2. ...;
  3. if(a[0] > 0)
  4. ...;

開(kāi)啟足夠的編譯器警告(GCC 中為 -Waddress,并已包含在 -Wall 中),并設(shè)置為錯(cuò)誤,可以在編譯期間發(fā)現(xiàn)該問(wèn)題。

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

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

8.3 【必須】不應(yīng)當(dāng)向指針賦予寫(xiě)死的地址

特殊情況需要特殊對(duì)待(比如開(kāi)發(fā)硬件固件時(shí)可能需要寫(xiě)死)

但是如果是系統(tǒng)驅(qū)動(dòng)開(kāi)發(fā)之類的,寫(xiě)死可能會(huì)導(dǎo)致后續(xù)的問(wèn)題。

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

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

8.4 【必須】檢查空指針

錯(cuò)誤:

  1. *foo = 100;
  2. if (!foo) {
  3. ERROR("foobar");
  4. }

正確:

  1. if (!foo) {
  2. ERROR("foobar");
  3. }
  4. *foo = 100;

錯(cuò)誤:

  1. void Foo(char* bar) {
  2. *bar = '\0';
  3. }

正確:

  1. void Foo(char* bar) {
  2. if(bar)
  3. *bar = '\0';
  4. else
  5. ...;
  6. }

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

  • 低風(fēng)險(xiǎn)-拒絕服務(wù)

8.5 【必須】釋放完后置空指針

在對(duì)指針進(jìn)行釋放后,需要將該指針設(shè)置為NULL,以防止后續(xù)free指針的誤用,導(dǎo)致UAF等其他內(nèi)存破壞問(wèn)題。尤其是在結(jié)構(gòu)體、類里面存儲(chǔ)的原始指針。

錯(cuò)誤:

  1. void foo() {
  2. char* p = (char*)malloc(100);
  3. memcpy(p, "hello", 6);
  4. // 此時(shí)p所指向的內(nèi)存已被釋放,但是p所指的地址仍然不變
  5. printf("%s\n", p);
  6. free(p);
  7. // 未設(shè)置為NULL,可能導(dǎo)致UAF等內(nèi)存錯(cuò)誤
  8. if (p != NULL) { // 沒(méi)有起到防錯(cuò)作用
  9. printf("%s\n", p); // 錯(cuò)誤使用已經(jīng)釋放的內(nèi)存
  10. }
  11. }

正確:

  1. void foo() {
  2. char* p = (char*)malloc(100);
  3. memcpy(p, "hello", 6);
  4. // 此時(shí)p所指向的內(nèi)存已被釋放,但是p所指的地址仍然不變
  5. printf("%s\n", p);
  6. free(p);
  7. //釋放后將指針賦值為空
  8. p = NULL;
  9. if (p != NULL) { // 沒(méi)有起到防錯(cuò)作用
  10. printf("%s\n", p); // 錯(cuò)誤使用已經(jīng)釋放的內(nèi)存
  11. }
  12. }

對(duì)于 C++ 代碼,使用 string、vector、智能指針等代替原始內(nèi)存管理機(jī)制,可以大量減少這類錯(cuò)誤。

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

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

8.6 【必須】防止錯(cuò)誤的類型轉(zhuǎn)換(type confusion)

在對(duì)指針、對(duì)象或變量進(jìn)行操作時(shí),需要能夠正確判斷所操作對(duì)象的原始類型。如果使用了與原始類型不兼容的類型進(jìn)行訪問(wèn),則存在安全隱患。

錯(cuò)誤:

  1. const int NAME_TYPE = 1;
  2. const int ID_TYPE = 2;
  3. // 該類型根據(jù) msg_type 進(jìn)行區(qū)分,如果在對(duì)MessageBuffer進(jìn)行操作時(shí)沒(méi)有判斷目標(biāo)對(duì)象,則存在類型混淆
  4. struct MessageBuffer {
  5. int msg_type;
  6. union {
  7. const char *name;
  8. int name_id;
  9. };
  10. };
  11. void Foo() {
  12. struct MessageBuffer buf;
  13. const char* default_message = "Hello World";
  14. // 設(shè)置該消息類型為 NAME_TYPE,因此buf預(yù)期的類型為 msg_type + name
  15. buf.msg_type = NAME_TYPE;
  16. buf.name = default_message;
  17. printf("Pointer of buf.name is %p\n", buf.name);
  18. // 沒(méi)有判斷目標(biāo)消息類型是否為ID_TYPE,直接修改nameID,導(dǎo)致類型混淆
  19. buf.name_id = user_controlled_value;
  20. if (buf.msg_type == NAME_TYPE) {
  21. printf("Pointer of buf.name is now %p\n", buf.name);
  22. // 以NAME_TYPE作為類型操作,可能導(dǎo)致非法內(nèi)存讀寫(xiě)
  23. printf("Message: %s\n", buf.name);
  24. } else {
  25. printf("Message: Use ID %d\n", buf.name_id);
  26. }
  27. }

正確(判斷操作的目標(biāo)是否是預(yù)期類型):

  1. void Foo() {
  2. struct MessageBuffer buf;
  3. const char* default_message = "Hello World";
  4. // 設(shè)置該消息類型為 NAME_TYPE,因此buf預(yù)期的類型為 msg_type + name
  5. buf.msg_type = NAME_TYPE;
  6. buf.name = default_msessage;
  7. printf("Pointer of buf.name is %p\n", buf.name);
  8. // 判斷目標(biāo)消息類型是否為 ID_TYPE,不是預(yù)期類型則做對(duì)應(yīng)操作
  9. if (buf.msg_type == ID_TYPE)
  10. buf.name_id = user_controlled_value;
  11. if (buf.msg_type == NAME_TYPE) {
  12. printf("Pointer of buf.name is now %p\n", buf.name);
  13. printf("Message: %s\n", buf.name);
  14. } else {
  15. printf("Message: Use ID %d\n", buf.name_id);
  16. }
  17. }

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

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

8.7 【必須】智能指針使用安全

在使用智能指針時(shí),防止其和原始指針的混用,否則可能導(dǎo)致對(duì)象生命周期問(wèn)題,例如 UAF 等安全風(fēng)險(xiǎn)。

錯(cuò)誤例子:

  1. class Foo {
  2. public:
  3. explicit Foo(int num) { data_ = num; };
  4. void Function() { printf("Obj is %p, data = %d\n", this, data_); };
  5. private:
  6. int data_;
  7. };
  8. std::unique_ptr<Foo> fool_u_ptr = nullptr;
  9. Foo* pfool_raw_ptr = nullptr;
  10. void Risk() {
  11. fool_u_ptr = make_unique<Foo>(1);
  12. // 從獨(dú)占智能指針中獲取原始指針,<Foo>(1)
  13. pfool_raw_ptr = fool_u_ptr.get();
  14. // 調(diào)用<Foo>(1)的函數(shù)
  15. pfool_raw_ptr->Function();
  16. // 獨(dú)占智能指針重新賦值后會(huì)釋放內(nèi)存
  17. fool_u_ptr = make_unique<Foo>(2);
  18. // 通過(guò)原始指針操作會(huì)導(dǎo)致UAF,pfool_raw_ptr指向的對(duì)象已經(jīng)釋放
  19. pfool_raw_ptr->Function();
  20. }
  21. // 輸出:
  22. // Obj is 0000027943087B80, data = 1
  23. // Obj is 0000027943087B80, data = -572662307

正確,通過(guò)智能指針操作:

  1. void Safe() {
  2. fool_u_ptr = make_unique<Foo>(1);
  3. // 調(diào)用<Foo>(1)的函數(shù)
  4. fool_u_ptr->function();
  5. fool_u_ptr = make_unique<Foo>(2);
  6. // 調(diào)用<Foo>(2)的函數(shù)
  7. fool_u_ptr->function();
  8. }
  9. // 輸出:
  10. // Obj is 000002C7BB550830, data = 1
  11. // Obj is 000002C7BB557AF0, data = 2

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

  • 高風(fēng)險(xiǎn)-內(nèi)存破壞
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)