SELinux ポリシーを書き込む

Android オープンソース プロジェクト(AOSP)は、すべての Android デバイスに共通するアプリとサービスに堅牢な基本ポリシーを提供します。このポリシーは、AOSP のコントリビューターによって定期的に改訂されます。デバイス上の最終的なポリシーは、約 90~95% がコアポリシー、残りの 5~10% がデバイス固有のカスタマイズで構成されることが期待されます。この記事では、デバイス固有のカスタマイズおよびポリシー作成方法を主に説明し、さらにそれに伴う注意点も説明しています。

デバイスの起動

デバイス固有のポリシーを作成するには、次の手順に従います。

permissive モードで実行する

デバイスが permissive モードである場合、拒否はログに記録されますが適用されません。permissive モードは、次の 2 つの点で重要です。

  • permissive モードでは、ポリシーの導入によってその他の初期デバイス起動タスクが遅延することがありません。
  • 拒否が適用されると、他の拒否を確認できなくなります。たとえば、一般的にファイルへのアクセスには、ディレクトリ検索、ファイルのオープン、ファイルの読み取りが伴います。enforcing モードで発生するのはディレクトリ検索の拒否だけです。permissive モードではすべての拒否を確認できます。

デバイスを permissive モードにするには、カーネル コマンドラインを使用するのが最も簡単です。これはデバイスの BoardConfig.mk ファイル(platform/device/<vendor>/<target>/BoardConfig.mk)に追加できます。コマンドラインを変更したら、make clean、次に make bootimage を実行して、新しいブートイメージのフラッシュを行います。

その後、次のコマンドで permissive モードを確認します。

adb shell getenforce

グローバルな permissive モードにする期間は、2 週間が妥当です。 大部分の拒否を確認したら enforcing モードに戻り、発生したバグに対処します。拒否を生成し続けているドメイン、または本格的な開発がまだ進行中のサービスは、一時的に permissive モードにすることができますが、可能な限り早く enforcing モードに戻す必要があります。

enforcing モードを早期に適用する

enforcing モードでは、拒否はログに記録されたうえで適用されます。可能な限り早く、デバイスを enforcing モードにすることをおすすめします。デバイス固有のポリシーの作成と適用に時間がかかると、製品のバグが増え、ユーザー エクスペリエンスが低下する可能性が高まります。早い段階でドッグフーディングを開始し、現実に使用される機能をすべてテストすることが重要です。早期に enforcing モードにすることで、セキュリティ上の懸念を考慮して設計に関する決定を行うことができます。反対に、確認できた拒否だけに基づいて権限を付与することは、セキュリティ上問題があります。その場合、デバイスのセキュリティ監査を実施して、許可すべきでない動作をバグとして記録します。

既存のポリシーを削除する

新しいデバイスでデバイス固有のポリシーを最初から作成することには、次に示すようにさまざまな利点があります。

コアサービスの拒否に対応する

コアサービスによって生成された拒否は、一般的にファイルのラベル付けによって対処できます。 例:

avc: denied { open } for pid=1003 comm=”mediaserver” path="/dev/kgsl-3d0”
dev="tmpfs" scontext=u:r:mediaserver:s0 tcontext=u:object_r:device:s0
tclass=chr_file permissive=1
avc: denied { read write } for pid=1003 name="kgsl-3d0" dev="tmpfs"
scontext=u:r:mediaserver:s0
tcontext=u:object_r:device:s0 tclass=chr_file permissive=1

上記の例は、/dev/kgsl-3d0 に適切にラベル付けすることで完全に対処できます。この例では、tcontextdevice です。これはデフォルトのコンテキストであり、より具体的なラベルが割り当てられない限り、/dev 内のすべてに「device」ラベルが付与されます。audit2allow からの出力を単純に受け入れるだけでは、不正確で過度に許容的なルールができてしまいます。

この種の問題を解決するには、ファイルにさらに具体的なラベルを付けます。このケースでは gpu_device です。メディア サーバーにはすでに gpu_device にアクセスするために必要な権限がコアポリシーで付与されているため、権限の追加は不要です。

コアポリシーで事前定義されているタイプでラベル付けする必要がある、その他のデバイス固有のファイルを示します。

一般的に、デフォルトのラベルに権限を付与する方法は正しくありません。これら多くの権限は、neverallow ルールによって不許可になりますが、明示的に不許可になっていない場合でも、特定のラベルを付けることをおすすめします。

新しいサービスにラベル付けして拒否に対応する

Init によって開始されたサービスは、独自の SELinux ドメインで実行する必要があります。次の例では、「foo」サービスが独自の SELinux ドメインに入れられ、権限が付与されています。

このサービスは、デバイスの init.device.rc ファイルで次のように開始されます。

