Mac 开发(一) 苹果沙盒机制sandbox简介

@[TOC]php

Mac 开发(一) 苹果沙盒机制sandbox简介

mac沙盒实战demo点击这里下载:【MacFileAccessInSandboxgit

1 Mac sandbox简介

1.1 关于应用程序沙盒

  • 什么是沙盒?

维基百科的解释:github

  1. 在计算机安全领域,沙盒(英语:sandbox,又译为沙箱)是一种安全机制,为运行中的程序提供的隔离环境。一般是做为一些来源不可信、具破坏力或没法断定程序意图的程序提供实验之用
  2. 沙盒一般严格控制其中的程序所能访问的资源,好比,沙盒能够提供用后即回收的磁盘及内存空间。在沙盒中,网络访问、对真实系统的访问、对输入设备的读取一般被禁止或是严格限制。从这个角度来讲,沙盒属于虚拟化的一种。
  3. 沙盒中的全部改动对操做系统不会形成任何损失。一般,这种技术被计算机技术人员普遍用于测试可能带毒的程序或是其余的恶意代码

在OS X以及IOS系统中限制了进程对一些资源的访问权限,例如网络、某些特殊路径、文件的读写等等,限定了进程的一些行为,从而保证进程不会作出超越权限的操做。swift

下面看看苹果对沙盒的解释:api

应用沙箱是macOS提供的一种访问控制技术,在内核级别执行。若是应用程序受到威胁,它的目的是防止系统和用户数据受到损害。经过Mac应用商店发布的应用必须采用应用沙箱。经过开发者ID在Mac应用商店外签名和分发的应用程序也能够(在大多数状况下应该)使用应用沙箱。xcode

Mac OSX自从10.6系统开始引入沙盒机制,规定发布到Mac AppStore的应用,必须遵照沙盒约定。沙盒对应用访问的系统资源,硬件外设,文件,网络,XPC,都作了严格的限制,这样能防止恶意的App经过系统漏洞,攻击系统,获取控制权限,保证了OSX系统的安全。安全

沙盒至关于给每一个App一个独立的空间,你只能在本身的小天地里面玩。要获取本身空间以外的资源必须得到受权。bash

那么苹果为啥要限制提交Appstore的app必须使用苹果的沙盒机制呢?服务器

1.2 为啥要用沙盒机制

复杂的系统老是会有漏洞,而软件的复杂性只会随着时间的推移而增长。不管您多么当心地采用安全编码实践并防范bug,攻击者只须要经过一次防护就能够成功。虽然应用沙箱不能阻止对您的应用程序的攻击,但它能够最小化一个成功的攻击所形成的伤害。网络

非沙箱应用程序拥有运行该应用程序的用户的所有权限,并能够访问用户能够访问的任何资源。若是该应用程序或与之连接的任何框架包含安全漏洞,攻击者可能会利用这些漏洞来控制该应用程序,这样,攻击者就能够作用户能够作的任何事情。

为了缓解这个问题,应用沙箱策略有两个方面:

  1. 应用沙箱容许你描述你的应用如何与系统交互。而后,系统授予应用程序完成工做所需的访问权限,仅此而已。
  2. 经过打开和保存对话框、拖放和其余熟悉的用户交互,应用沙箱容许用户透明地授予应用额外的访问权限。

sandbox机制

2 沙盒原理

  1. 沙盒的大体工做流程入下图所示:
    沙盒工做流程
  1. 1进程尝试进行一次系统调用(system call),调用内核功能。
  2. 二、3MAC层须要根据该进程的安全策略判断这次系统调用是否能够执行。
  3. 四、五、六、七、八、9若是存在策略的话,经过sandbox.kext(hook函数)和AppleMatch.kext(沙盒的profile解析)两个内核扩展实现权限的检查。
  4. 10返回调用结果

与沙盒系统相关的模块大体以下:

  1. .libSystem.dylib: 提供sandbox_initsandbox_free_error等函数。
  2. libSandbox.dylib: 提供解析,编译,生成*.sb的沙盒profile的函数。
  3. sandbox.kext:提供了system callhook函数
  4. AppleMatch.kext:提供了解析profile的函数
  1. 结构图大体以下:

