摩訶不思議Androidパーミッション

エミュレーターでの検証なので、実機と動作が違う可能性があります。
※確認したのはエミュレーターの1.6・2.1・2.2・2.3.1・2.3.3・4.0.3のみで、試していないバージョンの動作が特殊である可能性があります。
※開発者向けの情報です。利用者はあまり気にする必要はありません。
※内容が間違っている可能性があります。私が確認して把握している仕様と、後ほど参考資料として出すpdfの内容*1とで辻褄が合っていないところがありますので。もしかするとtargetSdkVersionによって挙動が違ったりする可能性もあります。
※2012/06/25に少し加筆。イメージ画像追加、若干の微修正、「それでも残るアンインストールの問題」の「その環境では権限Xの定義が消えてしまいます」以降の内容修正。

パーミッションはインストール順によって面倒が一杯

Androidパーミッションの仕組みは「先に定義したもん勝ち」』でインストール&アンインストール順で色々面倒くさい事だらけです。その事について書かれてあったブログがありました。
Android の組込みアプリ以外のパーミッションの取り扱い
http://subtech.g.hatena.ne.jp/cho45/20100205/1265367008
とりあえずリンク先の「A → B とインストール」「B → A とインストール」部分だけ先に読んで、それからここに戻って続きを読んでください。


実は「B → A とインストール」の動作について、最近のAndroidは挙動が違ってます。(リンク記事は1.6時点)
では次の表をごらんください。


『未定義の権限Xについてuses-permissionしているアプリUが、権限Xを定義するアプリDを後からインストールした場合、OSがアプリUのuse-permissionを有効にするか?』の検証結果。

動作OSバージョン \ protectionLevel signatureOrSystem signature dangerous normal
1.6 × × × ×
2.1 × × × ×
2.2 × ×
2.3.1 × ×
2.3.3 × ×
4.0.3 × ×


何じゃそりゃーって感じですが、signature/signatureOrSystemだけ挙動が変わったようです。
詳しく調べたところ、権限を時間差でgrantするだけでなくて既存アプリの権限剥奪もやってくれるようです。
動作イメージを図にしてみる以下のような感じです。各アプリがインストール済みで、権限定義するアプリが後からインストールされた時の動作シナリオです。


↓バージョンによる変化の無いnormal/dangerousの挙動↓


↓signature/signatureOrSystemの複雑な挙動↓


Androidのpermission(権限)のおさらい

  • 権限は先に定義したもの勝ち
  • 権限を使用するにはuses-permissionの宣言をアプリのAndroidManifest.xmlに書かなければならない。
  • 同じ権限を定義したアプリを後からインストールしても、最初の権限定義しかOSに認められない
  • 未定義の権限をuses-permissionしても、使用許可はその時点では与えられない。
  • 特定コンポーネント(Activity・ContentProvider・Service・BroadCast等)のアクセス制御に権限が使われる。
  • アクセス時に権限が必要と定義されたコンポーネントには、uses-permissionに使用許可を与えられていないとアクセスに失敗する(SecurityException)。
  • 権限にはprotectionLevelという概念がある。
  • protectionLevel=signatureは『同じ署名がされているアプリのみuses-permissionを許可』する。
  • protectionLevel=signatureOrSystemは、signatureに加えてSystemアプリにも許可してあげる権限。まず使わない。
  • protectionLevel=normal/dangerousは、インストール時に権限使用を確認されるだけで、アプリの署名は関係ない。
  • uses-permissionの許可は、uses-permissionしてるアプリのインストール時点で行う。
  • 権限を定義したアプリをアンインストールすると、権限がシステムから削除される
  • しかし権限を定義したアプリをアンインストールしても、uses-permissionに対する許可は消えない
  • 権限を定義したアプリをアンインストールする時、同じ権限を定義したアプリを後からインストール済みであっても、その権限定義が代わりに有効になったりはしない
  • protectionLevel=signatureの署名チェックは、uses-permissionしてるアプリのインストール時のみ実行時に署名が同じかチェックするものでは無い
  • 未定義の権限をuses-permissionしているアプリは、インストール時の確認画面に権限使用する事を表示されない
  • protectionLevelがsignatureまたはsignatureOrSystemの権限をuses-permissionしているアプリは、インストール時の確認画面に権限使用する事を表示されない
  • protectionLevelがnormalまたはdangerousの権限をuses-permissionしているアプリは、インストール時の確認画面に権限使用する事を表示される
  • 基本的に、通常アプリの開発で使うべき独自定義権限は、protectionLevelがsignatureであるべき
  • アクセス制限したコンポーネントへのアクセスは、『コンポーネントを内蔵しているアプリ自身』であればuses-permission無しにアクセス出来るものがある*2


