一點firejail經驗——調整firefox

renyuneyun 2020年03月29日(週日) 1 mins

近期折騰一文寫作的時候,其實我已經在嘗試使用firejail了。然而當時僅僅是在用它已經提供好的Profile,以及爲支持fcitx進行了細微修改,並沒有深入去瞭解其Profile如何寫就,以及它究竟能限制什麼。

而我由於在使用pass進行密碼管理,故而在firefox上使用passff插件以便自動填寫密碼。然而在默認的firejail profile下,passff無法正常工作。於是我起了去折騰的心,而直到今日終於解決問題。鑑於網上對於firejail的文章很少,中文尤其少,故而覺得可以留篇介紹文章。

其實我好幾年前就開始用pass了,但直到最近幾個月纔發現passff這個插件。

基本firejail知識

Firejail是一個沙盒軟件,所以可以期待它的功能也就是沙盒軟件的各項功能——文件系統隔離,進程隔離,訪問控制等等。它用到了Linux Namespace等機制以便實現沙盒,而且它本身需要SUID(顯然)。

我本身也在簡單的使用apparmor(最早是爲了限制ruijieclient的權限),那麼它們兩個是什麼關係呢?本來它們兩個是沒太大關係的,畢竟apparmor是直接工作在內核裏的,目的是控制進程的訪問權限以及其能力(比如可以/不可以修改某文件等);而firejail是沙盒,出了沙盒啥都沒了。然而firejail畢竟可以將外部信息暴露給沙盒內的程序,而且也可以進行訪問控制,於是這時候它就和apparmor的部分功能有所重疊。而firejail更是提供了一個apparmor整合模式,使得整個事情更複雜了。

所以,其實我並沒有弄清楚到底它們倆(當交織在一起時)關係是什麼。Arch wiki的相關章節也沒有解釋得十分清楚——大約是說firejail的apparmor整合會允許apparmor繼續在沙盒中工作?

至於「爲什麼要使用沙盒」……好吧,如果你有這個疑問,那麼你可能不適合看本文,建議先去看看沙盒的意義或者多好好用用計算機。

firejail的使用比較簡單,只要在想執行的軟件前加上firejail就行,例如firejail firefox。如果有相應的Profile,那麼firejail就會去按照相應的Profile來限制和授予訪問權。

而如果沒有定義相應的Profile,似乎會按默認Profile來授權?

Firejail的Profile在好幾個地方存放,在arch wiki中說得很清楚。許多情況下,它都會載入相應的local Proflie,以便於用戶自定義(這其實是需要顯式寫明的,不過默認的所有Profile都有這句)。而firejail的命令行參數本身也可以對權限進行控制,可以用來調試。

Firejail的Profile還是比較可讀的,看看就大概理解什麼意思了,然後簡單對應一下manpage就能完全理解。當然,書寫還是需要對照manpage的。而且它也支持嵌套(include),以便重用。

理所當然地,firejail也支持通過--profile=XXX在命令行參數中指定要使用的Profile。這樣就有一個很方便的調試方案:通過指定想要調試的Profile,打開一個shell,然後在其中進行調試。

對firefox的控制

在firejail的網站上專門有一節講它是如何控制firefox的權限的。有興趣理解細節的可以看一看,但核心就是下面的這些內容。

  1. 文件系統僅暴露必要部分
    1. /home下僅有當前用戶,當前用戶下僅暴露
      1. firefox本身的配置和緩存目錄
      2. Downloads目錄
    2. 其餘部分能不暴露就不暴露,能少給權限就少給權限
  2. 對系統工具進行限制,尤其是禁止啓動各語言的解釋器
  3. 禁止dbus等的訪問

做這一切,其實就是爲了最大可能地防止惡意網站通過瀏覽器漏洞對系統進行惡意訪問乃至控制。它默認的Profile安全是安全了,但一些正常的功能也被限制了。如果你打開默認的firefox Profile,你就會發現其中許多註釋都是在說「本行目的是xxx,但會導致xxx無法使用」。

中文輸入法

那麼知道了默認的Profile都做了什麼限制,我們就可以着手修改它了。即使對其本身不完全理解,我們也可以自己打開一個shell來檢查需要的功能是否存在。

既然我是中文用戶嘛,自然也需要中文輸入法的。然而在沙盒中,默認配置下是沒有辦法使用中文輸入法的(我的是fcitx)。翻閱了一些資料(不記得了……),查到我需要打開dbus和協議的支持。換句話說,我需要禁止對它們的禁用。

推薦的做法是在用戶配置目錄下,新建一個相應的local配置文件(比如firefox-common.local),在其中寫上相關內容:

ignore nodbus
ignore protocol

再次打開firefox的時候就會發現fcitx已經可以正常工作了。注意該做法是允許外界的fcitx和沙盒內部通信,而不是在沙盒內再開一個fcitx。

我其實沒完全弄清楚到底哪些protocol需要被允許。這句ignore protocol直接允許了所有的,按其man page說就是「unix, inet, inet6, netlink and packet」。哪位有fcitx開發經驗的還望不吝賜教。

支持passff

相比起fcitx能找到資料,passff就稍微麻煩一些了——沒有資料,只能自己去摸。我在檢查後發現,其問題的核心是passff的程序本身(/usr/lib/passff/passff.py)無法被啓動。而passff程序無法被啓動,則是因爲python解釋器沒有可執行權限。

於是解法有兩個:

  1. 打開python解釋器的可執行權限;
  2. 想辦法將兩者打包在一起(類似靜態鏈接)。