sandbox结构图

  1. 沙盒的工做流程大体能够总结为:
  1. 经过sandbox_init初始化某沙盒策略脚本并编译为二进制文件
  2. 在进程进行system call时,经过TrustedBSD提供的hook模块,利用Sandbox.kext提供的system call hook函数,结合沙盒策略进行判断,该进程是否有权限执行该system call

3 xcode中开启沙盒权限

3.1 XCode Capabilities 开启Sandbox权限

3.1.1 Capabilities 开启Sandbox权限

应用开发完成提交到App Store时,必须进行沙盒化。切换到工程target设置Tab的Capabilities中。

  • 第一项就是App Sandbox开关,点击ON,表示应用使用沙盒。

App Sandbox开关,点击ON,表示应用使用沙盒
上图的选项的一些解释以下:

  1. Network:网络访问控制
  1. Incoming Connections (Server): 应用作为Server对外提供HTTP,FTP等服务时须要打开。若是你的App担任服务器角色,须要链接通讯须要开启此权限。
  2. Outgoing Connections (Client): 作为客户端,访问服务器时须要打开。若是你的App须要做为客户端进行socket链接通讯须要开启此权限。
  1. Hardware:硬件资源控 它包含下面这些子项:
  1. Camera: 若是你须要开启摄像头功能,勾选此项。
  2. Audio Input: 若是你须要获取音频 输入权限(如麦克风),勾选此项。
  3. USB : 若是你须要使用USB传输文件,须要开启此功能 4: Printing: 若是你须要打印文件里面的内容,须要开启此功能
  1. App Data:获取系统的联系人,位置,日历服务时须要打开
  1. Contacts: 若是要访问联系人,须要勾选此项
  2. Location: 若是须要定位,须要勾选此项。
  3. Calendar: 若是须要访问日历,须要勾选此项。
  1. File Access:文件和用户目录的访问控制,分为禁止none,只读,读写3类
  1. User Selected File:文档类应用或者须要用户选择打开某个文件时,须要选择合适的访问权限.
  2. Downloads Folder: 若是须要访问当前用户 Downloads文件夹,须要勾选此项,能够设置为只读,或者可读可写
  3. Pictures Folder: 若是须要访问当前用户 Pictures文件夹,须要勾选此项,能够设置为只读,或者可读可写
  4. Music Folder: 若是须要访问当前用户 Music文件夹,须要勾选此项,能够设置为只读,或者可读可写
  5. Movies Folder: 若是须要访问当前用户 Movies文件夹,须要勾选此项,能够设置为只读,或者可读可写

特别注意:若是应用中不须要的权限项,一概不要打开。不然App Review团队会拒绝你的应用上架.

3.1.2 Entitlements 直接变xml,开启Sandbox权限

实际上,在沙盒中每一个须要访问权限的项都对应一个key,对应的value,YES 或 NO表示是否容许访问。当你选择了项后,都会记录在一个扩展名为.entitlements的plist 的文件中,以下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.assets.movies.read-write</key>
	<true/>
	<key>com.apple.security.assets.music.read-only</key>
	<true/>
	<key>com.apple.security.assets.pictures.read-only</key>
	<true/>
	<key>com.apple.security.cs.allow-dyld-environment-variables</key>
	<true/>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
	<true/>
	<key>com.apple.security.cs.disable-executable-page-protection</key>
	<true/>
	<key>com.apple.security.cs.disable-library-validation</key>
	<true/>
	<key>com.apple.security.device.audio-input</key>
	<true/>
	<key>com.apple.security.device.camera</key>
	<true/>
	<key>com.apple.security.device.usb</key>
	<true/>
	<key>com.apple.security.files.bookmarks.app-scope</key>
	<true/>
	<key>com.apple.security.files.downloads.read-write</key>
	<true/>
	<key>com.apple.security.files.user-selected.read-write</key>
	<true/>
	<key>com.apple.security.network.client</key>
	<true/>
	<key>com.apple.security.network.server</key>
	<true/>
	<key>com.apple.security.personal-information.photos-library</key>
	<true/>
	<key>com.apple.security.print</key>
	<true/>
	<key>com.apple.security.temporary-exception.apple-events</key>
	<array>
		<string>com.apple.itunes</string>
	</array>
	<key>com.apple.security.temporary-exception.files.absolute-path.read-write</key>
	<true/>
	<key>com.apple.security.temporary-exception.shared-preference.read-only</key>
	<array>
		<string>com.apple.iphoto</string>
		<string>com.apple.photobooth</string>
		<string>com.apple.photos</string>
	</array>