さて上記のおさらい、全部パーフェクトに把握されてた人はいらっしゃったでしょうか? かくいう私もつい最近まで上記の半分くらいは知らずに過ごしてましたよ……。

Android OSにまかせきりだと、アクセスされ放題!

前項の「おさらい」にあったように、権限の定義は先着順です。
インストール順次第で、コンポーネントのアクセスが簡単に可能になります。
次の例を見てみましょう。

  • 「AttackApp」……権限Xの定義をして、権限Xのuses-permission宣言したアプリ(攻撃側)
  • 「ProtectedApp」……権限Xの定義をして、コンポーネントのアクセスに権限Xを要求するアプリ(攻撃される側)


このケースで「AttackApp」→「ProtectedApp」の順にインストールされると、「ProtectedApp」内のコンポーネントは丸裸に出来ます。超簡単に攻撃出来ますね!
ちなみに「AttackApp」での権限XがprotectionLevel=signatureになってれば「AttackApp」インストール時に何も表示されません。独自権限についてユーザーが自衛する事なんて絶対無理ですね!
この辺は最初に紹介したリンク記事の『C → A → B とインストール』*3と同じ話ですね。


この為、先日JSSECにて公開されたpdf*4では「自社限定公開」用の対策として、「権限を定義したアプリをチェックせよ!」と書かれています。
本来の定義通りのprotectionLevelになっているか、権限を定義したアプリの『署名のフィンガープリント』が想定通りか等が書かれてますね。
その対策なら「AttackApp」が勝手に権限を定義している事が判明するので、防御が出来るという仕組みなのです。
しかし、まだそれでは対策不足なケースがあったりします。

実は2.1以前のprotectionLevel=signatureには抜け穴がある。

『権限を定義したアプリの署名をチェックする』対策をしたアプリであっても『署名が異なる攻撃アプリ』から、protectionLevel=signatureの権限にアクセスする方法があります。
今度は次のような例を考えましょう。

  • 「AttackApp01」……権限Xのuses-permission宣言したアプリ(攻撃側)
  • 「AttackApp02」……権限Xの定義をしているアプリ(攻撃側)
  • 「ProtectedApp」……権限Xの定義をして、コンポーネントのアクセスに権限Xを要求するアプリ(攻撃される側)

