除了測試當(dāng)前指針長度,否則一般不會在pointer上使用sizeof。
正確:
size_t pointer_length = sizeof(void*);
可能錯誤:
size_t structure_length = sizeof(Foo*);
可能是:
size_t structure_length = sizeof(Foo);
關(guān)聯(lián)漏洞:
中風(fēng)險-邏輯漏洞
錯誤:
int a[3];
...;
if (a > 0)
...;
該判斷永遠(yuǎn)為真,等價于:
int a[3];
...;
if (&a[0])
...;
可能是:
int a[3];
...;
if(a[0] > 0)
...;
開啟足夠的編譯器警告(GCC 中為 -Waddress
,并已包含在 -Wall
中),并設(shè)置為錯誤,可以在編譯期間發(fā)現(xiàn)該問題。
關(guān)聯(lián)漏洞:
中風(fēng)險-邏輯漏洞
特殊情況需要特殊對待(比如開發(fā)硬件固件時可能需要寫死)
但是如果是系統(tǒng)驅(qū)動開發(fā)之類的,寫死可能會導(dǎo)致后續(xù)的問題。
關(guān)聯(lián)漏洞:
高風(fēng)險-內(nèi)存破壞
錯誤:
*foo = 100;
if (!foo) {
ERROR("foobar");
}
正確:
if (!foo) {
ERROR("foobar");
}
*foo = 100;
錯誤:
void Foo(char* bar) {
*bar = '\0';
}
正確:
void Foo(char* bar) {
if(bar)
*bar = '\0';
else
...;
}
關(guān)聯(lián)漏洞:
低風(fēng)險-拒絕服務(wù)
在對指針進(jìn)行釋放后,需要將該指針設(shè)置為NULL,以防止后續(xù)free指針的誤用,導(dǎo)致UAF等其他內(nèi)存破壞問題。尤其是在結(jié)構(gòu)體、類里面存儲的原始指針。
錯誤:
void foo() {
char* p = (char*)malloc(100);
memcpy(p, "hello", 6);
// 此時p所指向的內(nèi)存已被釋放,但是p所指的地址仍然不變
printf("%s\n", p);
free(p);
// 未設(shè)置為NULL,可能導(dǎo)致UAF等內(nèi)存錯誤
if (p != NULL) { // 沒有起到防錯作用
printf("%s\n", p); // 錯誤使用已經(jīng)釋放的內(nèi)存
}
}
正確:
void foo() {
char* p = (char*)malloc(100);
memcpy(p, "hello", 6);
// 此時p所指向的內(nèi)存已被釋放,但是p所指的地址仍然不變
printf("%s\n", p);
free(p);
//釋放后將指針賦值為空
p = NULL;
if (p != NULL) { // 沒有起到防錯作用
printf("%s\n", p); // 錯誤使用已經(jīng)釋放的內(nèi)存
}
}
對于 C++ 代碼,使用 string、vector、智能指針等代替原始內(nèi)存管理機(jī)制,可以大量減少這類錯誤。
關(guān)聯(lián)漏洞:
高風(fēng)險-內(nèi)存破壞
在對指針、對象或變量進(jìn)行操作時,需要能夠正確判斷所操作對象的原始類型。如果使用了與原始類型不兼容的類型進(jìn)行訪問,則存在安全隱患。
錯誤:
const int NAME_TYPE = 1;
const int ID_TYPE = 2;
// 該類型根據(jù) msg_type 進(jìn)行區(qū)分,如果在對MessageBuffer進(jìn)行操作時沒有判斷目標(biāo)對象,則存在類型混淆
struct MessageBuffer {
int msg_type;
union {
const char *name;
int name_id;
};
};
void Foo() {
struct MessageBuffer buf;
const char* default_message = "Hello World";
// 設(shè)置該消息類型為 NAME_TYPE,因此buf預(yù)期的類型為 msg_type + name
buf.msg_type = NAME_TYPE;
buf.name = default_message;
printf("Pointer of buf.name is %p\n", buf.name);
// 沒有判斷目標(biāo)消息類型是否為ID_TYPE,直接修改nameID,導(dǎo)致類型混淆
buf.name_id = user_controlled_value;
if (buf.msg_type == NAME_TYPE) {
printf("Pointer of buf.name is now %p\n", buf.name);
// 以NAME_TYPE作為類型操作,可能導(dǎo)致非法內(nèi)存讀寫
printf("Message: %s\n", buf.name);
} else {
printf("Message: Use ID %d\n", buf.name_id);
}
}
正確(判斷操作的目標(biāo)是否是預(yù)期類型):
void Foo() {
struct MessageBuffer buf;
const char* default_message = "Hello World";
// 設(shè)置該消息類型為 NAME_TYPE,因此buf預(yù)期的類型為 msg_type + name
buf.msg_type = NAME_TYPE;
buf.name = default_msessage;
printf("Pointer of buf.name is %p\n", buf.name);
// 判斷目標(biāo)消息類型是否為 ID_TYPE,不是預(yù)期類型則做對應(yīng)操作
if (buf.msg_type == ID_TYPE)
buf.name_id = user_controlled_value;
if (buf.msg_type == NAME_TYPE) {
printf("Pointer of buf.name is now %p\n", buf.name);
printf("Message: %s\n", buf.name);
} else {
printf("Message: Use ID %d\n", buf.name_id);
}
}
關(guān)聯(lián)漏洞:
高風(fēng)險-內(nèi)存破壞
在使用智能指針時,防止其和原始指針的混用,否則可能導(dǎo)致對象生命周期問題,例如 UAF 等安全風(fēng)險。
錯誤例子:
class Foo {
public:
explicit Foo(int num) { data_ = num; };
void Function() { printf("Obj is %p, data = %d\n", this, data_); };
private:
int data_;
};
std::unique_ptr<Foo> fool_u_ptr = nullptr;
Foo* pfool_raw_ptr = nullptr;
void Risk() {
fool_u_ptr = make_unique<Foo>(1);
// 從獨(dú)占智能指針中獲取原始指針,<Foo>(1)
pfool_raw_ptr = fool_u_ptr.get();
// 調(diào)用<Foo>(1)的函數(shù)
pfool_raw_ptr->Function();
// 獨(dú)占智能指針重新賦值后會釋放內(nèi)存
fool_u_ptr = make_unique<Foo>(2);
// 通過原始指針操作會導(dǎo)致UAF,pfool_raw_ptr指向的對象已經(jīng)釋放
pfool_raw_ptr->Function();
}
// 輸出:
// Obj is 0000027943087B80, data = 1
// Obj is 0000027943087B80, data = -572662307
正確,通過智能指針操作:
void Safe() {
fool_u_ptr = make_unique<Foo>(1);
// 調(diào)用<Foo>(1)的函數(shù)
fool_u_ptr->function();
fool_u_ptr = make_unique<Foo>(2);
// 調(diào)用<Foo>(2)的函數(shù)
fool_u_ptr->function();
}
// 輸出:
// Obj is 000002C7BB550830, data = 1
// Obj is 000002C7BB557AF0, data = 2
關(guān)聯(lián)漏洞:
高風(fēng)險-內(nèi)存破壞
更多建議: