macOSアプリに埋め込むバイナリをサンドボックス化....できた!


はじめに

[当初公開した記事に誤りがあったため、修正しました。読んで惑わされてしまった人には謹んでお詫びいたします]

App storeで自作アプリを公開しようと思ったら色々手こずったことについての備忘録。そのうち誰かの役に立つかもと思い、書き留めておく。

App storeで自作アプリを公開するには、アプリをサンドボックス化しなくてはならないとのこと。特に手こずったのは、アプリに埋め込むサードパーティ製のバイナリファイルもサンドボックス化しなくてはならなかった点だ。

macOS 10.14.5 Xcode 11.3.1を使用しました。

サンドボックス化とは

要するにサンドボックス化とは、サンドボックス環境下でアプリが動作するようにすることだ。サンドボックス環境下ではアプリの動作が開発者が意図した動作に制限される。サンドボックス化においては、開発者は、アプリに必要な最小限の機能を実行可能なように設定する。

サンドボックス化していないアプリは、アプリユーザーができることは何でもしてしまい得る。このようなアプリにセキュリティホールがあった場合、悪意ある第三者がこれを利用して、アプリを使って任意の機能を何でも実行できてしまうリスクがある。このリスクを低減するのが、アプリをサンドボックス化する目的だ。

アプリのサンドボックス化

アプリのサンドボックス化はそれほど難しくない。Xcodeでプロジェクトアイコンを選択して、「Target」を選択した状態で、Capabilityタブを選び、App Sandboxをオンにして、アプリが真に必要とする機能だけをオンにしてやれば良い。デフォルトではこれら機能は全てオフになっている。

設定項目は、以下のようだ。

ここで設定すると、[アプリ名].entitlementsファイルが自動生成され、Xcodeプロジェクトに追加される。
あとはビルドするだけでアプリはサンドボックス化される。

ただしサンドボックス化すると、例えば、アプリが、アプリバンドル内にファイルを一時保存するといったことができなくなる。もしこのような処理をしているならコードを書き換える必要がある。この場合、サンドボックス化したアプリ用に作成された特定のディレクトリを、一時ファイルの置き場として利用することができる(後述)。

[アプリ名].entitlementsファイルの中身

Sandboxの設定を変更すると、[アプリ名].entitlementsファイルの中身が自動的に置き換わる。

中身はxml形式だが、Xcodeで開くと、以下のようになっている。


これをテキストエディタで開くと以下のようだ。

[アプリ名].entitlementsファイルは、コード署名時に使われる

このファイルへのパスは、Build Settingsの中のSigningの中の、Code Signing Entitlementsで参照されている。つまりSandbox化に使われる情報は、(アプリ本体のバイナリの)コード署名の時に使われるということだ。

コード署名は、配布時にコードにつける署名であり、そのコードが開発者によって作成されたものであること(改ざんされていないこと)を確認するために必要なものだ。

バイナリファイルのサンドボックス化

ここまでは良い。問題は、自作アプリに、サードパーティが作ったバイナリファイルを埋め込みたいのだが、このためにArchive->Validationが通らなかったことだ。

出ていたエラーは

App Store Connect Operation Error
App sandbox not enabled. 
The following executables must include the "com.apple.security.app-sandbox" entitlement with a Boolean value of true in the entitlements property list: 
[(xx.xx.xxxx.xx.XXXX.pkg/Payload/AppName.app/Contents/Resources/binary1",
(xx.xx.xxxx.xx.XXXX.pkg/Payload/AppName.app/Contents/Resources/binary2")] 
Refer to App Sandbox page at
 https://developer.apple.com/documentation/security/app_sandbox
 for more information on sandboxing your ap.

というものだ。

バイナリファイルに設定するentitilement

バイナリにつけるための.entitlementsファイルを準備する。

アプリにバイナリファイルを埋め込んでおいて、アプリからバイナリを実行する場合、アプリを親プロセスとすると、バイナリファイルは子プロセスだ。子プロセスに設定して良いentitlementは2つだけだ(これ以外設定してはいけないというのはここに書いてある)。この2つの設定は、Xcodeで見ればこのようだ:


テキストエディタで見れば、このようだ:

<?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.inherit</key>
    <true/>
</dict>
</plist>

つまりサンドボックスを使用することを明示するために
com.apple.security.app-sandboxをtrueに設定。
親プロセスのsandbox設定を引き継ぐために
com.apple.security.inheritをtrueに設定している。

子プロセスの方でcom.apple.security.inheritをtrueにしなかったり、他のentitlementを設定してしまったりすると、この子プロセスをNSTaskから使用しようとした場合に以下のエラーがコンソールアプリのユーザーレポートに出る。

Sandbox creation failed: Container object initialization failed.
failed to get bundleid for app "/Users/yoho/pathToBinary"

バイナリファイルにコード署名をつける

ターミナルで、entitlementを指定してコード署名をつける。
付け方は以下の通り。

codesign -f --entitlements /Users/yoho/binary.entitlements -s "Xxxxxx Yyyyyy (8DK4RRM7WW)" /Users/yoho/myProjects/ProjName/binary1

ここでbinary.entitlementsは、上記entitlementファイル(xml形式)だ。"Xxxxxx Yyyyyy (8DK4RRM7WW)"は、コード署名に使われる開発者の名前とIDだ。これらが何であるかは、キーチェーンアクセスで「自分の証明書」を見れば良い。この証明書は、AppleのDevelopperサイトから入手したものだ。

バイナリのコード署名を調べる方法

ターミナルで以下を実行

codesign -d --entitlements :- /Users/yoho/pathToBinary

entitlementの書式が有効かチェックする方法

ターミナルで以下を実行

plutil /Users/yoho/AppName.entitlements

もう1つの必要な設定

上の方法でバイナリをサンドボックス化できるが、このままでは親プロセスから実行できない。(ここにある記事が参考になった)。これはコード署名時に、親プロセスのentitlementに自動で以下を挿入するのがデフォルトになっているからだ。

<key>com.apple.security.get-task-allow</key>
<true/>

Build settingsのSigningのCode Signing inject Base EntitlementsをNOに設定してやる。


これでOKだ。

動作時

サンドボックス化したアプリが起動すると、/Users/yoho/Library/Containers/配下に、バンドルIDの名前がついたフォルダが作成される。inheritの設定がうまくできていない時は、子プロセスをNSTaskでlaunchした時に、子バイナリの名前のフォルダが作成されるようだ。

また、エラー発生時のエラーメッセージは、標準出力にも、エラー標準出力にもなく、コンソールアプリににしかでないことがあるようだ。

ファイルの置き場所

サンドボックス化されたアプリでも自由に読み書きできるフォルダ(applicationSupportDirectory)がある。そのフォルダへのパスは以下で調べることができる。

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *directory_1 = [paths firstObject];
// /Users/yoho/Library/Containers/[BUNDLE-ID]/Data/Library/Application Support

あるいはNSHomeDirectory()でパスを得るのも良い。BUNDLE-ID以下のどこを用いるべきなのかはよくわからなかった。

NSString *directory_2 = NSHomeDirectory()
// /Users/yoho/Library/Containers/[BUNDLE-ID]/Data

その他ファイルへのアクセスの設定

.entitlementsの設定は追加することができる。たとえば、特定のディレクトリへの読み書きを可能にできる。絶対パスか、ユーザーのホームフォルダからの相対パスで指定できる。
com.apple.security.temporary-exception.files.absolute-path.read-write
com.apple.security.temporary-exception.files.home-relative-path.read-write
Typeはアレイで、アレイの要素はファイル/フォルダパスの文字列だ。

後日記....
com.apple.security.temporary-exception.files.home-relative-path.read-writeに「/」を指定したらApp Storeからの配信時に審査に通らなかった。これを指定すると、ほとんどの場所にアクセスできるようになってしまうので、許可できないということか。

コード署名はどこにあるのか?

バイナリにコード署名をつけた場合に、この署名情報はどこにあるのだろうか?Xcodeでコマンドラインツールを作ると、コード署名されてしまうので、C言語で「Hello World」を表示するバイナリをgccで作成して、ファイルサイズを見ると8 KBであった。これにコード署名するとファイルサイズは18 KBとなった。

また実行ファイルを、署名前後でddxコマンドでダンプし、DIFFFで比較すると、コード署名の部分はバイナリファイルの後半にまとまって存在していることがわかった。

というわけで、バイナリファイルの後部にコード署名が追加されるらしいこと、この場合ではコード署名は10 KB程度のサイズであることが判明した。コード署名部分以外にも、全部で10バイトほど変更される箇所があるようだが、何の変更なのかはわからない。

終わりに

以下、打ち消し線を引いているが、blast+の最新バージョン2.11.0では、実行はできるもののサンドボックスが許可しない動作が含まれているためと思うが、正常には機能しなかった。blast+の古いバージョン2.2.31では無事に動作した(バージョン2.9.0まで動作する)。Xcodeをアップデートしたら最新のblast+でも動作するのかもしれない。

内包したバイナリ全てをサンドボックス化して、ようやくvalidationにパスし、機能するアプリにすることができた....と言いたいところだが、実は思った通りには機能していない。

埋め込みたいのはNCBIが公開しているblast+プログラム群だったのだが、この中のmakeblastdbというバイナリが、サンドボックス化するとdatabaseを作成してくれないという問題が解決できていない。

実行自体はでき、中途ファイルと思しき小さいファイル(ロック用のファイルと思われる)が作成されることから、おそらくサンドボックスが許可しない動作が含まれていて、そのために実行ができないようなのだ。com.apple.security.temporary-exception.files.absolute-path.read-writeでrootを指定しても駄目なので、ファイルの読み書きに関連するものではないと思われる。またXcodeのGUI上で設定可能な設定全てを有効にしてみたが、これでも駄目なので、何かよくわからない実行ファイルを使おうとしているのかもしれない。

標準出力:

Building a new DB, current time: 11/23/2020 16:02:15
New DB name:   /Users/yoho/Library/Containers/[BUNDLE-ID]/Data/Library/Application Support/blastdb
New DB title:  blastdb
Sequence type: Nucleotide
Deleted existing Nucleotide BLAST database named /Users/yoho/Library/Containers/[BUNDLE-ID]/Data/Library/Application Support/blastdb
Keep MBits: T
Maximum file size: 1000000000B

No volumes were created.

標準エラー出力:

Error: mdb_env_open: Operation not permitted

makeblastdbと関連ツールのソースコードはあるのだが、C++で書かれており、また巨大な他ツール群とバンドルされていることもあって、恥ずかしながら思うように追えず、何が問題なのか判然としない。

NCBIの開発者にメールで問い合わせるか、Appleのdevelopper forumに問い合わせるか、何れにせよ、英語で聞かないとなあ.....

良い知恵がありましたら、お貸しください。