インストール順が「AttackApp02」→「AttackApp01」→「ProtectedApp」である場合、前項と同じ「自社限定公開」用の対策をすれば『権限定義しているアプリの署名が違う』と判明し防御する事が出来ます。
しかし

  1. 「AttackApp02」をインストール(権限Xが定義される)
  2. 「AttackApp01」をインストール(権限Xの使用が許可)
  3. 「AttackApp02」をアンインストール(権限Xの定義が消えるが、AttackApp01の権限X使用は許可されたまま
  4. 「ProtectedApp」をインストール(権限Xが定義される)

という流れであった場合、権限を定義してるアプリは「ProtectedApp」となります。これでは前項で紹介したJSSECの「自社限定公開」用の対策をすり抜けてしまいますね! ピンチです!!


しかも「AttackApp01」と「AttackApp02」が同じ署名で、かつ権限XをprotectionLevel=signatureで定義していれば、「AttackApp01」のインストール画面でも権限Xのuses-permissionしている事はユーザーに判断出来ません。
2.1以前も含めて完璧に対応するには、JSSECのpdfの「パートナー限定公開」の手法を組み合わせて、呼び出し元アプリの署名チェックを「自社限定公開」の仕組みを強化する必要があるでしょう*5
またインストール済みアプリのuses-permission宣言をすべてチェックし、「ProtectedApp」へアクセスする権限を使用してるアプリの署名をチェックするという方法も採ることが出来そうです。ただし処理が重くなってしまいそうなのが難点ですが…。

で、結局2.2〜4.0.3は?

実はAndroid2.2以降の環境でsignature/signatureOrSystemで権限を定義する限りは、前項の問題に対する追加対策は不要だったりします。Android OS側で、最初の項目で表に書いたように挙動が変更されているからです。

  • 「権限使用がまだ許可されてないuses-permissionへの許可割当」が行われる
  • 「権限使用許可されたuses-permissionの再チェック」が行われる

という仕様ですね。*6


その為先程と同じ手法でも

  1. 「AttackApp02」をインストール(権限Xが定義される)
  2. 「AttackApp01」をインストール(権限Xの使用が許可)
  3. 「AttackApp02」をアンインストール(権限Xの定義が消えるが、AttackApp01の権限X使用は許可されたまま)
  4. 「ProtectedApp」をインストール(権限Xが定義され、その際AttackApp01の権限X使用がチェックされて(署名が違う為)権限Xは剥奪される

となります。
ちなみにsignature/signatureOrSystemについては上記の通りですが、dangerous/normalについては2.2以前と挙動は変わっていませんので、uses-permissionの許可が残るままなので要注意です。まあそもそも(通常のアプリでは)非推奨のものですし、protectionLevelには素直にsignatureを使う事をお勧めします。

それでも残るアンインストールの問題

(※呼び出し元アプリや権限定義アプリの署名チェック処理は実装した上での解説です)


signature permissionについて2.2から前述の仕様変更をされた為、ターゲットを2.2以降に限定する場合はアプリのインストール順の縛りをかなり緩く出来ます。
(バッド?)ノウハウとして「全ての連携するアプリで権限を定義する」というルール*7に従わなくても、「権限定義担当のアプリがインストールされるのを待つ」処理を組み込む事で以下のようなアプリは自由な順番でインストール可能です。

  • 「ProtectedMainApp」……権限X、権限Yを定義。権限X、権限Yのuse-permission宣言。
  • 「ProtectedSubApp01」……コンポーネント使用に権限Xを要求
  • 「ProtectedSubApp02」……コンポーネント使用に権限Yを要求


さらに以下の様なMain-subとは違った対等な構成のアプリであっても、順序を気にせずインストールが可能です。

  • 「ProtectedMainApp01」……権限Xを定義。コンポーネント使用に権限Xを要求。権限Y、権限Zのuse-permission宣言。
  • 「ProtectedMainApp02」……権限Yを定義。コンポーネント使用に権限Yを要求。権限X、権限Zのuse-permission宣言。
  • 「ProtectedMainApp03」……権限Zを定義。コンポーネント使用に権限Zを要求。権限Y、権限Yのuse-permission宣言。


しかしながら解決が難しいのが「アンインストール」の問題。「独自定義PermissionはComponentの提供側アプリだけでなく利用側アプリでも定義する」のルールに従った形で考えてみます。

  • 「ProtectedApp01」……権限Xを定義。コンポーネント使用に権限Xを要求。
  • 「ProtectedApp02」……権限Xを定義。コンポーネント使用に権限Xを要求。権限Xのuse-permission宣言。
  • 「ProtectedApp03」……権限Xを定義。権限Xのuse-permission宣言。


このケースでもインストール順序は気にしなくて大丈夫です。
しかし「ProtectedApp03」が何らかの理由で多くのユーザーがアンインストールしたがるアプリであって、なおかつ最初のインストールが「ProtectedApp03」だった場合、その環境では権限Xの定義が消えてしまいます
ただしuse-permissionの許可はギリギリ残ります。(^_^;) …しかしながら割当済みの許可が想定通りのものなのかOSの動作だけではチェック不可能な為、アプリ内で呼び出し元の署名チェックが必須となります。また『OS側のアクセス制限が残ってくれる』と言い換えれば有り難い事だとも言えなくは無いのですが、JSSECのpdfで書いているままの「権限を定義したアプリのチェック」「権限の内容チェック」を実装してると、残った「ProtectedApp01」「ProtectedApp02」の使用に不具合が出かねませんので注意が必要です。


『ファイナルアンサーはこれだ!』とまでは断言しがたいのですが、今のところ

  • 『呼び出し元アプリ』の(署名)は常にチェック。
  • 権限の定義状態はチェックしても良いが、全ての関連アプリで権限定義をしておく場合はアンインストールによる未定義を想定する。
  • なるべくならインストール順を固定し、権限は必ず定義されているものとしてチェックを行う。

という形で実装するのが良さそうな雰囲気です。

*1:JSSECの「セキュアコーディングガイド 2012年6月1日版」内の「5.2.2.3. 独自定義のDangerous Permissionは利用してはならない」について「うまくいかないケース」が私の理解してる内容ではあり得ない話になってました。

*2:Service等はuses-permission無しではアクセス不可。詳細は調査するの面倒なのでパス…。

*3:「(protectionLevel="normal" == ユーザに確認がでないパーミッション)」という記述が間違ってると思うのだが…。当方の1.6環境ではアプリBインストール時に(若干の操作が必要だが)使用する権限に表示される。

*4:http://www.jssec.org/news/index.htmlAndroid アプリのセキュア設計・セキュアコーディングガイド』【6月1日版】

*5:呼び出し元の概念が難しいコンポーネントがあったりするんですけどね…。

*6:ところでこういうOSの仕様変更、どこで確認出来るんでしょうか?? http://developer.android.com/sdk/android-2.2-highlights.html とかには書いてないですよね…??

*7:JSSECのpdf「5.2.2.4. 独自定義PermissionはComponentの提供側アプリだけでなく利用側アプリでも定義する」より。