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下訪問文件的坑點

相關文章
相關標籤/搜索