service foo /system/bin/foo
    class core
  1. 新しいドメイン「foo」を作成します。

    作成するファイル device/manufacturer/device-name/sepolicy/foo.te には次の内容を含めます。

    # foo service
    type foo, domain;
    type foo_exec, exec_type, file_type;
    
    init_daemon_domain(foo)
    

    これは foo SELinux ドメインの初期テンプレートであり、その実行可能ファイルで実行する特定のオペレーションに基づいてルールを追加できます。

  2. /system/bin/foo にラベルを付与します。

    device/manufacturer/device-name/sepolicy/file_contexts に次のように追加します。

    /system/bin/foo   u:object_r:foo_exec:s0
    

    これにより、実行可能ファイルに適切にラベルが付与され、SELinux のサービスが適切なドメインで実行されます。

  3. ブートイメージとシステム イメージのビルドとフラッシュを行います。
  4. ドメインの SELinux ルールを改良します。

    拒否を使用して、必要な権限を判断します。audit2allow ツールは適切なガイドラインになりますが、ポリシーの作成用に情報を得る目的でのみ使用してください。出力をそのままコピーすべきではありません。

enforcing モードに切り替える

permissive モードでのトラブルシューティングも可能ですが、できるだけ早く enforcing モードに切り替えて、enforcing モードを維持してください。

よくあるミス

デバイス固有のポリシーを作成するときによくあるミスについて、解決策をいくつか示します。

否定を過度に使用する

次のルールの例は、玄関のドアに鍵をかけたのに、窓を開けっ放しにしている状況に似ています。

allow { domain -untrusted_app } scary_debug_device:chr_file rw_file_perms

意図は明確です。サードパーティ製アプリ以外の誰でもデバッグ デバイスにアクセス可能にすることです。

このルールにはいくつかの欠陥があります。どのアプリでも isolated_app ドメイン内のサービスを任意に実行できるため、untrusted_app を除外しても大きな効果は見込めません。同様に、サードパーティ製アプリ用の新しいドメインが AOSP に追加されれば、それも scary_debug_device にアクセスできます。このルールは過度に許容的です。ほとんどのドメインで、このデバッグツールにアクセスできることにメリットはありません。このルールは、アクセスを必要とするドメインだけを許可するように作成すべきでした。

本番環境で機能をデバッグする

デバッグ機能もそのポリシーも、実稼働ビルドに存在してはなりません。

最も簡単な代替策は、adb rootadb shell setenforce 0 のように、eng/userdebug ビルドで SELinux が無効になっている場合のみ、デバッグ機能を許可することです。

もう 1 つの安全な代替策は、デバッグ権限を userdebug_or_eng ステートメントに含めることです。

ポリシーのサイズの爆発的増大

実際の SEAndroid ポリシーの特徴に関してでは、デバイス ポリシーのカスタマイズが肥大化しているという懸念すべき傾向について解説しています。デバイス固有のポリシーが占める割合は、デバイス上で実行されるポリシー全体の 5〜10% を超えないようにします。割合が 20% 以上を占めるカスタマイズには、過度な特権を持つドメインや無効なポリシーが含まれていることがほとんどです。

不必要に肥大化したポリシー:

  • ポリシーが RAM ディスクに置かれていて、さらにカーネルメモリにも読み込まるため、メモリにかかる負荷が倍増します。
  • それによって大きなブートイメージが必要になり、ディスク容量が浪費されます。
  • ランタイム ポリシーの検索時間にも悪影響が及びます。

次に、メーカー固有のポリシーがデバイス上のポリシーの 50% および 40% を占める、2 つのデバイスの例を示します。以下のようにポリシーを作り直すことで、機能を損なうことなくセキュリティが大幅に向上しました(比較のために AOSP デバイスの Shamu と Flounder を取り上げています)。

図 1: セキュリティ監査後のデバイス固有のポリシーのサイズ比較

図 1: セキュリティ監査後のデバイス固有のポリシーのサイズ比較

どちらの場合も、サイズと権限の数の両方でポリシーが劇的に削減されました。ポリシーのサイズが縮小した最大の要因は、不要な権限を削除したことにあります。その不要な権限の多くが、audit2allow によって無差別に生成されてポリシーに追加されたものでした。またどちらのデバイスでも、使用されていないドメインが問題になっていました。

dac_override 機能を許可する

dac_override 拒否は、攻撃プロセスが不正な unix user/group/world 権限でファイルへのアクセスを試行していることを意味します。適切な解決策は、dac_override 権限を決して付与しないことです。 代わりに、ファイルまたはプロセスの unix 権限を変更します。一般的に initvoldinstalld などの少数のドメインは本質的に、他のプロセスのファイルにアクセスするために unix ファイル権限をオーバーライドする機能を必要とします。詳しくは、Dan Walsh のブログをご覧ください。