[英]How to compare C pointers?
最近,我寫了一些代碼來比較像這樣的指針:
if(p1+len < p2)
但是,有些工作人員說我應該這樣寫:
if(p2-p1 > len)
為了安全起見。 這里, p1和p2是char *
指針, len是整數。 我對此一無所知。是嗎?
EDIT1:當然, p1和p2在乞討時指向同一個內存對象。
EDIT2:就在一分鍾之前,我在我的代碼中找到了這個問題的bogo (約3K行),因為len
太大了, p1+len
不能存儲在4個字節的指針中,所以p1 + len <p2為真但實際上它不應該,所以我認為我們應該在某些情況下比較像這樣的指針:
if(p2 < p1 || (uint32_t)p2-p1 > (uint32_t)len)
通常,只有指針指向同一個內存對象的部分(或者超過對象末尾的一個位置)時,才能安全地比較指針。 當p1
, p1 + len
和p2
都符合此規則時,兩個if
-tests都是等效的,所以你不必擔心。 另一方面,如果只知道p1
和p2
符合這個規則,並且p1 + len
可能在結束之后太遠,那么只有if(p2-p1 > len)
是安全的。 (但我無法想象你的情況。我假設p1
指向某個內存塊的開頭,而p1 + len
指向它結束后的位置,對吧?)
他們可能一直在考慮的是整數運算:如果i1 + i2
可能會溢出,但你知道i3 - i1
不會,那么i1 + i2 < i3
可以回繞(如果它們是無符號整數)或者觸發未定義的行為(如果它們是有符號整數)或兩者(如果你的系統恰好執行有符號整數溢出的環繞),而i3 - i1 > i2
將不會有這個問題。
編輯添加:在評論中,你寫“ len
是來自buff的值,所以它可能是任何東西”。 在這種情況下,它們是完全正確的,並且p2 - p1 > len
更安全,因為p1 + len
可能無效。
“未定義的行為”適用於此處。 你不能比較兩個指針,除非它們都指向同一個對象或指向該對象結束后的第一個元素。 這是一個例子:
void func(int len)
{
char array[10];
char *p = &array[0], *q = &array[10];
if (p + len <= q)
puts("OK");
}
你可能會想到這樣的功能:
// if (p + len <= q)
// if (array + 0 + len <= array + 10)
// if (0 + len <= 10)
// if (len <= 10)
void func(int len)
{
if (len <= 10)
puts("OK");
}
但是,編譯器知道ptr <= q
對於ptr
所有有效值都為真,因此它可能會優化函數:
void func(int len)
{
puts("OK");
}
快多了! 但不是你想要的。
是的,有野外存在的編譯器可以做到這一點。
這是唯一安全的版本:減去指針並比較結果,不要比較指針。
if (p - q <= 10)
從技術上講, p1
和p2
必須是指向同一個數組的指針。 如果它們不在同一個數組中,則行為未定義。
對於加法版本, len
的類型可以是任何整數類型。
對於差異版本,減法的結果是ptrdiff_t
,但任何整數類型都將被適當地轉換。
在這些約束中,您可以以任何方式編寫代碼; 兩者都不正確。 在某種程度上,這取決於你正在解決的問題。 如果問題是“數組的這兩個元素是否比len
元素分開”,則減法是合適的。 如果問題是' p2
與p1[len]
(又名p1 + len
)'相同,則添加是合適的。
實際上,在許多具有統一地址空間的機器上,你可以減去指向不同數組的指針,但你可能會得到一些有趣的效果。 例如,如果指針是某些結構類型的指針,而不是同一數組的部分,那么被視為字節地址的指針之間的差異可能不是結構大小的倍數。 這可能會導致特殊問題。 如果他們指向相同的數組,就不會有這樣的問題 - 這就是限制到位的原因。
現有的答案顯示了為什么if (p2-p1 > len)
優於if (p1+len < p2)
,但仍然存在問題 - 如果p2
碰巧指向緩沖區中的p1
,而len
是無符號類型(例如size_t
),那么p2-p1
將為負數,但會轉換為大的無符號值,以便與unsigned len進行比較,因此結果可能為true,這可能不是您想要的。
所以你可能真的需要像if (p1 <= p2 && p2 - p1 > len)
才能完全安全。
正如迪特里希已經說過的,比較不相關的指針是危險的,並且可以被認為是不確定的行為。
假設兩個指針在0到2GB的范圍內(在32位Windows系統上),減去2個指針將得到介於-2 ^ 31和+ 2 ^ 31之間的值。 這正是帶符號的32位整數的域。 因此,在這種情況下,減去兩個指針似乎是有意義的,因為結果將始終在您期望的域內。
但是,如果在您的可執行文件中啟用了LargeAddressAware標志(這是特定於Windows的,不了解Unix),那么您的應用程序將具有3GB的地址空間(當在具有/ 3G標志的32位Windows中運行時)甚至4GB(在64位Windows系統上運行時)。 如果然后開始減去兩個指針,結果可能在32位整數的域之外,並且您的比較將失敗。
我認為這是地址空間最初划分為2GB的2個相等部分的原因之一,而LargeAddressAware標志仍然是可選的。 但是,我的印象是當前的軟件(你自己的軟件和你正在使用的DLL)似乎非常安全(沒有人再減去指針,不是嗎?)並且我自己的應用程序默認啟用了LargeAddressAware標志。
表達式p1 + len < p2
編譯為p1 + sizeof(*p1)*len < p2
,並且指向類型大小的縮放可能會溢出指針:
int *p1 = (int*)0xc0ffeec0ffee0000;
int *p2 = (int*)0xc0ffeec0ffee0400;
int len = 0x4000000000000000;
if(p1 + len < p2) {
printf("pwnd!\n");
}
當len
乘以int
的大小時,它會溢出到0
因此條件被評估為if(p1 + 0 < p2)
。 這顯然是正確的,並且以下代碼以太高的長度值執行。
好的,那么p2-p1 < len
。 同樣的事情,溢出會殺死你:
char *p1 = (char*)0xa123456789012345;
char *p2 = (char*)0x0123456789012345;
int len = 1;
if(p2-p1 < len) {
printf("pwnd!\n");
}
在這種情況下,指針之間的差異被評估為p2-p1 = 0xa000000000000000
,這被解釋為負的有符號值。 因此,它比len
更小,並且執行以下代碼時len
值太低(或指針差異太大)。
我知道在存在攻擊者控制值的情況下安全的唯一方法是使用無符號算法:
if(p1 < p2 &&
((uintptr_t)p2 - (uintptr_t)p1)/sizeof(*p1) < (uintptr_t)len
) {
printf("safe\n");
}
p1 < p2
保證p2 - p1
不能產生真正的負值。 第二個子句執行p2 - p1 < len
的操作,同時強制以非UB方式使用無符號算術。 即, (uintptr_t)p2 - (uintptr_t)p1
精確地給出了較大的p2
和較小的p1
之間的字節數,無論所涉及的值如何。
當然,除非您知道需要為確定的攻擊者辯護,否則您不希望在代碼中看到此類比較。 不幸的是,這是保證安全的唯一方法,如果您依賴於問題中給出的任何一種形式,那么您就會受到攻擊。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.