</dict>
</plist>

复制代码

用plist属性显示以下:

用plist属性显示

应用打包时会对这个文件进行签名。 当应用运行期间要获取某个权限时,系统都会经过.entitlements去检查应用是否有受权,若是没有就拒绝访问。

3.2 Mac 文件沙盒化实战

3.2.1 没有使用sandbox使能够任意访问文件

mac沙盒实战demo点击这里下载:【MacFileAccessInSandbox

  • 首先咱们建立一个Mac app工程,我这里选择的是Swift版本的工程。

    新建Swift工程

  • 添加一些调用api的UI控件

    添加一些调用api的UI控件

  • 链接绑定UI事件

    链接绑定UI事件

  • 实现一个简单的打开文件功能以下:

extension ViewController {
    fileprivate func openFile() {
        let openPanel = NSOpenPanel()
        openPanel.prompt = "Open"
        openPanel.allowsMultipleSelection = true
        openPanel.canChooseDirectories = true
        openPanel.canChooseFiles = true
        openPanel.resolvesAliases = true
        openPanel.nameFieldLabel = "Open File"
        openPanel.title = "Open"
        openPanel.allowedFileTypes = ["txt"]
        openPanel.begin { (response) in
            if response == .OK {
                let urls = openPanel.urls
                for url in urls {
                    UserDefaults.standard.set(url, forKey: LastSaveFilePathKey)
                    UserDefaults.standard.synchronize()
                    self.filePathField.stringValue = url.path
                    
                    let text = try? String(contentsOf: url, encoding: .utf8 )
                    self.textV.string = text ?? ""
                }
            }
        }
    }
}
复制代码

打开文件

  • 接下来咱们实现保存文件功能
/// 保存文件
    fileprivate func saveFile() {
        guard let lastSelectUrl = UserDefaults.standard.value(forKey: LastSaveFilePathKey) as? URL else {
            return
        }
        
        let text =  textV.string
        
        try? text.write(to: lastSelectUrl, atomically: true, encoding: .utf8)
        
    }
复制代码

接下来咱们验证一些保存文件功能: 没有修改内容前:

没有修改内容前
查看真实文件:
查看真实文件
咱们在不使用sandbox的功能的时候,是能够正常读写文件的,以下是我先把sandbox功能关闭:

注意:
有两种方式关闭sandbox:

  1. 直接在Capabilities中将Sandbox删除
  2. 不使用证书签名的方式编译,沙盒机制只会在证书签名的方式下才生效。

这里我采用的是第二种方式关闭sandbox,以下图所示,先在buildsetting中搜索Signing选项,找到 “Code Signing Identity” 双击把里面的内容删除。

在这里插入图片描述
或者选择Other
选择Other
删除里面内容

  • 咱们先来看看关闭sandbox的状况: 关闭沙盒后,咱们能够随意访问任意的有权限的目录的文件,以下面是我在关闭沙盒的状况下能够访问电脑桌面的任意文件,这里我选择的是:/Users/kongyulu/Desktop/笔记/test.txt

关闭沙盒后能够修改沙盒外的文件

  • 这里我增长了再次启动app时读取上一次保存路径的文件功能,以下:
fileprivate func initData() {
        guard let path = UserDefaults.standard.value(forKey: LastSaveFilePathKey) as? String else {
            return
        }
        filePathField.stringValue = path
        let url = URL(fileURLWithPath: path)
        do {
            let text = try String(contentsOf: url)
            textV.string = text
        } catch {
        }
    }
复制代码

关闭沙盒的状况下,能够正常访问,并读取到给的路径的文件的内容,以下:

关闭沙盒的状况下,能够正常访问,并读取到给的路径的文件的内容

  • 接下来咱们来加上证书签名,让Sandbox生效,这个时候,咱们会发现我刚刚保存的那个沙盒以外的路径在APP启动时不能读取到txt文本的内容了,点击保存按钮也没法将文件修改,这是为啥呢?

