掉坑
記得在笨鳥(niǎo)海淘做倉(cāng)庫(kù)管理系統(tǒng)的時(shí)候,我用Windows寫倉(cāng)庫(kù)管理軟件。當(dāng)時(shí)系統(tǒng)用的是Windows 10。微軟的官網(wǎng)對(duì)UWP吹得神乎其神,各種設(shè)備兼容,一套代碼多種設(shè)備運(yùn)行云云,很動(dòng)人。最后,在功能快完成的時(shí)候,掉坑了。APP沒(méi)有打印的權(quán)限。那是我第一次掉進(jìn)APP沙盒的坑。
最近一次掉沙盒的坑是上周,寫PNG Zip的時(shí)候。起初,我覺(jué)得一周的時(shí)間應(yīng)該能完成所有的工作。最終,我花了一周半的時(shí)間。
所以,我覺(jué)得我應(yīng)該跟你分享一下這個(gè)掉坑和填坑的過(guò)程??赐赀@篇文章,你應(yīng)該對(duì)沙盒機(jī)制有了一定程度的了解,如何在沙盒機(jī)制下編程,最后我還會(huì)給你一些建議。
在說(shuō)掉坑填坑之前,我們先了解一下,沙盒是什么。
沙盒是什么
在我們?nèi)粘S玫降牟僮飨到y(tǒng)中,應(yīng)用沙盒機(jī)制已經(jīng)普遍存在了。如iOS、Android、macOS、Windows。由于手機(jī)是我們每天都用的貼身工具,里面保存了非常多的隱私數(shù)據(jù)。所以手機(jī)操作系統(tǒng)中,所有應(yīng)用程序都是沙盒隔離的。而PC系統(tǒng)在這兩年也開(kāi)始推廣沙盒的應(yīng)用,如macOS、Windows 10。今天我要講的是macOS的沙盒。
我們先來(lái)看看沙盒的定義。在Wikipedia中,沙盒是這么定義的。
In computer security, a sandbox is a security mechanism for separating running programs, usually in an effort to mitigate system failures or software vulnerabilities from spreading. It is often used to execute untested or untrusted programs or code, possibly from unverified or untrusted third parties, suppliers, users or websites, without risking harm to the host machine or operating system.
在計(jì)算機(jī)安全中,沙盒是一種隔離應(yīng)用程序的機(jī)制,通常致力于抑制系統(tǒng)故障或者軟件故障的擴(kuò)散。它通常用于執(zhí)行未測(cè)試的或者不受信任的,來(lái)自于未認(rèn)證或者不受信任的第三方、供應(yīng)商、用戶或者網(wǎng)站的應(yīng)用程序和代碼,以免主機(jī)和操作系統(tǒng)受到破壞。
在macOS中,沙盒是這樣的。
App Sandbox is an access control technology provided in macOS, enforced at the kernel level. It is designed to contain damage to the system and the user’s data if an app becomes compromised.
應(yīng)用沙盒是一種由macOS提供的內(nèi)核級(jí)別的訪問(wèn)控制技術(shù)。它是為了控制系統(tǒng)或者用戶數(shù)據(jù)受到的破壞而設(shè)計(jì)的(當(dāng)一個(gè)應(yīng)用程序受到劫持的時(shí)候)。
這兩個(gè)定義說(shuō)的意思差不多,就是對(duì)應(yīng)用進(jìn)行隔離。包括運(yùn)行環(huán)境和用戶數(shù)據(jù)。具體就是說(shuō),第一次打開(kāi)應(yīng)用,你上不了網(wǎng),你拍不了照片,你寫不了文章。所有的一切你都需要用戶授權(quán)。
為什么需要沙盒
答案是安全?,F(xiàn)如今應(yīng)用變得非常的復(fù)雜。而復(fù)雜的系統(tǒng),不管你多么努力,制定多么嚴(yán)格的安全編程規(guī)范,測(cè)試得多么地充分,攻擊者總能找到漏洞。沙盒的作用就是當(dāng)你的應(yīng)用受到攻擊時(shí),系統(tǒng)和用戶數(shù)據(jù)受到的破壞能被控制在一定的范圍內(nèi)。
如何在沙盒環(huán)境下編程
在沙盒環(huán)境下,我們需要做一些權(quán)限相關(guān)的額外工作。
- 聲明權(quán)限
- 獲取權(quán)限
- 保存權(quán)限
- 重新獲取權(quán)限
- 釋放權(quán)限
1)聲明權(quán)限
在macOS中,通過(guò)Entitlements來(lái)聲明權(quán)限。創(chuàng)建xcode工程時(shí),xcode會(huì)默認(rèn)創(chuàng)建appname.entitlements的plist文件。開(kāi)發(fā)者可以編輯這個(gè)文件,或者通過(guò)target的Capabilities標(biāo)簽配置。

