天天看點

Linux核心月報2017年7月号

Upstream

latest kernel: 4.13-rc2

latest stable: 4.12.3

Event

文章來源

https://lwn.net 文章摘要

  1. 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 根本不關心這個。

  1. 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:

container_socket

container_fd

,...);

可以讓容器使用netlink socket更友善。

讓程序在容器裡面運作的系統調用是:

pid_t

fork_into_container

);

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

代碼需要使用者态程序來處理

出的資料。

NFSv4

client

調用程式做

DNS

解析。

子產品

loader

需要

helper

程序做子產品的

demand

loading

核心秘鑰管理代碼也需要使用者态

這些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規範化有所幫助。

  1. 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何時可以被重新使用。通知消息通過如下方式讀取:

recvmsg

&

message

MSG_ERRORQUEUE           

使用zero-copy傳輸需要将頁鎖定到記憶體,對資料量很小的傳輸這個鎖定記憶體操作開銷比較大,是以并不推薦對小資料量的傳輸使用該方式。實際上即便設定了MSG_ZEROCOPY,核心裡面也有可能對小操作使用拷貝的方式,但在這種情況,會有額外的狀态資料包的開銷。 有些情況zero-copy是不可能的,比如:網絡裝置不支援生成checksum,這個時候核心需要自己計算,也就不可避免的要拷貝資料;另外類似需要對資料進行加密發送,這種情況也是無法實作zero-copy。

benchmark(netperf)測試的結果顯示zero-copy有39%的性能提升,當然在實際環境并無法達到這麼好效果,一個線上負載測試的結果顯示有5-8%的性能提升。 參考論文

  1. 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

參數useroffset和usersize是新的參數。它們描述了從slab緩存中配置設定對象的的區域。如果usersize為0,就不允許拷貝。從kmemcachecreate()建立的slab和這些函數比如kmalloc()的擷取的對象,都是白名單。

不管什麼時候,從slab擷取的對象被傳遞到使用者空間拷貝函數裡。那些要被拷貝的區域将會被檢查以保證它們全在白名單裡。如果檢查失敗,核心oops就會發生。

上述設計的一個影響就是那些對象隻有單個的區域可以被暴露到使用者空間。如果有拷貝不止一個字段的需求,那麼這些字段必須合在一起,這樣單個的區域才可以覆寫到它們。為了達到這個目的,在白名單化階段就需要把一些結構重新組織。目前,在更新檔集裡,許多結構已經被專門白名單化了。

更新檔集裡的最後一步就是為記憶體配置設定建立一個新的标志,GFPUSERCOPY。有一些專門的系統調用來強制核心從使用者空間以可控制的大小配置設定結構。正常來講,這是沒有壞處的,隻要大小控制在合理邊界即可。但是一些攻擊也會根據這個特點來實施。如果那些配置設定帶上了GFPUSERCOPY标志,它們将會從一個分開的slab上擷取,這樣導緻控制堆區域的布局很難。

不太清楚這些更新檔什麼時候進主線,但目前看,進入主線沒有一些嚴重的障礙。

繼續閱讀