2012-08-31 220 views
6

程序中有一些固定大小的二進制緩衝區用於存儲數據。 memcpy用於將緩衝區從一個複製到另一個。由於源緩衝區可能大於目標緩衝區。我如何檢測是否存在緩衝區溢出?如何防止memcpy緩衝區溢出?

+1

檢測?你知道目標緩衝區大小?然後編寫像這樣的代碼memcpy(src,dst,sizeof(dst)) – BSen

+0

比較源緩衝區和目標緩衝區的大小,看哪個更大? – SingerOfTheFall

+1

@BSen''sizeof''只會給出一個指針的大小。 – juanchopanza

回答

8

您必須知道源緩衝區中有多少數據以及目標緩衝區中有多少可用空間。

別叫memcpy()如果沒有目標緩衝區足夠的空間,你想從源緩衝區中的數據複製。 (如果源大於目標,則必須決定是否截斷數據。)

如果您不知道,請重新編寫代碼,以便知道其中有多少空間;否則,這是不安全的。

請注意,如果源緩衝區和目標緩衝區有重疊的機會,則應該使用memmove()而不是memcpy()

在C++中,側目首先使用memcpy();這是一種C風格的操作,而不是C++。

+0

謝謝。在C++中執行內存複製的正確方法是什麼? –

+1

@MichaelD:將你的數據存儲在一個'std :: vector <>'中,並使用'vector2 = vector1'。 – MSalters

+0

如何將數據插入向量?使用push_back逐字節插入日期? –

3

你應該總是知道並檢查src和dest緩衝區大小!

void *memcpy(void *dest, const void *src, size_t n); 

n絕不應該比srcdest尺寸最大的。

0

例如,如果您有:

目的地4個字節大小

源5個字節大小

您可以確保複製,最多4個字節到目標緩衝區:

size_t getCopySize(size_t sourceSize, size_t destSize) 
{ 
    return (destSize <= sourceSize ? destSize : sourceSize); 
} 
memcpy(destination, source, getCopySize(sizeof(source),sizeof(destination))); 

根據您的應用程序,您還可以確保剩餘的數據將在稍後複製,或者如果可以忽略某些數據,則可以跳過它。

4

我怎樣才能檢測是否存在緩衝區溢出?

我認爲你有三個或四個選擇(給與否)。


的第一個選擇是爲memcpy提供一個「安全」功能。這是我在我的職權範圍內所要求的,我會定期對其進行審覈。我還要求驗證所有參數,並確定所有參數。

斷言創建自我調試代碼。我希望開發人員編寫代碼;我不希望他們浪費時間進行調試。所以我需要他們編寫調試自己的代碼。 ASSERTs也很好地記錄事情,所以他們可以吝嗇文檔。在發佈版本中,ASSERT被預編譯器宏刪除。

errno_t safe_memcpy(void* dest, size_t dsize, void* src, size_t ssize, size_t cnt) 
{ 
    ASSERT(dest != NULL); 
    ASSERT(src != NULL); 
    ASSERT(dsize != 0); 
    ASSERT(ssize != 0); 
    ASSERT(cnt != 0); 

    // What was the point of this call? 
    if(cnt == 0) 
     retrn 0; 

    if(dest == NULL || src == NULL) 
     return EINVALID; 

    if(dsize == 0 || ssize == 0) 
     return EINVALID; 

    ASSERT(dsize <= RSIZE_MAX); 
    ASSERT(ssize <= RSIZE_MAX); 
    ASSERT(cnt <= RSIZE_MAX); 

    if(dsize > RSIZE_MAX || ssize > RSIZE_MAX || cnt > RSIZE_MAX) 
     return EINVALID; 

    size_t cc = min(min(dsize, ssize), cnt); 
    memmove(dest, src, cc); 

    if(cc != cnt) 
     return ETRUNCATE; 

    return 0; 
} 

如果您safe_memcpy返回非0,再有就是像一個壞的參數或潛在的緩衝區溢出錯誤。


第二種選擇是使用C標準提供的「更安全」功能。 C通過ISO/IEC TR 24731-1, Bounds Checking Interfaces具有「更安全」的功能。在符合的平臺上,您只需撥打gets_ssprintf_s即可。它們提供一致的行爲(如始終確保字符串是NULL已終止)和一致的返回值(如成功時爲0或errno_t)。

errno_t err = memcpy_s(dest, dsize, src, cnt); 
... 

不幸的是,gcc和glibc不符合C標準。 Ulrich Drepper(glibc維護人員之一)稱爲邊界檢查接口"horribly inefficient BSD crap",他們從未被添加。


第三種選擇是使用平臺的「安全」接口(如果存在)。在Windows上,這恰好與ISO/IEC TR 24731-1, Bounds Checking Interfaces中的相同。你也有字符串安全庫。

在Apple和BSD上,您沒有memcpy的「更安全」功能。但你有更安全的字符串功能,如strlcpystrlcat和朋友。


在Linux上,您的第四個選擇是使用FORTIFY_SOURCE。 FORTIFY_SOURCE使用memcpy,strcpygets等高風險函數的「更安全」變體。編譯器在推導出目標緩衝區大小時使用更安全的變體。如果副本超出目標緩衝區大小,則該程序將調用abort()。如果編譯器無法推斷目標緩衝區大小,則不使用「更安全」的變體。

要禁用FORTIFY_SOURCE進行測試,您應該使用-U_FORTIFY_SOURCE-D_FORTIFY_SOURCE=0來編譯該程序。