譯文 空指標的樂趣(1)

2021-06-07 18:03:07 字數 3432 閱讀 2332

譯者:王旭 (  , @gnawux ) /blog/?p=39

現在,大部分讀者都已經知道了 brad spengler 發布的「the local kernel exploit」(本地核心漏洞利用)。這一漏洞會影響 2.6.30 核心(以及 rhel5的乙個測試版 2.6.18 核心),受到了多方關注。本文將詳細分析如何利用這一漏洞,以及讓這個漏洞得以成真的令人震驚的一連串錯誤。

tun/tap 驅動提供了乙個虛擬的網路裝置,它會建立乙個隧道;這一驅動在多種場合都有很有用,包括虛擬化、vpn 等很多地方。使用 tun 時,程式通常開啟 /dev/net/tun,然後使用 ioctl() 呼叫來建立網路端點。herbert xu 近來注意到,缺少對包的審計可能會導致惡意程式能夠占用大量的核心記憶體,並導致系統效能下降。他的解決方案是通過乙個補丁給該裝置新增乙個「偽 socket」,使它可以使用核心的審計機制。問題是解決了,但是,回頭看來,它的代價是已入了乙個更嚴重問題。

tun 裝置支援 poll() 系統呼叫。(在 2.6.30 核心中)實現這個功能的函式的開頭是這樣的:

static unsigned int tun_chr_poll(struct file *file, poll_table * wait)

