重複釋放(double-free)
把一本圖書館的書還一次沒問題。把同一本書還第二次會讓館員困惑——紀錄現在說有一本書正在被歸還,而它早已上架、甚至可能已借給別人。重複釋放就是對同一塊區塊呼叫了兩次 free()。第一次釋放是正確的;第二次是錯誤,因為那塊區塊已不再屬於你、而且可能早已被重用。
精確地說:free(p) 之後稍晚又一次 free(p),而中間沒有任何配置給 p 一塊新區塊,這就是重複釋放,且是未定義行為。為何危險:記憶體配置器維護著內部記帳——記錄哪些區塊空閒、它們如何串接的小型標頭與自由串列連結。第一次 free 更新該記帳、把區塊標為可用。第二次 free 試圖對一個配置器已認為空閒的區塊再做一次,破壞那些內部結構。從此什麼都可能發生:稍後的某次 malloc 可能回傳一塊在別處仍在使用的區塊,兩個指標最終別名到同一塊記憶體,程式在離原錯誤很遠的地方出問題。攻擊者刻意觸發重複釋放來破壞配置器、奪取程式控制權,所以這是嚴重的安全瑕疵,不只是穩定性錯誤。
重複釋放通常源自所有權不清:程式中兩個部分都認為自己負責同一塊區塊,於是都去釋放它;或某條錯誤處理路徑釋放了一塊區塊,稍後的清理又再釋放一次。標準的防禦與釋放後使用相同:釋放後立刻把指標設為 NULL(由於 free(NULL) 保證什麼都不做,第二次釋放就變得無害),並為每筆配置保持單一清楚的擁有者。AddressSanitizer 會回報重複釋放,並附上兩次 free 呼叫的確切堆疊追蹤。
char *p = malloc(8); free(p); free(p); /* 重複釋放:未定義行為,破壞配置器 */ /* 拆解:free(p); p = NULL; free(p); 是無害的,因為 free(NULL) 不做事 */
對仍有效之指標的第二次 free 會破壞配置器記帳;把指標設為 NULL 能讓再次釋放成為安全的空動作。
free(NULL) 被明確定義為什麼都不做,但對非 NULL 指標釋放兩次是未定義行為——只有在你把指標設為 NULL 之後才有這份安全。