<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      一個由進程內存布局異常引起的問題

      前段時間業務反映某類服務器上更新了 bash 之后,ssh 連上去偶發登陸失敗,客戶端吐出錯誤信息如下所示:

      圖 - 0

      該版本 bash 為部門這邊所定制,但是實現上與原生版并沒有不同,那么這些錯誤從哪里來?

      是 bash 的鍋嗎

      從上面的錯誤信息可以猜測,異常是 bash 在啟動過程中分配內存失敗所導致,看起來像是某些情況下該進程錯誤地進行了大量內存分配,最后導致內存不足,要確認這個事情比較簡單,動態內存分配到系統調用這一層上主要就兩種方式: brk() 和 mmap(), 所以只要統計一下這兩者的調用就可以大概估算出是否有大內存分配了。

      bash 是由 sshd 啟動的,于是 strace 跟蹤了一下 sshd 進程,結果發現異常發生時,bash 分配的內存非常地少,少到有時甚至只有幾十字節也會失敗,幾乎可以斷定 bash 在內存使用上沒有異常,但在這期間發現一個詭異的現象,Bash 一直只用 brk 在分配小內存,brk() 失敗后就直接退出了,一般程序使用的 libc 中的 malloc (或其它類似的 malloc) 會結合 brk 和 mmap 一起使用【0】,不至于 brk 一失敗就分配不到內存,順手查看了下 bash 的源碼,發現它確實基于 brk 做了自己的內存管理,并沒有使用 malloc 或 mmap。

      但那并不是重點,重點是即使是只使用 brk,也不至于只能分配幾十字節的內存。

      進程的內存布局

      進程的內存布局在結構上是有規律的,具體來說對于 linux 系統上的進程,其內存空間一般可以粗略地分為以下幾大段【1】,從高內存到低內存排列:
      1、內核態內存空間,其大小一般比較固定(可以編譯時調整),但 32 位系統和 64 位系統的值不一樣。
      2、用戶態的堆棧,大小不固定,可以用 ulimit -s 進行調整,默認一般為 8M,從高地址向低地址增長。
      3、mmap 區域,進程茫茫內存空間里的主要部分,既可以從高地址到低地址延伸(所謂 flexible layout),也可以從低到高延伸(所謂 legacy layout),看進程具體情況【2】【3】。
      4、brk 區域,緊鄰數據段(甚至貼著),從低位向高位伸展,但它的大小主要取決于 mmap 如何增長,一般來說,即使是 32 位的進程以傳統方式延伸,也有差不多 1 GB 的空間(準確地說是 TASK_SIZE/3 - 代碼段數據段,參看 arch/x86/include/asm/processor.h 里宏 TASK_UNMAPPED_BASE 的定義)【4】
      5、數據段,主要是進程里初始化和未初始化的全局數據總和,當然還有編譯器生成一些輔助數據結構等等),大小取決于具體進程,其位置緊貼著代碼段。
      6、代碼段,主要是進程的指令,包括用戶代碼和編譯器生成的輔助代碼,其大小取決于具體程序,但起始位置根據 32 位還是 64 位一般固定(-fPIC, -fPIE等除外【5】)。

      以上各段(除了代碼段數據段)其起始位置根據系統是否起用 randomize_va_space 一般稍有變化,各段之間因此可能有隨機大小的間隔,千言萬語不如一幅圖:


      圖 - 1

      所以現在的問題歸結為:為什么目標進程的 brk 的區域突然那么小了,先檢查一下 bash 的內存布局:


      圖 - 2

      這個進程的內存布局和一般理解上有很大出入,從上往下是低內存到高內存:
      #1 處為進程的代碼段和數據段,這兩個區域一般處于進程內存空間的最低處,但現在在更低處明顯有動態庫被映射了進來。
      #2 處為 brk 的區域,該區域還算緊臨著數據段,但是,brk 與代碼段之間也被插入了動態庫,而且更要命的是,brk 區域向高處伸展的方向上,動態庫映射的區域貼的很近,導致 brk 的區域事實上只有很小一個空間(0x886000 - 0x7ac000)。

      這并不是我們想要的內存布局,我們想要的應該是長成下面這樣的:


      圖 - 3

      看出來不同了沒有,兩個 bash 進程都是 64 位的,不同在于前者是 sshd 起的進程后者是我手動在終端上起起來的,手動 cat /proc/self/maps 看了下 64 位的 cat 的進程的內存布局也是正常的:


      圖 - 4

      那 sshd 進程呢?

      圖 - 5

      sshd 進程也不正常,而且意外發現 sshd 是 32 位的,于是寫了個測試程序:


      圖 - 6

      該程序編譯為 32 位在目標機器上可以重現問題,而如果編譯為 64 位則一切正常,另一個發現是只要是 32 位的進程,它們的內存布局都"不正常"。

      操作系統的鍋嗎?

      要搞清楚這個問題得先搞明白進程在內核里啟動的流程,對用戶態的進程來說,任何進程都是從母進程 fork 出來后再執行 execve, execv 則主要調用對應的加載器(主要是 elf loader)來把代碼段、數據段以及動態連接器(ld.so,如果需要)加載進內存空間的各個相應位置,完成之后直接跳到動態連接器的入口(這里先忽略靜態鏈接的程序),其它的動態庫都由動態庫連接器負責加載,需要注意的是,無論是內核加載 ld.so 還是 ld.so 加載其它動態庫,都需要 mmap 的協助,這是用來在內存空間里找位置用的。

      現在我們來看看內核出了什么問題,目標系統版本如下,經過咨詢系統組的人確認,該系統基于 centos 6.5: http://vault.centos.org/6.5/centosplus/Source/SPackages/kernel-2.6.32-431.el6.centos.plus.src.rpm


      圖 - 7

      首先看看 arch/x86/mm/mmap.c: arch_pick_mmap_layout() 這個函數,它的作用是根據進程和當前系統的設置初化 mmap 相關的入口:


      圖 - 8

      Exec-shield 是一類安全功能的開關,由紅帽在很多年前主導搞的對 buffer overflow 攻擊的一系列增強,具體可以參看這幾個連接 1234,exec shield 在實現和使用上一直有問題,也破壞了有些舊程序的兼容性【6】,因此一直沒進主干,只在 redhat 家族 6.x 及其派生系統上使用。

      這個功能有一個開關 /proc/sys/kernel/exec-shield,根據鏈接【6】上的說明,exec-shield 可以設置為 0、1、2、3,分別表示:強制關閉/默認關閉除非可執行程序指定打開/默認打開除非可執行程序指定關閉/強制打開。

      mm->get_unmapped_area 是進程需要進行 mmap 時調用的最終函數, arch_get_unmap_area() 用來以傳統方式從低位開始搜索合適的位置,arch_get_unmapped_area_topdown() 則以 flexible layout 的方式從高位開始搜索合適的位置,關鍵點在于 125 ~ 129 行,exec-shield 引進了另一種專門針對 32 位進程的內存分配方式,這種方式指定如果要分配的內存需要可執行權限,那么應該從 mm->shlib_base 這里開始搜索合適的位置,shlib_base 的值為 SHLIB_BASE 加上一個小的隨機偏移,而 SHLIB_BASE 的值為【7】:

      圖 - 9

      注意到該地址位于 32 位進程的代碼段之前(0x8048000),所以這就解釋了為什么 32 位的進程,它的動態庫被加載到了低位甚至穿插進了 brk 和數據段之間的空隙,本來這個特殊的搜索內存空間的方式是只針對需要可執行權限的內存,但由于 elf 加載器在加載動態庫時是分段(PT_LOAD)進行加載【8】,第一個段的位置由 mm->get_unmap_area() 搜索合適的位置分配,后續的段則使用 MAP_FIXED 強制放在了第一個段的后面,所以導致數據段也映射到了低位.【9】

      下圖 1641 行展示了 mmap 時怎樣從 mm 結構里獲取 get_area 函數,可以看到,只要 mm->get_unmmapped_exec_area 不為空,且要分配的內存需要可執行權限,就優先使用 mm->get_unmmapped_exec_area 進行搜索。


      圖 - 10

      上面這種針對 exec 內存的分配方式實際上很容易引起沖突,redhat 在這里也是打了不少補丁,參看123

      問題并沒有解決

      上面的解釋說明了為什么 32 位進程的內存布局會異常,但是這里的問題是,為什么用 32 位進程起 64 位進程時,64 位的進程也同樣受到了影響。要搞清楚這里的問題,就得看看 fs/binfmt_elf.c: load_elf_binary() 這個函數,它用來在當前進程中加載 elf 格式可執行文件并跳過去執行,此函數被 32 位的 elf 與 64 位 elf 所共用(借助了比較隱蔽的宏),它做的事情總結起來包括如下:
      1、讀取和解析 elf 文件里包含的各種信息,關鍵信息如代碼段,數據段,動態鏈接器等。
      2、flush_old_exec(): 停止當前進程內的所有線程,清空當前內存空間,重置各種狀態等。
      3、設置新進程的狀態,如分配內存空間,初始化等。
      4、加載動態連接器并跳過去執行。


      圖 - 11

      現在回到我們問題,當前進程是 32 位的,在 64 位的系統上執行 32 位的進程需要內核支持,當內核發現 elf 是 32 位的程序時,會在 task 內部置一個標志,這個標志在上圖 load_elf_binary() 函數里 740 行調用 SET_PERSONALITY() 才會被清除,所以在 721 行時,當前進程仍認為自己是 32 位的,flush_old_exec() 做了什么事情呢,參看:fs/exec.c: flush_old_exec()


      圖 - 12

      注意其中 1039 行,bprm->mm 表示新的內存空間(舊的還在,但馬上就要釋放并切換新的),這里需要對新的內存空間進行設置,參看: fs/exec.c: exec_mmap()


      圖 - 13

      我們可以看到在當前進程還是 32 位的時候,內核對新的內存空間進行了初始化,導致 arch_pick_mmap_layout() 錯誤地將 arch_get_unmaped_exec_area 賦值給了 bprm->mm->get_unmapped_exec_area 這個成員變量,雖然圖-11中 load_elf_binary() 函數在 748 行,32 位的標志被清空之后再次調用 set_up_new_exec() -> arch_get_unmapped_exec_area(),但 arch_get_unmaped_exec_area() 并沒有清空 mm->get_unmapped_exec_area 這個變量,導致 execv 后雖然進程是 64 位的,但仍然以 mm->shlib_base 這里作為起始地址搜索內存空間給動態庫使用, oops.

      解決方案

      最直接可靠的做法是在進入 arch_pick_mmap_layout() 時,先把 mm->get_unmapped_exec_area 置為 NULL,但這就要修改內核了,用戶態要規避的話有以下方式:
      1、設置 ulimit -s unlimited,并設置 exec-shield 為 0 或 1,再起進程,這樣一來,因為用戶態的棧是無限長的,內核只能以傳統的方式來對 32 位進程分配內存,不會掉進 exec-shield 的坑里。
      2、把 randomize_va_space 禁掉,但這個做法只是把頭埋進了沙子里。

      總的來說,上面兩種用戶態的規避方案基本是哪里疼往哪貼膏藥,并非解決問題之道(且有安全隱患),退一步來說,不要用 32 位的進程來起動 64 位進程還相對穩妥點.

      參考

      【0】https://en.wikipedia.org/wiki/C_dynamic_memory_allocation
      【1】https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/5/html/Tuning_and_Optimizing_Red_Hat_Enterprise_Linux_for_Oracle_9i_and_10g_Databases/sect-Oracle_9i_and_10g_Tuning_Guide-Growing_the_Oracle_SGA_to_2.7_GB_in_x86_Red_Hat_Enterprise_Linux_2.1_Without_VLM-Linux_Memory_Layout.html
      【2】understanding the linux kernel, page 819, flexible memory region layout: https://books.google.com.hk/books?id=h0lltXyJ8aIC&pg=PT925&lpg=PT925&dq=linux+flexible+memory&source=bl&ots=gO7rIYb8HR&sig=pirB5pswdHFHSljy57EksxS3ABw&hl=en&sa=X&ved=0ahUKEwjpkfa-2_rRAhVGFJQKHcETDSUQ6AEITDAH#v=onepage&q=linux flexible memory&f=false
      【3】https://gist.github.com/CMCDragonkai/10ab53654b2aa6ce55c11cfc5b2432a4
      【4】http://lxr.free-electrons.com/source/arch/x86/include/asm/processor.h#L770
      【5】 https://access.redhat.com/blogs/766093/posts/1975793
      【6】https://lwn.net/Articles/31032/
      【7】https://lwn.net/Articles/454949/
      【8】http://lxr.free-electrons.com/source/fs/binfmt_elf.c#L549
      【9】http://lxr.free-electrons.com/source/fs/binfmt_elf.c#L563
      【10】類似問題: https://bugzilla.redhat.com/show_bug.cgi?id=870914 https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=522849

      posted on 2017-02-06 16:30  twoon  閱讀(9070)  評論(18)    收藏  舉報

      主站蜘蛛池模板: 亚洲人成色99999在线观看| 人妻有码中文字幕在线| 成人性能视频在线| 日韩免费码中文在线观看| 久久精品国产91精品亚洲| 午夜精品视频在线看| 铜川市| 青青草国产精品日韩欧美| 精品久久免费国产乱色也| 精品久久精品午夜精品久久| 欧美亚洲一区二区三区在线| 中文字幕网红自拍偷拍视频| 亚洲av无码片在线播放| 中文字幕人妻无码一区二区三区| 日韩精品一区二区三区激情| 亚洲精品自产拍在线观看动漫| 香港日本三级亚洲三级| 南雄市| 久久精品国产99亚洲精品| 精品免费看国产一区二区| 德州市| 亚洲欧洲日产国码久在线| 18岁日韩内射颜射午夜久久成人 | 福利一区二区在线视频| 欧美日韩中文字幕视频不卡一二区 | 亚洲女同精品久久女同| 日韩大片高清播放器| 天堂av最新版中文在线| 波多野结衣av一区二区三区中文| 大屁股国产白浆一二区| 曲松县| 亚洲中文字幕无码中字| 资源在线观看视频一区二区| 欧美成本人视频免费播放| 四虎国产精品成人免费久久| 国产中文99视频在线观看| 亚洲精品国产老熟女久久| 视频一区视频二区制服丝袜| 亚洲精品一区二区三区蜜| 亚洲精品成人片在线播放| 浦北县|