掉坑
记得在笨鸟海淘做仓库管理系统的时候,我用Windows写仓库管理软件。当时系统用的是Windows 10。微软的官网对UWP吹得神乎其神,各种设备兼容,一套代码多种设备运行云云,很动人。最后,在功能快完成的时候,掉坑了。APP没有打印的权限。那是我第一次掉进APP沙盒的坑。
最近一次掉沙盒的坑是上周,写PNG Zip的时候。起初,我觉得一周的时间应该能完成所有的工作。最终,我花了一周半的时间。
所以,我觉得我应该跟你分享一下这个掉坑和填坑的过程。看完这篇文章,你应该对沙盒机制有了一定程度的了解,如何在沙盒机制下编程,最后我还会给你一些建议。
在说掉坑填坑之前,我们先了解一下,沙盒是什么。
沙盒是什么
在我们日常用到的操作系统中,应用沙盒机制已经普遍存在了。如iOS、Android、macOS、Windows。由于手机是我们每天都用的贴身工具,里面保存了非常多的隐私数据。所以手机操作系统中,所有应用程序都是沙盒隔离的。而PC系统在这两年也开始推广沙盒的应用,如macOS、Windows 10。今天我要讲的是macOS的沙盒。
我们先来看看沙盒的定义。在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.
在计算机安全中,沙盒是一种隔离应用程序的机制,通常致力于抑制系统故障或者软件故障的扩散。它通常用于执行未测试的或者不受信任的,来自于未认证或者不受信任的第三方、供应商、用户或者网站的应用程序和代码,以免主机和操作系统受到破坏。
在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.
应用沙盒是一种由macOS提供的内核级别的访问控制技术。它是为了控制系统或者用户数据受到的破坏而设计的(当一个应用程序受到劫持的时候)。
这两个定义说的意思差不多,就是对应用进行隔离。包括运行环境和用户数据。具体就是说,第一次打开应用,你上不了网,你拍不了照片,你写不了文章。所有的一切你都需要用户授权。
为什么需要沙盒
答案是安全。现如今应用变得非常的复杂。而复杂的系统,不管你多么努力,制定多么严格的安全编程规范,测试得多么地充分,攻击者总能找到漏洞。沙盒的作用就是当你的应用受到攻击时,系统和用户数据受到的破坏能被控制在一定的范围内。
如何在沙盒环境下编程
在沙盒环境下,我们需要做一些权限相关的额外工作。
- 声明权限
- 获取权限
- 保存权限
- 重新获取权限
- 释放权限
1)声明权限
在macOS中,通过Entitlements来声明权限。创建xcode工程时,xcode会默认创建appname.entitlements的plist文件。开发者可以编辑这个文件,或者通过target的Capabilities标签配置。
在Capabilities只能声明部分权限,有些权限需要手工编辑.entitlement文件。
2)获取权限
声明了权限后,应用就可以使用相应的编程接口来实现所要的功能。例如,在用户Home目录读写文件。你可以通过NSFileManager提供的API来获取相应的路径。
swift
class NSFileManager
var homeDirectoryForCurrentUser: URL { get }
Objective-C
interface NSFileManager
@property(readonly, copy) NSURL *homeDirectoryForCurrentUser;
对于用户Home目录以外的文件,需要唤起NSOpenPanel来让用户选择,或者通过拖拽的方式将文件拖拽到应用界面中。通过阅读Introduction to Drag and Drop,你可以很快地实现拖拽功能。
3)保存权限
通过用户选择或者拖拽获取到的文件读写权限在应用关闭之后就会丢失。所以,需要一种方法来保存这个权限。macOS给我们提供了Security Scope Bookmark。NSURL提供了对应的接口来创建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的对象表示。NSData可以持久化到文件,也可以通过IPC等方式发送给其他进程。在应用重启或者另一个进程接收到NSData对象后,即可重新获取权限。如果想要持久化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)重新获取权限
Security Scope Bookmark中保存了NSURL的相关信息。我们可以通过NSURL提供的接口重新获得保存的NSURL对象。
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;
通过NSURL重新获取权限。
swift
class NSURL
func startAccessingSecurityScopedResource() -> Bool
Objective-C
interface NSURL
- (BOOL)startAccessingSecurityScopedResource;
5)释放权限
一旦不再需要使用权限,尽快释放权限。
swift
class NSURL
func stopAccessingSecurityScopedResource()
Objective-C
interface NSURL
- (BOOL)stopAccessingSecurityScopedResource;
坑
人在江湖飘,哪能不挨刀。掉坑总是难免的。NSURL提供了一个很容易让人误用的接口。当我第一眼看到这个接口时,我就把它当作持久化bookmark的接口了。而且这个接口也确实能持久化bookmark数据。然而当你重启应用重新得到NSURL对象后会发现,无法获取权限。
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;
实际上,这个接口是用于创建别名链接的。
一些建议
作为开发者,出于降低项目风险和减少工作量的目的。我的建议是尽可能禁用沙盒机制。因为沙盒这个机制还处于发展阶段。权限控制的粒度和编程接口都还没稳定。
可以不开启的场景
- 内部工具
- 企业应用
- 非官方应用商店渠道销售的应用
必须开启沙盒的场景
- 官方应用商店销售的应用
- 手机应用
总结
应用沙盒提供了一种隔离机制,使得被劫持的应用能够造成的破坏被限制在一定的范围内。同时,沙盒机制是一把双刃剑。他在提供保护的同时,也加重了开发者的负担,产生额外的辅助数据,占用磁盘空间。甚至在沙盒环境下,部分功能无法实现。
题外话
说到底,这是个信任问题。软件之间、系统之间、人与人之间需要一种信任。当信任缺失的时候,所有参与到其中的任何一方都要付出代价。