2010-07-20 44 views
20

所以在這裏我相信我在查看別人的代碼時發現了一個小緩衝區溢出問題。它立即使我感到不正確,並且有潛在的危險,但我承認我無法解釋這個「錯誤」的實際後果,如果有的話。這個緩衝區溢出的後果?

我寫了一個測試應用程序來演示錯誤,但發現(令我沮喪),它似乎無論溢出都正確運行。我想相信這只是偶然,但需要一些反饋來確定我的想法是否錯誤,或者如果真的存在問題,那麼在我的測試應用程序中就不會顯示它的頭。

問題代碼(我覺得是,反正):現在

char* buffer = new char[strlen("This string is 27 char long" + 1)]; 
sprintf(buffer, "This string is 27 char long"); 

,之所以這樣站出來給我,我想將其標記爲可能的緩衝區溢出是因爲第一strlen的。由於指針算術,+ 1的'錯誤'放置將導致strlen返回26而不是27(以「他的字符串是27個字符長」)的長度。我相信,sprintf,然後打印27個字符到緩衝區並導致緩衝區溢出。

這是一個正確的評估?

我編寫了一個測試應用程序來爲我正在查看的代碼演示這個人,並發現即使在調試器中字符串也能正確打印。我還試圖在堆棧之前和之後放置其他變量,以查看是否可以影響相鄰區域的內存,但仍然接收到正確的輸出。我意識到我新分配的堆內存可能不會相鄰,這可以解釋缺乏有用的溢出,但我真的很想確認他人的意見,如果這實際上是一個問題。

由於這是一個非常簡單的「問題」,如果您也可以用某種參考來支持您的答案,那將會很好。雖然我非常重視並歡迎您的意見,但我不會接受「是的」作爲最終答案。提前謝謝你。




更新:許多很好的答案有很多額外的洞察力。不幸的是,我無法接受他們。感謝您分享您的知識併成爲我的「第二意見」。我很感激幫助。

+4

由於填充/對齊,您可能不會被上面的代碼咬傷。你可以用64字符長的字符串重複你的實驗,所以分配需要65個字符嗎?在'sprintf'之前分配兩個這樣的字符串,以不同的順序填充它們。 – 2010-07-20 14:21:50

+4

這是非常討厭的代碼,需要一個原始字符串,併爲其添加+1!我會僅僅根據這個事實對代碼進行審查。 – 2010-07-20 14:24:58

+1

這就是爲什麼我們的開發人員使用盡可能多的經過充分測試的庫,因爲我們犯這樣的愚蠢錯誤! :-) @Johnson我非常肯定開發者打算把長度加1,而不是字符串本身,因此這個bug。 – corsiKa 2010-07-20 14:25:55

回答

14

您的評估是正確的。 [編輯]增加了由James Curran提到的更正。[/編輯]

可能,您的測試應用程序沒有顯示問題,因爲分配四捨五入爲4,8或16的下一個倍數(這是常見的分配粒度)。

這意味着您應該能夠使用31個字符的長字符串進行演示。

或者,使用「儀表」本機內存分析器,它可以將防護字節緊密圍繞這樣的分配。

6

您的評估是正確的,只是springf將投入28個字符在緩衝區的末端計數結束串的NUL(這就是爲什麼你需要錯位「+1」擺在首位)

請注意,根據我的經驗,如果某些東西在調試器外部失敗,但在調試器中逐步完成,則100%的時間內都會超出本地緩衝區。調試器將更多東西推入堆棧,所以重要的東西被覆蓋的可能性很小。

+0

謝謝你對'\ 0'的更正,我不確定它爲什麼會滑下我的腦海:) – KevenK 2010-07-20 14:48:19

1

是的,你是對的。分配的緩衝區太小而不能保存字符串。

由於這是在堆上分配的,所以這可能導致堆損壞。然而,這種可能性取決於在這一點之前還有堆內存管理器被使用之前其他的內存分配和釋放。有關更多信息,請參閱Heap Overflow

3

問題是你正在寫內存的某個地方,但不是在堆棧上。 因此,很難真正看到有什麼問題。 如果你想看到的損害,儘量堆

char buffer[strlen("This string is 27 char long" + 1)]; 

和寫過去,它在分配的字符串。 其他變量將被編寫,如果你真的知道二進制文件的工作方式,你還可以添加一些代碼來執行。要利用這樣的緩衝區溢出,你需要編寫你想要的數據,然後找到一種方法來「跳」到這個數據來執行。

