Upstream
latest kernel: 4.13-rc2
latest stable: 4.12.3
Event
無
文章來源
https://lwn.net
文章摘要
- The trouble with SMC-R
原文鏈接
關於 SMC-R, 讀者可以自行閱讀 rfc7609 或者參考 4.11 內核的相關實現。 本質上通過內核層面標準的 socket 接口來實現 tcp/rdma 在傳輸層層面的融合,並實現自動的軟件 failover 功能(不考慮 RoCE lag 之類的 hw/fw 實現)。
主要特點:
socket 語義的兼容性, 兼容 SSL
應用不用關心底層傳輸層協議以及切換
幾乎 bypass 協議棧,但是保留 socket 層
無法很好的支持 zero copy
軟件 failover 協議定義完整,可以跨硬件(不同廠商的硬件),可以支持多網卡 (這點 RoCE lag 是不行的)
IBM 已經產品化
協議標準化
注:我認為不能 zero copy 是個很大的問題,兼容 socket 語義直接在 userspace 層面也是可以做的,可以全面的 bypass 協議棧,當然 SMC-R 的實現也不是一定在內核態,協議可以全部實現在用戶態,SMC-R 對於RDMA 的實現對於兼容 socket 以及 軟件 failover 層面的定義不錯。但未必是基於性能最大化的考慮,從已經產品化和標準化的角度,總體來說是有參考意義的。
我個人不認為這本質上是一個技術問題,主要是 linux-rdma 陣營和 netdev 陣營的協調性的問題,文章中也提到了 這個 patch 被 merge 進入 4.11 根本沒有經過 linux-rdma 相關郵件列表以及 maintainer 的首肯,D.M. 直接就給合併了,導致一定的爭論,netdev 的郵件列表,rdma 的相關開發人員也沒有特別關注到這個,總之是步調不太一致了。對於我們來說,我們不管他們各自出於什麼目的,知道什麼是你需要的,各自的優缺點更重要,把它們根據自己的業務需要融合到自己的產品裡,而不是是否該進 mainline ,很多好的 patch 也未必都是被社區接受的,還有些 committer 根本不關心這個。
- Containers as kernel objects
原文鏈接
近年來內核對容器的支持越來越全面,催生了一批容器化的系統。有趣的是內核關於容器是什麼樣的並沒有明確定義,它只是提供了一些用戶態可以使用的機制。David Howells最近著力於改變這種情況,他提交了一組patchset把container定義成一個內核對象。然而社區不是很接受這樣的修改。
容器是一種輕量化的虛擬化技術,為容器內的進程製造擁有整個系統的假象。通過區分namespace,為每個namespace分別提供網絡、文件系統和cgroup等來隔離容器,控制資源使用。輔以安全模塊或者seccomp來對容器更多限制。結果以可以接受的複雜程度提供了大量靈活的機制,這是非常Linux的方式。但是容器源於的缺失使得內核端的事情變得有些複雜。
增加容器object
Howell的patch引入了一套系統調用,用於維護容器。
int
container_create
(
const
char
*
name
,
...);
這個系統調用用於創建一個容器,容器名稱由第一個參數指定。flags參數指定namespace相關,例如指定 CONTAINER_NEW_USER_NS創建的容器會使用心得namespace。返回值是用於引用創建容器的文件描述符。還有其他flags用於控制文件描述符關閉是否需要銷燬容器這樣的事情。
容器創建之後沒有任何進程運行;如果用新掛載的namespace創建,容器裡也不會有文件系統。fsopen()和fsmount()系統調用可以用來把文件系統添加到容器內部。“at”版本的文件系統調用(openat(),例如)可以接受容器的文件描述符。也可以用這樣的方式讓容器使用socket:
int
container_socket
(
int
container_fd
,...);
可以讓容器使用netlink socket更方便。
讓進程在容器裡面運行的系統調用是:
pid_t
fork_into_container
(
int
container_fd
);
Howell認為還有其他可以加到這個機制的方法,例如suspend和restart容器的系統調用、容器cgroup管理的方法。只是Howell的修改前景並不明朗。
容器object抽象的不好?
很多人對這樣的修改並不買賬。
Jessica Frazelle認為Howell提出的容器object抽象的不夠好,有其他很多創建容器的方法。還提到Open Containers Initiative中的很多runtime規範。
James Bottomley更直接的認為這種修復的方向不對。他認為現在容器好就好在大家不必就容器是什麼樣的達成一致。可以根據需要創建一個用戶態namespace沒有mount namespace,或者一個體繫結構模擬容器只有一個mount namespace。 Kubernetes系統裡面對容器的使用允許namespace在“pods”之間共享,這個跟當前的容器object的定義也是衝突的。
Eric Biederman的反對更強烈。namespace這樣的修改會導致容器的所用conner cases完全暴露給用戶態,開發者需要解決的問題更復雜,產生更多bug。
Upcalls
退一步來看,這套patchset的出發點也不是讓用戶態管理容器更加方便,而是希望使內核“upcall”在容器環境裡面更好的運轉。
通常來說,內核是系統的最底層。然而也不全是這樣,例如 call_usermodehelper()調用創建一個用戶態進程來完成某些工作——換句話說“向上調用”到用戶態。用到這樣的“upcall”的有:
*
core
-
dump
代碼需要用戶態進程來處理
dump
出的數據。
*
NFSv4
client
調用程序做
DNS
解析。
*
模塊
loader
需要
helper
進程做模塊的
demand
-
loading
*
內核祕鑰管理代碼也需要用戶態
helper
這些upcall現在通常用到容器,然而內核沒有容器的明確定義,並不能確定到底在那個容器裡執行upcall,這樣upcall可能會引起問題。
定義容器的確是解決這個問題的一個方法。但是Howell的解法引入了另外兩個問題:(1)容器對象是解決問題的最好方法嗎?(2)即便容器object有意義,需要暴露給用戶態嗎?
Colin Walters提出的另外一個解決方法是完全取締“upcall”,用類似設備相關的events upcall被替換的方法。但是Jeff Layton指出這中解決方法只適用於部分問題,在其他情況下則可能引起系統可靠性的問題。
本文寫在相關討論火熱進行的時候,發展難以預料。但是近期來看暴露到用戶態的容器object難以進入內核。upcall的問題也許需要從其他方向進行解決,不過看起來這個問題還是需要一些時間。
3.Specifying the kernel ABI
原文鏈接
內核ABI規範化
在Open Source Summit Japan上,Sasha Levin談了談內核ABI的規範化,並介紹到目前為止取得的一些進展,當然還有大量工作尚未完成。主流觀點認為,內核新增patch不應該破壞內核ABI兼容性,比如,一個運行在4.0內核上的應用程序,也需要能在更新的5.0內核上運行。但遺憾的是,當前還沒有工具檢測ABI的兼容性是否被破壞。於是乎,只能依靠用戶去發現這種行為,然後報告到社區,由內核開發者修復。
由於沒有ABI規範,一些基礎軟件,如glibc, qemu, strace, 為了保證可靠性,在系統調用前,往往進行重複的參數檢查;這在性能方面帶來一定損耗。同時,內核在系統調用路徑上操作參數時,也容易破壞ABI的兼容性。更棘手的是,新發布了一個內核版本,一段時間後,用戶發現一些老程序不能在新內核上運行(ABI不兼容);於此同時,另外一些用戶基於新的ABI接口開發了新的應用。這時,內核開發者將面臨一個兩難的決策,不管如何,總會讓一些用戶不開心。
倘若ABI不能向後兼容,還會帶來許多其它噩夢,不一一詳述。因此,內核開發者需要一種內嵌在內核代碼中的“規範”,用於描述哪些修改內核的行為是被禁止的。該規範一石二鳥,即強制要求內核和用戶程序行為規範化,又解決了向後兼容問題。然而,該“規範”到底長什麼樣子呢?是人類可讀的文檔,還是機器可讀的代碼?從內核角度來看,需要能根據該規範生成代碼,用於系統調用參數和返回值檢查(這是ABI兼容性的一部分)。從用戶態來看,它需要使應用程序和庫訪問內核ABI更容易,更有保障。
目前,最難的是如何確定規範的格式。open() 和 close() 系統調用很容易描述,但是許多其它系統調用更復雜,且相互耦合。因此,spec文檔需要更詳細的記錄各個系統調用的行為,要比現有的man幫助文檔內容更豐富,更偏向於實現原理。其次,這些spec文檔需要經過嚴格的測試,以保證它沒有破壞現有用戶態程序的行為。
最後,Levin說自己正與syzkaller的開發者合作,參與一些前期工作, 希望對ABI規範化有所幫助。
- Namespaced file capabilities
原文鏈接
內核文件capabilities當前在用戶名字空間(user namespaces)上的使用有很多的缺陷。當前主要的問題集中在可執行文件的capabilities是全局的。當前有內核開發人員提交了一組補丁 嘗試讓用戶名字空間能夠感知到capabilities,但是這組補丁也引發了關於這一機制工作方式的討論。討論的核心問題是文件capabilities如何指定給一組文件。
Linux Capabilities機制允許將一組特權授權一個進程,以便更細粒度的控制特權用戶的權限,從而限制傳統Unix中root用戶的特權帶來的安全問題。舉個例子,一個非特權程序需要發送信號給另外一個不相關的進程時,只需要具備CAP_KILL權限,而並不需要獲得root權限。
傳統Unix系統,特權操作通過setuid(1)賦予普通用戶。而在帶有capabilities的系統上,只需要將可執行文件與對應權限關聯即可。而上面提到的文件capabilities則在2.6.24內核上就已經進入到內核主線。
用戶名字空間允許一組進程在名字空間中以root用戶運行,而其實這個用戶在名字空間外的root ID會映射到一個普通ID上來執行操作。為了執行一些特權操作,這個名字空間中root用戶將需要執行的一些程序通過setuid設置特權操作。而這些設置了特權操作的程序在名字空間外則不再具備特權。同樣的功能目前的文件capabilities則不具備。因為上面提到的全局視角問題。所有用戶名字空間對某個可執行文件都具有相同的視角。同時因為名字空間中的進程實際上並不是運行在root名字空間,因此也無法修改文件capabilities。
當前文件capabilities是通過擴展屬性(Extended Attribute, EA)來實現的,內部實際上是將這些文件capabilities保存在security.capability屬性中。內核會對security.*進行特殊處理。只有特權程序(比如:具備CAPSYSADMIN能力)才被允許修改這些屬性。這也就解釋了為什麼容器內非特權程序是無法添加文件capabilities的原因。同時也可以看到,當前沒有方法能夠針對特定的用戶名字空間來保存不同的EA。
Stefan Berger提交的補丁中通過擴展EA的語法來嘗試解決上述問題。該方法會將一個root名字空間下的用戶ID映射到特定用戶空間上的UID 0。比如一個UID 1000的用戶啟動一個用戶名字空間並作為root用戶運行,則他可以訪問UID 1000的所有文件。如果該用戶在用戶名字空間中嘗試添加capabilities,則這個信息會被保存成
security
.
capability@uid
=
1000
在名字空間外,這個新屬性沒有任何效果。而在用戶名字空間中,這個屬性則顯示為security.capability,因此容器中的文件可以按照授予的特權來運行。
當前的補丁並不是針對所有EA,而是涉及安全的一部分,比如security.capability以及security.selinux。當然隨後security.selinux的特性被移除了,因為SELinux維護者Stephen Smalley指出當前的實現有問題。
Casey Schaufler反對這一補丁,原因是如果兩個用戶空間使用相同的UID,並且共享目錄樹,那麼這些文件capabilities在兩個名字空間中都是可見的。他認為使用UID作為關鍵字來映射文件capabilities是不正確的。他認為應該找個其他的持久ID與用戶名字空間做關聯,以解決上面的問題。
James Bottomley反對這一補丁的理由是在動態分配用戶ID的容器上無法工作。他建議創建一個@uid前綴來解決動態分配的問題。
總之,雖然原始補丁問題多多,但是在最近幾個版本我們就會看到支持用戶名字空間的文件capabilities特性了。
5.Zero-copy networking
原文鏈接
網絡大部分時候都是性能敏感的,受數據拷貝操作的影響比較大,網絡數據包的zero-copy一直在不斷改進。通過sendfile()系統調用,文件內容可以不用拷貝到用戶態就直接發送出去;但是這個只能用於發送文件數據,類似對數據排序後的輸出這種的沒法使用zero-copy的sendfile()。 來自google的MSGZEROCOPY系列就是為了解決上述問題。首先需要在socket建立之後調用setsockopt()設置新的SOCKZEROCOPY選項;然後可以用如下方式實現一個zero-copy的發送:
status
=
send
(
socket
,
buffer
,
length
,
MSG_ZEROCOPY
);
都成功的情況下,給定的buffer將會鎖定在內存裡面。在send返回之前,由於zero-copy,注意需要保護好buffer不被修改。 zero-copy機制實現裡面會將通知消息發送到和socket綁定的error queue裡面,這樣可以獲知到數據發送完和buffer何時可以被重新使用。通知消息通過如下方式讀取:
status
=
recvmsg
(
socket
,
&
message
,
MSG_ERRORQUEUE
);
使用zero-copy傳輸需要將頁鎖定到內存,對數據量很小的傳輸這個鎖定內存操作開銷比較大,因此並不推薦對小數據量的傳輸使用該方式。實際上即便設置了MSG_ZEROCOPY,內核裡面也有可能對小操作使用拷貝的方式,但在這種情況,會有額外的狀態數據包的開銷。 有些情況zero-copy是不可能的,比如:網絡設備不支持生成checksum,這個時候內核需要自己計算,也就不可避免的要拷貝數據;另外類似需要對數據進行加密發送,這種情況也是無法實現zero-copy。
benchmark(netperf)測試的結果顯示zero-copy有39%的性能提升,當然在實際環境並無法達到這麼好效果,一個線上負載測試的結果顯示有5-8%的性能提升。 參考論文
- Hardened usercopy whitelisting
原文鏈接
更強的用戶拷貝白名單化
有很多種方法嘗試去搞垮操作系統內核。有一種特別有效的方法,如果可以做到的話,就是攻擊用戶空間和內核空間之間拷貝數據的操作。如果內核可以被欺騙拷貝到用戶空間大量數據,就會造成信息洩漏。反方向,如果攻擊者覆蓋內核內存會變得更糟。目前,內核已經有一系列抵抗攻擊的patch,但仍然有些工作需要匯合進去。
內核裡用到的堆內存主要來自於slab分配器。 hardened usercopy patch set(已經merge到4.8內核),試圖去限制錯誤拷貝的影響,它主要是通過保證單個的拷貝操作不會跨越一個slab分配好的對象和它的下一個對象的邊界。但是內核 會從slab分配器裡獲得大量的內存對象,並且它沒有必要在用戶和內核空間拷貝整個對象。假如只需要拷貝對象的部分的話,那麼阻止一個無賴的拷貝操作(從那些沒有必要暴露的結構裡拷貝或者是拷貝進去)將會有用。
舉個例子,mmstruct結構描述了一個進程的虛擬地址空間。它飲食了大量的安全敏感信息。其中一個字段為savedauxy字段被拷貝到用戶空間或者到從用戶空間拷貝。用於操作這個字段的prctl()函數並不會直接把它拷貝到mmstruct結構裡。有一些晦澀的代碼(在ELF二進制代碼)傳遞這個字段到copyto_user()。這樣限制拷貝操作就不會有暴露整個結構的風險。
授權保護是hardened usercopy whitelisting的目標。經驗說法是我們需要知道這些patch的由來。這些代碼是來自於grsecurity/Pax 補丁集。
簡單來講,這些補丁集增強了hardened usercopy whitelisting機器,主要通過一個slab分配好的對象上的用戶拷貝區域。只有在這個區域裡的數據才可以被拷貝到用戶空間(通過copytouser()或者copyfromuser())。值得強調的是,對於原來的一些操作比如put_user(),no checking機制已經被應用上。這樣這些操作的大小就是固定的,不會再受到攻擊的影響。
正常情況下,一個slab緩存使用kmemcachecreate()分配,這個補丁加入一個新的函數
struct
kmem_cache
*
kmem_cache_create_usercopy
(
const
char
*
name
,...);
參數useroffset和usersize是新的參數。它們描述了從slab緩存中分配對象的的區域。如果usersize為0,就不允許拷貝。從kmemcachecreate()創建的slab和這些函數比如kmalloc()的獲取的對象,都是白名單。
不管什麼時候,從slab獲取的對象被傳遞到用戶空間拷貝函數裡。那些要被拷貝的區域將會被檢查以保證它們全在白名單裡。如果檢查失敗,內核oops就會發生。
上述設計的一個影響就是那些對象只有單個的區域可以被暴露到用戶空間。如果有拷貝不止一個字段的需求,那麼這些字段必須合在一起,這樣單個的區域才可以覆蓋到它們。為了達到這個目的,在白名單化階段就需要把一些結構重新組織。目前,在補丁集裡,許多結構已經被專門白名單化了。
補丁集裡的最後一步就是為內存分配創建一個新的標誌,GFPUSERCOPY。有一些專門的系統調用來強制內核從用戶空間以可控制的大小分配結構。正常來講,這是沒有壞處的,只要大小控制在合理邊界即可。但是一些攻擊也會根據這個特點來實施。如果那些分配帶上了GFPUSERCOPY標誌,它們將會從一個分開的slab上獲取,這樣導致控制堆區域的佈局很難。
不太清楚這些補丁什麼時候進主線,但目前看,進入主線沒有一些嚴重的障礙。