{struct tun_file *tfile = file->private_data;

struct tun_struct *tun = __tun_get(tfile);

struct sock *sk = tun->sk;

unsigned int mask = 0;

if (!tun)

return pollerr;

上面有下劃線的的那行**是 herbert 的補丁中新增的,這正是惹禍的開始。精心編寫的核心**都會小心的避免對指標的解引用,以避免 null;事實上,這裡只在那個條件語句那裡檢查了 tun 指標。並且,這是件好事;現在看來,如果進行了 configuring ioctl() 呼叫,tun 確實將是 null。這時,按照預期,本來 tun_chr_poll() 應該返回乙個錯誤狀態。

但 herbert 的補丁新增的指標解引用是在檢查之前的,這顯然是乙個 bug。在正常操作中,這個 bug 的影響會比較有限,如果 tun 是 null 的話,會導致核心 oops。oops 會首先殺掉進行這個系統呼叫的程序,並將回溯資訊加入系統日誌,此外就不應該發生什麼其他的事情了。最壞情況下,這應該也就是個拒絕服務問題。

依照上述推理,這是乙個小問題,雖人 null (0)可能確實是個合法的指標位址。預設的,不論在使用者空間還是核心空間,虛擬位址空間的底部(「0頁」和它上面的一些頁面)都是不允許任何訪問的,以用來捕捉空指標錯誤(如上描述)。不過,使用 mmap() 系統呼叫將真實的記憶體對映到虛擬位址空間的底部仍然是可能的。這個功能有一些合法的用例,包括執行一些過時的程式。儘管如此,大部分現代的系統都通過使用 mmap_min_addr sysctl 設定來禁止對映到0頁。

安全模組檢查被認為可以作為核心已經進行了的檢查的乙個補充,但這次,它並沒有如願工作。

這一設定應該阻止使用者空間的程式區對映零頁,這也就保證了空指標的解引用只會導致一次核心的oops。但是,不知何故,如果安全模組機制被配置入核心的話,2.6.30 中的 mmap() 的**會顯示地拒絕執行 mmap_min_addr 。取而代之的是將這個工作留給特定的安全模組來進行。安全模組的檢查工作被認為是核心中已有的檢查工作的乙個補充,但它此時卻並不工作。對於 0 頁,安全模組會授權訪問,而其他情況下則會拒絕訪問。這個錯誤的最後一步是,red hat 的預設 selinux policy 允許對映 0 頁。這樣,執行 selinux 實際是降低了系統的安全性。

但沒有 selinux 的生活也不是就一馬平川了。在沒有 selinux 的時候,攻擊行為會被mmap_min_addr 限制,這似乎足夠讓一切結束了。但這是可以通過使用 personality() 系統呼叫繞過的。開啟 svr4 個性化會在程式被 exec() 呼叫的時候將乙個唯讀頁面對映到 0 位址,但只有程序有 cap_sys_rawio 能力的時候才會這樣。所以,需要乙個更進一步的欺詐行為:頂級的攻擊**設定 svr4 更興華,然後使用 exec 執行有特殊外掛程式的 pulseaudio 伺服器。pulseaudio 伺服器是 setuid root 的,所以它將會在呼叫時對映到 0 頁面。當呼叫到外掛程式**的時候,pulseaudio 將會放棄它的許可權,但是,這時 0 頁已經對攻擊**可用了,攻擊**可以讓 0 頁可寫,並將其自己的資料放在這裡。

上面這些攻擊的結果就是,使用者空間程序是有可能對映0頁而不讓 tun_chr_poll() 發生核心 oops。不過,你可能會想,攻擊者還不能高興得太早,畢竟接下來 tun 就會檢查空指標。這正是這一系列錯誤中的下乙個:gcc編譯器缺省會優化掉 null 的徹底檢驗。原因在於,因為這個指標已經被解引用過了(而且也什麼都沒發生),所以它不可能是 null。所以,沒有理由再去檢查它了。於是,儘管這個邏輯本來在大部分情況下都有效,但是在 null 是乙個合法指標的時候卻是錯誤的。

所以,攻擊者這時就能通過乙個空 tun 指標而成功進入 tun_chr_poll() 內部了。接下來需要指出如何利用這種情況控制核心。tun_chr_poll() 中後面的下一步**是這樣的:

if (sock_writeable(sk) ||

(!test_and_set_bit(sock_async_nospace, &sk->sk_socket->flags) &&

sock_writeable(sk)))

mask |= pollout | pollwrnorm;

注意,sk 的值來自於 tun 的解引用,所以它位於攻擊者的控制之下。sock_async_nospace 是 0,所以 test_and_set_bit() 呼叫可以用於設定記憶體中任何字的最低權重位。這是個小小的記憶體衝突,但這已經被證實是足夠的了,在 brad 展示的攻擊**中,sk->sk_socket->flags 指標指向了 tun 驅動的 file_operations 結構;特別的,它是指向了 mmap() 函式。tun驅動不支援 mmap() 呼叫,所以這個指標通常應該是 null,在 poll() 呼叫之後,它就是 1 了。

攻擊**的最後一步就是呼叫這個開啟的 tun 裝置的檔案描述符的 mmap() 呼叫。由於內部的mmap() 已經不是空了(剛剛被我們設定成了 1),核心將會跳到**。那個位址已經在攻擊**所對映的0頁面中了,所以,它在攻擊者的控制之下。於是,攻擊**使用下乙個跳轉跳到其自己的**處即可。這樣,當核心呼叫(它以為的)tun 驅動的 mmap() 函式的時候,結果就是任意**都可以在核心模式下執行;這裡,攻擊**獲得了完全的控制權。

在乙個良好設計的系統中,乙個單獨的錯誤很少導致災難性的故障。而這裡就是這樣乙個例子。很多東西都出錯才導致了這個攻擊成為可能:安全模組能夠不顧系統策略而授權訪問地位記憶體,selinux 策略允許這些對映,pulseaudio 可以被攻擊**利用從而讓這一對映可以被攻擊**使用,空指標在解引用之前未被檢驗,並且檢驗被編譯器優化掉了,**以某種方式使用空指標可以獲取系統的控制權。這是一條長長的錯誤鏈,其中的每一環節都是讓這一攻擊成功的必要條件。

空指標的理解

空指標 null pointer 空指標 乙個被賦值為0的指標 1 空指標常量 null pointer constant an integer constant expression with the value 0,or such an expression cast to type void ...

空指標和迷途指標的區別

解釋 include int main int pint new int pint 10 cout pint delete pint pint 0 pint 20 oh no,this was deleled.cout pint 迷途指標也叫懸浮指標,失控指標,是對乙個指標delete後 這樣會釋放...

空指標訪問的陷阱

asan工具報錯發生在如下行,此處maudiorenderserviceptr為null。this onstop false,maudiorenderserviceptr getaddr 下面對正常執行和工具除錯出現的結果一致進行分析,以及延伸的討論一下關於空指標訪問的一些陷阱。空指標即未指向任何物...