作者tinlans ( )
看板C_and_CPP
標題Re: [問題] use after free 盲點請益
時間Wed Jul 4 15:22:22 2018
※ 引述《lovejomi (JOMI)》之銘言:
: 最近被問說 為什麼use after free後 你使用那塊memory的話 "有可能"會crash 或是
: 不會
: 我答不出來(這邊不討論 delete後 值被改動 foo->ptr已經變成垃圾指標 造成的
: access violation)
: 原因是 如果只是單純access 不管是read write, 我都不覺得會crash.... 以下是
: 我的測試和認知
: https://ideone.com/HKYiIQ
: int* ptr = new int;
: *ptr = 123;
: cout << "before: " << ptr << ":" << *ptr << endl; (1)
: delete ptr;
: cout << "read after free: " << ptr << ":" << *ptr << endl; (2)
: *ptr = 456; // write after free
: cout << "read after free: "<< ptr << ":" << *ptr << endl; (3)
: 我的認知
: new會透過cstdlib malloc要一塊 valid的user space address (他應該會跟OS要超過
: 我demand的)
new 不一定透過 C library 的 malloc() 去要記憶體,只是大多數實作是這樣。
: 然而我delete了 除了cstdlib裡面free做了一些事情外 , 他很可能也會跟OS講說我這
: 塊memory已經沒有要使用了(這邊不確定是不是每次 這是不是造成下面行為差異的主因)
malloc() 和 free() 通常為了減少 system call 的次數而進行一些最佳化。
要記憶體的時候會多要一點,釋放記憶體的時候不會立即歸還。
特別是早期 UNIX 是用 brk() 和 sbrk() 這種移動柵欄的方式去實作,
如果剛好 heap 尾巴有還沒 free 的空間,也不可能把柵欄退回去。
現代實作還會搭配 mmap() 跟 munmap(),所以這類問題的影響也變小了。
: 再來我連續的 對這塊記憶體做讀取 和 寫入
: 1. 如果只是讀取而已 有沒有可能 造成crash? (以我測試來說 從沒因為read 而
: crash, 但如果真的有可能會 我想知道為什麼)
當然有可能會,沒 crash 是因為 free() 沒把要來的空間歸還的關係。
已經還回去的話就是非法記憶體存取,會直接吃到 SIGSEGV。
你可以呼叫 mmap() 去拿一塊記憶體,用 munmap() 歸還以後對那個位址 read 看看。
: 2. 寫入比較不理解, 我拿到的是user space address, 雖然我delete了 但我write的時
: 候並沒有寫在其他超出user space的記憶體或是read-only的區段
: 為什麼 "有機會" 造成 SIGSEGV? 到底是誰 raise這signal?
你好像誤會了什麼,不是 address 落在 user space 就隨便你讀。
process 依然要跟 OS 申請 user space 裡的空間,沒申請到的區域你不能摸。
雖說這個講法並非通用於所有 OS,但是在有 SIGSEGV 這名詞出現的 OS 幾乎都是這樣。
你用上面提到的 mmap() -> munmap() 實驗玩一下就會知道。
會 raise 這種 signal 的當然是 OS。
至於 OS 為什麼會知道要 raise 這個 signal,那是因為它和 MMU 之間有互動。
: 3. 上面程式碼 (2) "有時候"會印出*ptr = 0,有時候是原來數值, 雖然我也只知道值不
: 可預期, 但我想問的是, 到底是誰把0 寫入進去? 是cstdlib嗎? 如果是的話 照理講要
: 每次都變0, 但顯然不是每次
: 以我測試, 如果我掛上valgrind ./a.out ==> 這行就不會變0 維持原來數值, 一旦
: 直接./a.out 就會是0
: 以上到底是誰介入了? 這件事我想說很可能是-O之類的差異 但我發現似乎不是
: debug / release build都有這種現象.
: 然而有時候這一行就直接SIGSEGV了如同2.所問的問題
照常理來推斷,應該是 operator<< 的實作部分可能有 new / delete。
然後有什麼東西剛好被配置在同樣的地方,然後被寫進了 0。
開 gdb 下 watch point 應該可以抓到精確位置。
valgrind 是用 LD_PRELOAD 的機制替換了記憶體配置相關的 functions,
所以記憶體配置行為會和正常執行時有所不同,這很正常。
: 4. 我有用signal handler 擷取這SIGSEVG 我天真的想要把這件事給吃下來
: 但我看文章說 signal handler return後會回到 原本執行到的code
: 以我測試來說 我會 無限迴圈的觸發SIGSEGV , 想問一下是不是不可能吃得下來
: 一定要 abort或是把handler設定成default 讓系統自己handle
你大概忘記一件很重要的事情。
從 signal handler 返回的話,原本被停下來的事情會被重做。
除非你能把原本非法的 address 變成合法,不然就是無限觸發 SIGSEGV。
: 5. 補個問題我嘗試在ubuntu產生core dump
: 大概做了幾件事
: echo 'core_%e.%p' | sudo tee /proc/sys/kernel/core_pattern
: core_%e.%p
: ulimit -c unlimited
: gdb a.out --core=core_a.out.1234
: 想問說~ 難道一定要user自己在電腦想辦法開core dump的能力而無法透過自己
: application 本身"暫時" 開啟嗎? 找不太到文章
man setrlimit
: 以上雖然可以用undefined behavior來解釋
: 可是如果想知道更深入的概念 該怎麼做
ls /usr/share/man/man2
把下面看得到的東西全部 man 一遍,讀完,然後 trace glibc 跟 Linux kernel。
如果這苦工跟你志向不合,你可以找做過這類事情的人問問。
OS 跟 MMU 的互動從 OS 跟計算機結構課本上可以學個大概,細節可以問專家。
: 謝謝
--
Ling-hua Tseng
Senior Engineer
Compiler Group, RD/Software Division
Andes Technology Corporation
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 122.116.164.123
※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1530688947.A.B7C.html
推 lovejomi: 想請問關於3.的回答, 我是只有main thread 然後連續執 07/04 18:57
→ lovejomi: 行,中間沒有其他程式碼,這時候還會有機會別人拿到同 07/04 18:57
→ lovejomi: 樣記憶體並且設成0嗎,如果有 那大概是誰有這能力呢? 07/04 18:57
推 lovejomi: 關於1.的回答,可是如果write crash了 為什麼不會先死在 07/04 19:03
→ lovejomi: read呢? 07/04 19:03
我前面有講啊,推測是 operator<< 內部的實作可能用了 new / delete。
光是你 (2) 這一行就呼叫了整整五次:
cout << "read after free: " << ptr << ":" << *ptr << endl; (2)
你那幾次 cout 輸出了一堆東西,這個輸出動作就在你 main thread 裡。
也許你覺得那只不過是輸出東西到螢幕上,但內部動作其實頗複雜的。
你每呼叫一次 operator<< 都有破壞的可能性,只是什麼時候破壞就看運氣問題。
→ yvb: 其實 man ulimit 看 SEE ALSO 那邊就有 setrlimit(2) . 07/04 19:33
→ yvb: 另外, 讀不影響記憶體的值, 寫會影響, 所以你怎知 cout<< 07/04 19:34
→ yvb: 或 printf() 之類是否也用到那部分記憶體, 因而爛掉呢... 07/04 19:35
→ yvb: 又, man setrlimit 的 SEE ALSO => core(5) => man 5 core 07/04 19:38
※ 編輯: tinlans (122.116.164.123), 07/04/2018 20:19:07
推 cphe: 推推,有空也要來試試看 07/04 20:28