在Capabilities只能聲明部分權(quán)限,有些權(quán)限需要手工編輯.entitlement文件。
2)獲取權(quán)限
聲明了權(quán)限后,應(yīng)用就可以使用相應(yīng)的編程接口來(lái)實(shí)現(xiàn)所要的功能。例如,在用戶Home目錄讀寫文件。你可以通過(guò)NSFileManager提供的API來(lái)獲取相應(yīng)的路徑。
swift
class NSFileManager
var homeDirectoryForCurrentUser: URL { get }
Objective-C
interface NSFileManager
@property(readonly, copy) NSURL *homeDirectoryForCurrentUser;
對(duì)于用戶Home目錄以外的文件,需要喚起NSOpenPanel來(lái)讓用戶選擇,或者通過(guò)拖拽的方式將文件拖拽到應(yīng)用界面中。通過(guò)閱讀Introduction to Drag and Drop,你可以很快地實(shí)現(xiàn)拖拽功能。
3)保存權(quán)限
通過(guò)用戶選擇或者拖拽獲取到的文件讀寫權(quán)限在應(yīng)用關(guān)閉之后就會(huì)丟失。所以,需要一種方法來(lái)保存這個(gè)權(quán)限。macOS給我們提供了Security Scope Bookmark。NSURL提供了對(duì)應(yīng)的接口來(lái)創(chuàng)建Security Scope Bookmark。
swift
class NSURL
func bookmarkData(options: NSURL.BookmarkCreationOptions = [],
includingResourceValuesForKeys keys: [URLResourceKey]?,
relativeTo relativeURL: URL?) throws -> Data
Objective-C
interface NSURL
- (NSData *)bookmarkDataWithOptions:(NSURLBookmarkCreationOptions)options
includingResourceValuesForKeys:(NSArray<NSURLResourceKey> *)keys
relativeToURL:(NSURL *)relativeURL
error:(NSError * _Nullable *)error;
Security Scope Bookmark以NSData的對(duì)象表示。NSData可以持久化到文件,也可以通過(guò)IPC等方式發(fā)送給其他進(jìn)程。在應(yīng)用重啟或者另一個(gè)進(jìn)程接收到NSData對(duì)象后,即可重新獲取權(quán)限。如果想要持久化bookmark,可以使用NSData的writeToURL方法。
swift
class NSData
func write(to url: URL,
atomically: Bool) -> Bool
Objective-C
interface NSData
- (BOOL)writeToURL:(NSURL *)url
atomically:(BOOL)atomically;
4)重新獲取權(quán)限
Security Scope Bookmark中保存了NSURL的相關(guān)信息。我們可以通過(guò)NSURL提供的接口重新獲得保存的NSURL對(duì)象。
swift
class NSURL
convenience init(resolvingBookmarkData bookmarkData: Data,
options: NSURL.BookmarkResolutionOptions = [],
relativeTo relativeURL: URL?,
bookmarkDataIsStale isStale: UnsafeMutablePointer<ObjCBool>?) throws
Objective-C
interface NSURL
+ (instancetype)URLByResolvingBookmarkData:(NSData *)bookmarkData
options:(NSURLBookmarkResolutionOptions)options
relativeToURL:(NSURL *)relativeURL
bookmarkDataIsStale:(BOOL *)isStale
error:(NSError * _Nullable *)error;
通過(guò)NSURL重新獲取權(quán)限。
swift
class NSURL
func startAccessingSecurityScopedResource() -> Bool
Objective-C
interface NSURL
- (BOOL)startAccessingSecurityScopedResource;
5)釋放權(quán)限
一旦不再需要使用權(quán)限,盡快釋放權(quán)限。
swift
class NSURL
func stopAccessingSecurityScopedResource()
Objective-C
interface NSURL
- (BOOL)stopAccessingSecurityScopedResource;
坑
人在江湖飄,哪能不挨刀。掉坑總是難免的。NSURL提供了一個(gè)很容易讓人誤用的接口。當(dāng)我第一眼看到這個(gè)接口時(shí),我就把它當(dāng)作持久化bookmark的接口了。而且這個(gè)接口也確實(shí)能持久化bookmark數(shù)據(jù)。然而當(dāng)你重啟應(yīng)用重新得到NSURL對(duì)象后會(huì)發(fā)現(xiàn),無(wú)法獲取權(quán)限。
swift
class NSURL
class func writeBookmarkData(_ bookmarkData: Data,
to bookmarkFileURL: URL,
options: NSURL.BookmarkFileCreationOptions) throws
Objective-C
interface NSURL
+ (BOOL)writeBookmarkData:(NSData *)bookmarkData
toURL:(NSURL *)bookmarkFileURL
options:(NSURLBookmarkFileCreationOptions)options
error:(NSError * _Nullable *)error;
實(shí)際上,這個(gè)接口是用于創(chuàng)建別名鏈接的。
一些建議
作為開(kāi)發(fā)者,出于降低項(xiàng)目風(fēng)險(xiǎn)和減少工作量的目的。我的建議是盡可能禁用沙盒機(jī)制。因?yàn)樯澈羞@個(gè)機(jī)制還處于發(fā)展階段。權(quán)限控制的粒度和編程接口都還沒(méi)穩(wěn)定。
可以不開(kāi)啟的場(chǎng)景
- 內(nèi)部工具
- 企業(yè)應(yīng)用
- 非官方應(yīng)用商店渠道銷售的應(yīng)用
必須開(kāi)啟沙盒的場(chǎng)景
- 官方應(yīng)用商店銷售的應(yīng)用
- 手機(jī)應(yīng)用
總結(jié)
應(yīng)用沙盒提供了一種隔離機(jī)制,使得被劫持的應(yīng)用能夠造成的破壞被限制在一定的范圍內(nèi)。同時(shí),沙盒機(jī)制是一把雙刃劍。他在提供保護(hù)的同時(shí),也加重了開(kāi)發(fā)者的負(fù)擔(dān),產(chǎn)生額外的輔助數(shù)據(jù),占用磁盤空間。甚至在沙盒環(huán)境下,部分功能無(wú)法實(shí)現(xiàn)。
題外話
說(shuō)到底,這是個(gè)信任問(wèn)題。軟件之間、系統(tǒng)之間、人與人之間需要一種信任。當(dāng)信任缺失的時(shí)候,所有參與到其中的任何一方都要付出代價(jià)。