理論上來說,後者會更安全一些,畢竟打包在一起可以減少python解釋器被非法調用所產生的風險。然而操作上實在比較麻煩,所以我還是選擇了方案一。畢竟我並不打算給firefox以其他額外權限,尤其是沒給它對我的HOME的任意讀取,所以就算python解釋器被惡意調用,存在的唯一風險僅取決於firejail的漏洞。

很顯然,解法肯定不是chmod +x,不然也太坑爹了。在調試前,首先去看一眼man firejail-profile,確定firejail的配置中都能幹什麼。於是會發現,firejail沒有「賦予可執行權限」這麼一個配置選項,所以需要從「取消不可執行」這方面着手。

打開firefox的Profile(/etc/firejail/firefox.profile),會看到其本體並沒有什麼相關內容,但它包含了另一個文件——firefox-common.profile。這個文件又繼續包含了disable-interpreters.inc,而它就是我要找的核心部分。

打開disable-interpreters.inc文件會發現,其按種類禁止了各種解釋器的可執行權限。所以解法嘛,自然就是將其反過來。例如:

noblacklist ${PATH}/python3*

顯然,這裏的${PATH}代指實際的PATH環境變量。然而要注意的是,不知是否是firejail機制的問題,如果在這裏寫/usr/bin/python3*,則不會被認爲和${PATH}/python3*匹配,因而最終它還是會上黑名單,於是只讀。

上面的做法是「打補丁」的形式,所以需要嚴格匹配原來的內容,以便「補丁」生效。另一種方案是直接完全重寫一個Profile,也完全可行。然而我覺得這樣寫起來比較麻煩,而且更新的時候還要再次去檢查,維護起來也比較麻煩。

爲了方便的話,也可以直接去包含軟件包已經提供好的Profile,而免得自己寫漏或是其他:

include allow-python3.inc

解決了python解釋器的問題,打開firefox就能發現passff啓動成功,但會抱怨沒有密碼存儲。這是因爲~/.password-store/目錄不在沙盒中,程序找不到相關文件。解決方案也很理所當然——將它加進去就是了:

whitelist ${HOME}/.password-store/

另外,仔細翻看firefox的Profile會發現,其實firejail已經提供了一些額外配置,尤其是firefox-common-addons.inc。有需要的可以直接包含或參考它們,免去每次都自己翻找的麻煩。

按說到這裏,passff的問題就解決了。然而不要忘了,passff還需要對密碼進行解密啊!所以我們還需要進行額外的一步,那就是允許對GPG密鑰進行訪問:

noblacklist ${HOME}/.gnupg
whitelist ${HOME}/.gnupg

白名單和黑名單

眼尖的人會發現,我既寫過noblacklist,又寫過whitelist,那麼它們倆(blacklist和whitelist)是什麼關係呢?

此事我還真的沒有查到簡明且完善的解釋,唯一的相對靠譜的是arch wiki的內容,但還是沒有解釋清楚。當然,arch wiki裏面倒是提到了它們都是成對出現的,這對於理解稍微有所幫助。在翻閱man page和進行試驗之後,我似乎弄明白了一些。

從man page來看,似乎這兩個東西只是湊巧叫了個相關的名稱,但其工作方法並無關係。首先看到,blacklist是阻止對某目錄/文件的訪問,徹底的阻止,但不移除;沒有被blacklist的文件則默認會維持原狀。然後看到,whitelist會自動做兩件事:1.將頂層目錄作爲tmpfs掛載;2.將whitelist的目錄和文件bind-mount進去。

注意,whitelist的頂層目錄有限,是「home, /dev, /etc, /media, /mnt, /opt, /srv, /sys/module, /usr/share, /var, and /tmp」。出了這些目錄,就不能用whitelist了,而得考慮其他方案了(比如直接上bind)。

因而,這兩者單獨使用的時候都很符合直覺:在沒有whitelist但有blacklist的時候,但凡沒有blacklist掉的就可以訪問,但凡blacklist掉的就沒法訪問(但文件存在);而沒有blacklist但有whitelist的時候,但凡whitelist掉的都可以訪問,但凡沒有whitelist掉的都無法訪問(文件不存在,但可以新建,只不過會退出沙盒會丟失)。於是當兩者同時出現時,事情就是兩者的交織。

就我個人的實踐來看,對一個blacklist掉的東西再寫whitelist並不會使得其可以訪問,而是需要既noblacklist又whitelist。如果只進行whitelist,那麼會得到一個屬於nobody的無權訪問的目錄/文件;如果只進行noblacklist,在沙盒中該目錄/文件根本不會存在。所以,它們應是兩種不同機制,而且whitelist先考慮,然後再考慮blacklist。

而且額外地,(no)blacklist可以進行更多展開(比如前面用到的${PATH}),而(no)whitelist則更少(因爲其頂層目錄有限)。

附——我的配置文件

整理一下前面提到的所有補丁,最終形成的就是這麼一個內容:

ignore nodbus
ignore protocol

noblacklist ${HOME}/.gnupg
whitelist ${HOME}/.gnupg
whitelist ${HOME}/.password-store/
include allow-python3.inc
noblacklist /usr/lib/passff/passff.py

我在這裏額外寫了一句noblacklist /usr/lib/passff/passff.py,但這句目前沒有任何意義——該文件本身並沒有上黑名單。但我還是寫了這句,防患於未然嘛。


Related posts:

您可以在Hypothesis上的該群組內進行評論,或使用下面的Disqus評論。