苹果爸爸规定:咱们程序刚刚启动的时候是不能去访问沙盒以外的路径的,苹果默认只容许访问APP它本身沙盒的内容是不受限制的,若是要访问沙盒以外的路径是须要用户受权的,咱们能够调用NSOpenPanel 类 弹出一个对话框给用户去选择他要打开的文件,当用户点击了OK按钮,则表示用户已经受权了这个文件,这个时候咱们须要经过bookmark去保存这个已经受权的文件路径信息(咱们能够保存到系统偏好Prefer里面,使用UserDefaults.standard.set(url, forKey: LastSaveFilePathKey)),下次App启动的时候,直接从bookmark获取到URL ,而后调用allowedURL = [NSURL URLByResolvingBookmarkData:bookmarkData options:NSURLBookmarkResolutionWithSecurityScope|NSURLBookmarkResolutionWithoutUI relativeToURL:nil bookmarkDataIsStale:&bookmarkDataIsStale error:NULL]; 函数获取到受权的URL, 这个时候咱们去访问url路径下的文件,能够直接访问,它原理用户给定的是什么权限,如今就能够获得什么权限。不须要再次弹出对话框让用户去选择路径受权了。

  • 那么问题来了,怎么去保存这个bookmark, 获取它呢,这里我在个人demo里面都给出了方法,能够本身去查看这个demo: 【MacFileAccessInSandbox

主要涉及到的代码是:

- (BOOL)requestAccessPermissionsForFileURL:(NSURL *)fileURL persistPermission:(BOOL)persist withBlock:(SandboxFileSecurityScopeBlock)block {
    NSParameterAssert(fileURL);
    
    NSURL *allowedURL = nil;
    
    // standardize the file url and remove any symlinks so that the url we lookup in bookmark data would match a url given by the askPermissionForURL method
    fileURL = [[fileURL URLByStandardizingPath] URLByResolvingSymlinksInPath];
    
    // lookup bookmark data for this url, this will automatically load bookmark data for a parent path if we have it
    NSData *bookmarkData = [self.bookmarkPersistanceDelegate bookmarkDataForURL:fileURL];
    if (bookmarkData) {
        // resolve the bookmark data into an NSURL object that will allow us to use the file
        BOOL bookmarkDataIsStale;
        allowedURL = [NSURL URLByResolvingBookmarkData:bookmarkData options:NSURLBookmarkResolutionWithSecurityScope|NSURLBookmarkResolutionWithoutUI relativeToURL:nil bookmarkDataIsStale:&bookmarkDataIsStale error:NULL];
        // if the bookmark data is stale we'll attempt to recreate it with the existing url object if possible (not guaranteed) if (bookmarkDataIsStale) { bookmarkData = nil; [self.bookmarkPersistanceDelegate clearBookmarkDataForURL:fileURL]; if (allowedURL) { bookmarkData = [self persistPermissionURL:allowedURL]; if (!bookmarkData) { allowedURL = nil; } } } } // if allowed url is nil, we need to ask the user for permission if (!allowedURL) { allowedURL = [self askPermissionForURL:fileURL]; if (!allowedURL) { // if the user did not give permission, exit out here return NO; } } // if we have no bookmark data and we want to persist, we need to create it if (persist && !bookmarkData) { bookmarkData = [self persistPermissionURL:allowedURL]; } if (block) { block(allowedURL, bookmarkData); } return YES; } 复制代码
  • 这个函数是先去这个请求的路径是否已经保存了,若是保存了是否能够得到它的bookmark, 若是能获取这个bookmkark,则经过[NSURL URLByResolvingBookmarkData:] 这个方法去获取受权的URL,得到后就能够直接访问路径的文件了,若是没有受权或者保存过bookmark,则重新去弹框,让用户受权。

  • 获取受权路径后,须要调用startAccessingSecurityScopedResource 开启访问权限,在使操做完文件后,须要调用stopAccessingSecurityScopedResource 关闭访问权限,防止恶意程序直接访问文件。

- (BOOL)accessFileURL:(NSURL *)fileURL persistPermission:(BOOL)persist withBlock:(SandboxFileAccessBlock)block {
    NSParameterAssert(fileURL);
    NSParameterAssert(block);
    
    BOOL success = [self requestAccessPermissionsForFileURL:fileURL persistPermission:persist withBlock:^(NSURL *securityScopedFileURL, NSData *bookmarkData) {
        // execute the block with the file access permissions
        @try {
            [securityScopedFileURL startAccessingSecurityScopedResource];
            block();
        } @finally {
            [securityScopedFileURL stopAccessingSecurityScopedResource];
        }
    }];
    
    return success;
}
复制代码

3.2.2 Sandbox下访问文件的坑点

相关文章
相关标签/搜索