+0

由於堆棧分配也通常與4或8字節的邊界對齊,所以這仍然不能保證*會導致問題。 – Roddy 2010-07-20 14:30:15

1

你是正確的,在這個例子中的指針算術會產生一個不正確的(較短)的長度傳遞給新的。無法進行此次崩潰的最可能原因是由於內存分配實際提供了多少緩衝空間,因此存在一些不確定性。

該庫允許提供比請求更大的緩衝區。此外,緩衝區之後的任何內容都有一個分配頭部作爲前綴,該頭部受制於機器字對齊規則。這意味着在下一個分配標題之前可能會有多達三個填充字節(取決於平臺)。

即使您重寫了下一個分配標頭(用於管理已分配的內存塊),直到下一個塊的所有者嘗試將其返回給堆,它纔會表現爲問題。

1

許多歷史性的malloc實現在分配塊之前和/或之後立即放置簿記數據。您可能會覆蓋這些數據,在這種情況下,除非您嘗試釋放內存(或者可能隨意使用下一個塊),否則您不會看到任何錯誤/崩潰。同樣,後續分配的簿記信息可能稍後會覆蓋您的字符串。

我懷疑現代的malloc實現通過使用完整性檢查數據填充分配來防止堆損壞,所以如果幸運的話,沒有什麼不好的事情會發生,或者在以後的分配/免費期間可能會收到警告消息操作。

0

正如其他人所說,你完全正確地認爲這是不好的,你沒有看到這個的原因是填充。試試valgrind就可以了,這應該明確地發現那個錯誤。

0

你真正的問題是,你寫的

char* buffer = new char[strlen("This string is 27 char long" + 1)]; 

代替

char* buffer = new char[strlen("This string is 27 char long") + 1]; 

意義上的第一個你給的strlen(),這是不是一個地址你的字符串開頭

試試這個代碼:

const char szText[] = "This string is 27 char long"; 
char* buffer = new char[strlen(szText) + 1]; 
sprintf(buffer, szText); 
+0

他已經知道了。在發佈答案之前閱讀問題。 – manneorama 2010-07-20 14:42:30

+1

我知道這個問題,這就是突出給我,並把我帶到這裏。這是在別人的代碼中,我只需要第二個意見,並且如預期的那樣,這裏的額外知識和洞察力已經非常驚人。感謝您的幫助! – KevenK 2010-07-20 14:43:16

+0

你沒有回答這個問題。他知道'+ 1'不合適。他在問題中明確表示。他想知道他的錯誤評估是否正確,因爲他無法通過測試產生不正確的行爲。 – 2010-07-20 14:43:47

1

我堆分配試了一下,變量不是在這種情況下,內存是連續的。這就是爲什麼在這種情況下很難使緩衝區溢出。

購買堆棧溢出嘗試

#include "stdio.h" 
#include "string.h" 

int main() 
{ 
    unsigned int y  = (0xFFFFFFFF); 
    char buffer[strlen("This string is 27 char long" + 1)]; 
     unsigned int x  = (0xFFFFFFFF); 
     sprintf(buffer, "This string is 27 char long"); 

     printf("X (%#x) is %#x, Y (%#x) is %#x, buffer '%s' (%#x) \n", &x, x,&y, y, buffer, buffer); 
     return 0; 
    } 

你會看到Y被損壞。

0

字符串在調試器中正常打印的原因在於,作爲sprintf的一部分,尾隨的NULL字符正在寫入內存(在這種情況下超出了您分配的緩衝區),並且在讀取字符串時存在NULL字符以按預期終止字符串。

問題是包含NULL字符的字節沒有作爲原始new的一部分進行分配,因此可能稍後用於其他分配。在這種情況下,當你之後來讀取字符串時,你可能會得到你的原始字符串附加了垃圾。

0

正確的說法。由於您將字符串的第二個字符的地址傳遞給strlen(),因此您將得到的長度減少一個字符。除此之外,主要問題是sprintf(),這是它不安全的原因之一。

即使這樣編譯並執行(也可能會崩潰)。

char* x = new char; 
    sprintf(x, "This is way longer than one character"); 
    printf("%s", x); 

爲了避免這種危險的問題,您應該使用在MSVC下GCC或sprintf_s()這個函數一樣的snprintf()或asprintf()的安全版本。

作爲參考資料,請看看這方面的The GNU C Library documentation以及MSDN的sprintf()文章的安全說明。