Apple Developer Connection
高度な検索
Member Login ログイン | ご入会 ADC連絡先

Technical Note TN2127
Kernel Authorization

Mac OS X 10.4 Tigerには、カーネル内の承認管理を行う、Kernel Authorization (Kauth)という新しいカーネルサブシステムが導入されています。Kauthサブシステムが公開しているカーネルプログラミングインターフェイス(KPI)によって、サードパーティのカーネルデベロッパは、カーネル内のアクションを承認したり、承認に関する決定を変更したり、カーネルの承認範囲を広げたりすることができます。また、Kauthを通知メカニズムとして使うこともできます。

Mac OS XカーネルのBSD部分とやり取りするコードを記述する場合は、Kauthに慣れるためにこのテクニカルノートを読む必要があります。上記のいずれかの作業を行う必要がある場合は、このテクニカルノートを詳しく読む必要があります。最後に、Mac OS X用のアンチウイルス製品を開発しているデベロッパの方は、「アクセス時」および「変更後」のファイルスキャン機能を実装する場合にこのテクニカルノートの情報が必要になります。





Kauthの基礎

KauthシステムはMac OS X 10.4 Tigerで新たに採用されました。主に、Tigerカーネルの主要な新機能であるアクセス制御リスト(ACL)の実装を簡素化する目的で導入されたものです。ACLの評価は複雑な作業であるため、これを行うコードが各ファイルシステムプラグインから取り出され、カーネル本体に移されました。Kauthは一般的かつ柔軟な方法でこれを実現します。

KauthはもともとはACLをサポートするために設計されましたが、汎用的なカーネル承認メカニズムであり、他のさまざまなタスクに使用できます。たとえば、アンチウイルスプログラムのデベロッパは、これをシンプルな通知メカニズムとして使うことが考えられます(「アンチウイルススキャナ」を参照)。

Kauthを理解するには、その中心にあるいくつかの概念を理解する必要があります。

  • スコープ — スコープはカーネル内における承認の対象範囲です。たとえば、KAUTH_SCOPE_VNODEスコープはVFSレイヤ内のすべての承認に使用されます。スコープを利用することで、カーネルの承認決定のすべてに関与するのではなく、一部分を対象とすることができます。

    スコープは逆DNS表記法を使用した形式の文字列であるため(たとえば、KAUTH_SCOPE_VNODE"com.apple.kauth.vnode")、必要に応じて独自のスコープを定義できます。

  • アクション — アクションはスコープ内の1つの操作です。たとえば、VFSサブシステムにはKAUTH_VNODE_READ_DATAというアクションが定義されています。これは、ユーザにファイルシステムオブジェクトからのデータの読み取りを承認するかどうかを決定します。アクションは整数定数(実際の型はkauth_action_t)によって指定され、各スコープにはそれぞれ独自のアクション名の名前空間があります。

    スコープとアクションの組み合わせによって、承認確認の対象となる操作が定義されます。

  • アクター — アクターは操作を実行する主体です。

  • クレデンシャル(信用情報) — クレデンシャルはアクターを識別する情報です。クレデンシャルは不透過型、kauth_cred_tで指定されます。この型を操作できるアクセサ関数は多数あります。たとえば、kauth_cred_getuidはクレデンシャルから実効ユーザID (EUID)を返します。

  • リクエスト — このドキュメントの文脈では、あるスコープ内でアクションを実行するための、アクターによるリクエストのことをいいます。

  • リスナー — リスナーは、リクエストに対して承認決定を下すコールバックのことをいいます。基本的に、リスナーはアクターのクレデンシャルに基づいてリクエストを承認します。リスナーは、決定に必要なスコープとアクションの依存関係情報を自由に使用できます。

    カーネルに組み込まれているスコープにはすべて、デフォルトのリスナーが1つ存在します。デフォルトのリスナーは、そのスコープ内のすべてのアクションに対する標準的なBSD承認モデルを実装しています。さらに、独自のリスナーをスコープに登録できます。

先頭に戻る

リスナーの実装

リスナーのプロトタイプをリスト1に示します。

リスト1:リスナーのプロトタイプ

static int MyListener(
    kauth_cred_t   credential,
    void *         idata,
    kauth_action_t action,
    uintptr_t      arg0,
    uintptr_t      arg1,
    uintptr_t      arg2,
    uintptr_t      arg3
);

最初の3つの引数の意味はすべてのスコープで同一です。

  • credentialはアクターのクレデンシャルへの参照です。

    重要:リクエストに対応するクレデンシャルを調べるときは、常に、<sys/kauth.h>に定義されているアクセサ関数を使用します。グループの所属を確認するときには、特に注意が必要です。Tigerでは、ユーザは多数のグループ(従来の制限である16より多い)に所属でき、グループはネストできます。ユーザがあるグループに属しているかを確認する場合は、kauth_cred_ismember_gidを使用します。

  • idataは、リスナーを登録するときに提供したクッキー(refCon)データです(「リスナーの登録」を参照)。

  • actionはリクエストされたアクションです(KAUTH_VNODE_READ_DATAなど)。

残りのパラメータの意味はスコープによって異なります。これらのパラメータについては後のセクションで詳しく述べます。ただしほとんどの場合、これらのパラメータは、承認決定を行うための予備情報となります。たとえば、VFSスコープ(KAUTH_SCOPE_VNODE)の場合、arg1は操作対象のvnode(vnode_t型)への参照です。

リスナーコールバックは次のいずれかの値を返します。

  • KAUTH_RESULT_DEFER — この値は、リスナーがこのリクエストに関する決定を他のリスナー(最終的にはデフォルトリスナー)に委ねることを示します。

  • KAUTH_RESULT_ALLOW — この値は、このリスナーに関するかぎり、リクエストが承認されることを示します。

  • KAUTH_RESULT_DENY — この値は、リクエストを拒否すべきであることを示します。

リクエストが承認されるには、少なくとも1つのリスナーがKAUTH_RESULT_ALLOWを返す必要があり、KAUTH_RESULT_DENYを返すリスナーがあってはいけません。これによりいくつかの結論が導き出されます。

  • すべてのリクエストについて、すべてのリスナーが呼び出される(最後のリスナーだけがKAUTH_RESULT_DENYを返す可能性があるため)。

  • リスナーは他のリスナーが拒否したリクエストを承認できないため、デフォルトリスナー以外のリスナーができるのは、セキュリティの強化のみである。

リスナーを作成する際は、いくつか重要なポイントに留意する必要があります。これらのポイントについては、「リスナーの落とし穴」で詳しく述べます。

先頭に戻る

リスナーの登録

既存のスコープにリスナーを登録するには、kauth_listen_scopeを使用します。

リスト2:kauth_listen_scope

extern kauth_listener_t kauth_listen_scope(
    const char *           identifier,
    kauth_scope_callback_t callback,
    void *                 idata
);

パラメータは以下のとおりです。

  • identifierはスコープの名前です。このルーチンは、このパラメータによって指される文字列のコピーは作成しません。定数文字列を渡す場合、これは問題になりませんが、実行時に文字列の計算を行う場合は、結果のkauth_listener_tを破棄するまで、文字列を保持する必要があります。

  • callbackはリスナーコールバック関数のアドレスです。この関数のプロトタイプはリスト1を参照してください。

  • idataはリスナーコールバックのクッキー(refCon)です。

エラーの場合、結果はNULLです。成功した場合、結果はリスナーへの参照です。リスナーの登録を解除する場合にこの参照を使用できます。

スコープを登録する前にリスナーを登録しても、エラーにはなりません。システムはリスナーを記憶していて、スコープが登録された時点で対応するリスナーを適用します。

先頭に戻る

リスナーの登録解除

リスナーの登録を解除するには、kauth_unlisten_scopeを使用します。

リスト3:kauth_unlisten_scope

extern void  kauth_unlisten_scope(kauth_listener_t listener);

パラメータは以下のとおりです。

  • listenerは、kauth_listen_scopeから取得したリスナーへの参照です。

重要:kauth_unlisten_scopeが戻っても、それは必ずしもリスナーを実行しているすべてのスレッドが、リスナーから戻ったことを示しているわけではありません。kauth_unlisten_scopeから戻った後、それらのスレッドがリスナーコードの実行を完了するまでは、スレッドの状態を破棄しないように措置を講じる必要があります。このための奨励される方法については、「KauthORamaサンプルコード」の例を参照してください。

先頭に戻る

新しいスコープの登録

重要:この作業は、独自のカスタムスコープを作成する場合に限り必要です。サードパーティデベロッパの場合は、既存のスコープにリスナーを登録することがほとんどです。

新しいスコープを登録するには、kauth_register_scopeを使用します。

リスト4:kauth_register_scope

extern kauth_scope_t kauth_register_scope(
    const char *           identifier,
    kauth_scope_callback_t callback,
    void *                 idata
);

パラメータは以下のとおりです。

  • identifierはスコープの名前です。すでに存在するスコープを登録すると、エラーが発生します。このルーチンは、このパラメータが参照する文字列のコピーは作成しません。定数文字列を渡す場合、これは問題になりませんが、実行時に文字列の計算を行う場合は、結果のkauth_scope_tを破棄するまで、文字列を保持する必要があります。

  • callbackは、このスコープにおけるリスナーコールバック関数のアドレスです。これがそのスコープのデフォルトリスナーになります。このパラメータはNULLになることが可能であり、その場合は、KAUTH_RESULT_DEFERを返すコールバックが想定されます。

  • idataはリスナーコールバックのクッキー(refCon)です。

エラーの場合、結果はNULLとなります。成功した場合、結果はスコープへの参照です。スコープの登録を解除する場合にこの参照を使用できます。

先頭に戻る

スコープの登録解除

スコープの登録を解除するには、kauth_deregister_scopeを使用します。

リスト5:kauth_deregister_scope

extern void  kauth_deregister_scope(kauth_scope_t scope);

パラメータは以下のとおりです。

  • scopeは、kauth_register_scopeから取得したスコープへの参照です。

スコープに登録されている他の(デフォルト以外の)リスナーは休止状態に入ります。スコープが再度登録されると、リスナーが再開します。

重要:kauth_deregister_scopeが戻っても、それは必ずしもリスナーを実行しているすべてのスレッドが、リスナーから戻されたことを示しているわけではありません。詳細については、(「リスナーの登録解除」の)kauth_unlisten_scopeに関する説明を参照してください。

先頭に戻る

アクションの承認

承認をリクエストするには、kauth_authorize_actionを使用します。

リスト6:kauth_authorize_action

extern int kauth_authorize_action(
    kauth_scope_t  scope,
    kauth_cred_t   credential,
    kauth_action_t action,
    uintptr_t      arg0,
    uintptr_t      arg1,
    uintptr_t      arg2,
    uintptr_t      arg3
);

パラメータは以下のとおりです。

  • scopeはアクションが定義されているスコープへの参照です。この値はkauth_register_scopeの結果から取得されます。

  • credentialはアクターのクレデンシャルへの参照です。通常は、この情報をすでに持っているか、kauth_cred_getを呼び出して、現在のスレッドのクレデンシャルを取得します。

  • actionはリクエストされたアクションです。

  • arg0arg3は、変更なしでリスナーコールバックに渡されます。

カーネル拡張がプラグインをサポートしており、これらのプラグインがkauth_authorize_actionを呼び出す場合は、そのプラグインに、スコープ参照(kauth_scope_t)を見つける手段が必要です。これを行う最も簡単な方法は、個々の要件に合わせて作成されたラッパー関数をエクスポートすることです。

重要:カーネルには、カーネルが定義しているすべてのスコープのためのkauth_authorize_actionラッパーがすでに用意されています。カーネルによって定義されたスコープ内で承認する場合は、kauth_authorize_actionを直接呼び出すのではなく、これらのラッパーを呼び出す必要があります。これらのラッパーと対応するスコープについては後述します。

先頭に戻る

組み込まれているスコープ

このセクションでは、Mac OS X 10.4 Tigerのカーネルに組み込まれているスコープをすべて取り上げます。

プロセススコープ

プロセススコープ(KAUTH_SCOPE_PROCESS、つまり"com.apple.kauth.process")は、一番わかりやすいスコープです。このスコープでは、2つのアクションだけが定義されています。

  • KAUTH_PROCESS_CANTRACE — 現在のプロセスがターゲットプロセスをトレースできるかを承認します。(proc_t型の)arg0はトレース対象プロセスです。((int *)型の)arg1はエラー番号形式のエラーコードを指すポインタです。リスナーがリクエストを拒否する場合は、この値をゼロ以外の値に設定する必要があります。

  • KAUTH_PROCESS_CANSIGNAL — 現在のプロセスがターゲットプロセスにシグナルを送信できるかを承認します。(proc_t型の)arg0はシグナルの送信先プロセスです。(int型の)arg1は送られるシグナルです。

    重要:このアクションはMac OS X 10.4 Tigerには実装されていません(r. 3931697)。

カーネルはこのスコープのためにkauth_authorize_actionラッパー、すなわちkauth_authorize_processもエクスポートします。

リスト7:kauth_authorize_process

extern int kauth_authorize_process(
    kauth_cred_t   credential,
    kauth_action_t action,
    proc_t         process,
    uintptr_t      arg1,
    uintptr_t      arg2,
    uintptr_t      arg3
);

kauth_authorize_actionを含むこのラッパーは、有用な2つの役割があります。

  1. 適切なスコープパラメータを提供する。

  2. proc_t型の)processを(uintptr_t型の)arg0にキャストする手間を解消する。

通常、サードパーティデベロッパの方がこのラッパーを使用することはありません。

先頭に戻る

汎用スコープ

汎用スコープ(KAUTH_SCOPE_GENERIC、つまり"com.apple.kauth.generic")のアクションは、KAUTH_GENERIC_ISSUSERという1つのアクションのみです。カーネルはこれを使って、アクターにスーパーユーザ権限があるかどうかを判定するようリクエストします。汎用引数(arg0arg3)はどれも有意ではありません。

重要:カーネルは現時点では、スーパーユーザの判定にこのリクエストを使用していません。多くの場合、カーネルは引き続き、クレデンシャルの実効ユーザIDと0を直接比較しています。

カーネルはこのスコープのためにkauth_authorize_actionラッパー、すなわちkauth_authorize_genericもエクスポートします。

リスト8:kauth_authorize_generic

extern int kauth_authorize_generic(
    kauth_cred_t   credential,
    kauth_action_t action
);

通常、サードパーティデベロッパの方がこのラッパーを使用することはありません。

先頭に戻る

ファイル操作スコープ

ファイル操作スコープ(KAUTH_SCOPE_FILEOP、つまり"com.apple.kauth.fileop")は、何らかの操作を実際に承認するものではないという点で、他のスコープと異なります。システムは、このスコープを使って重要なファイルシステム操作をリスナーに通知します。「アンチウイルススキャナ」で述べているように、このスコープはアンチウイルススキャンプログラムを実装する場合に使用できます。

KAUTH_SCOPE_FILEOPでは、以下のアクションを定義しています。

  • KAUTH_FILEOP_OPEN — ファイルシステムオブジェクト(ファイルまたはディレクトリ)が開かれたことを通知します。(vnode_t型の)arg0はvnodeへの参照です。((const char *)型の)arg1はオブジェクトのフルパスを指すポインタです。

  • KAUTH_FILEOP_CLOSE — ファイルシステムオブジェクトが閉じられようとしていることを通知します。(vnode_t型の)arg0はvnodeへの参照です。((const char *)型の)arg1はオブジェクトのフルパスを指すポインタです。(int型の)arg2はビットフラグの組み合わせです。現在定義されている唯一のフラグはKAUTH_FILEOP_CLOSE_MODIFIEDであり、変更されたファイルが閉じられようとしているとセットされます。

  • KAUTH_FILEOP_RENAME — ファイルシステムオブジェクトの名前が変更されたことを通知します。((const char *)型の)arg0はオブジェクトの以前のフルパスを指すポインタです。((const char *)型の)arg1はオブジェクトの新しいフルパスを指すポインタです。

  • KAUTH_FILEOP_EXCHANGE — 2つのファイルが(exchangedataによって)交換されたことを通知します。((const char *)型の)arg0は最初のファイルのフルパスを指すポインタです。((const char *)型の)arg1は2番目のファイルのフルパスを指すポインタです。

  • KAUTH_FILEOP_LINK — (linkシステムコール経由で)ファイルに新しいハードリンクが追加されたことを通知します。((const char *)型の)arg0は元のファイルのフルパスを指すポインタです。((const char *)型の)arg1は新規作成されたリンクのフルパスを指すポインタです。

  • KAUTH_FILEOP_EXEC — (execveシステムコール経由で)プログラムが実行されたことを通知します。(vnode_t型の)arg0は実行されるプログラムのvnode参照です(Mach-O実行ファイルの場合は、実際の実行ファイルになります。CFMアプリケーションの場合は、常にLaunchCFMApp参照になります。シェルやperlなどのインタプリタ型のスクリプトの場合、これはインタプリタではなくスクリプトです)。((const char *)型の)arg1はプログラムファイルのフルパスを指すポインタです。

このスコープにリスナーを設定すると、これらのイベントを通知するためにリスナーが呼び出されます。カーネルはリスナーの戻り値を無視しますが、必ずKAUTH_RESULT_DEFERを返すことをお勧めします。

カーネルはこのスコープのためにkauth_authorize_actionラッパー、すなわちkauth_authorize_fileopもエクスポートします。

リスト9:kauth_authorize_fileop

extern int kauth_authorize_fileop(
    kauth_cred_t   credential,
    kauth_action_t action,
    uintptr_t      arg0,
    uintptr_t      arg1
);

通常、サードパーティデベロッパの方がこのラッパーを使用することはありません。

先頭に戻る

vnodeスコープ

vnodeスコープ(KAUTH_SCOPE_VNODE、つまり"com.apple.kauth.vnode")は、現在定義されている最も複雑なスコープです。最初に注意すべきことは、vnodeスコープでは、アクションが列挙ではなくビットフィールドであることです。そのため、論理和をとることでアクションを組み合わせることができます。たとえば、KAUTH_VNODE_READ_DATA | KAUTH_VNODE_EXECUTEというアクションは、アクターがファイルの読み取りと実行の両方を希望していることを示します。

vnodeスコープ内のアクションを承認するには、vnode_authorizeを呼び出します。

リスト10:vnode_authorize

extern int vnode_authorize(
    vnode_t        vp,
    vnode_t        dvp,
    kauth_action_t action,
    vfs_context_t  context
);

パラメータは以下のとおりです。

  • vpはアクションが実行されるvnodeです。

  • dvpは親ディレクトリのvnodeです。ほとんどの場合、これはNULLであり、親が不明または不適切であることを示します。

  • actionは実行される操作です。これについては後述します。

  • contextは、アクターと関連するVFSコンテキストです。これは不透過のデータ構造であり、VFS実装と密接に関係しています。ほとんどのVFSエントリポイントには、このコンテキストが渡されます。また、<sys/vnode.h>には多くのVFSコンテキストルーチンが定義されています。

vnode_authorizekauth_authorize_actionを含む、ごく単純なラッパーです。これは2つの有用な機能を実行します。

  1. 正しいスコープリスナー引数(arg0arg3)を組み合わせる。これらの引数については後述します。

  2. エラーコードが正しいことを保証する。具体的には、kauth_authorize_actionが返すEPERMエラーをEACCES(ファイルシステム関数に適したエラー)に変換します。また、リスナーがリクエストを拒否し、(arg3によって、下記参照)特定のエラーコードを提供する場合は、そのエラーを返します。

重要:VFSプラグインがvnode_authorizeを呼び出すのは、比較的異例なことです。ほとんどの場合、VFSレイヤはすべてのアクションを承認してから、プラグインを呼び出します。ただし、状況によっては、VFSプラグインがvnode_authorizeを呼び出すこともあります。たとえば、HFS [Plus]におけるsearchfsの実装は、vnode_authorizeを使用して、実装が返すファイルシステムオブジェクトに呼び出し側がアクセスできるようにします。

vnodeスコープのスコープリスナー引数は以下のとおりです。

  • arg0vfs_context_t型)はcontext — VFSコンテキスト、前述のとおりです。

  • arg1vnode_t型)はvp — vnode自身。

  • arg2vnode_t型)はdvp — ある場合は、親vnode。これはNULLでも構いません。

  • arg3(int *)型)errPtr — エラー番号形式のエラーを指すポインタ。コールバックがリクエストを拒否する場合は、この値を設定を設定してエラーを示し、クライアントに返すことができます。これを設定しない場合、クライアントはEACCESを受け取ります。

vnodeスコープには、以下の標準アクションが定義されています。

  • KAUTH_VNODE_READ_DATAKAUTH_VNODE_LIST_DIRECTORYも同様) — vnodeがディレクトリの場合は、アクターによるディレクトリ内容の列挙を承認します。ディレクトリでなければ、アクターによるファイル内容の読み取りを承認します。

  • KAUTH_VNODE_WRITE_DATAKAUTH_VNODE_ADD_FILEも同様) — vnodeがディレクトリの場合は、アクターによるディレクトリへのファイルの追加を承認します。vpは、ファイルが追加する対象となるディレクトリです。dvpNULLです。ディレクトリでなければ、アクターによるファイル内容の書き込みを承認します。

  • KAUTH_VNODE_EXECUTEKAUTH_VNODE_SEARCHも同様) — vnodeがディレクトリの場合は、パス検索の一環として、アクターによるディレクトリ内の項目の有無の確認を承認します。ディレクトリでなければ、アクターによるファイルの内容の実行を承認します。

  • KAUTH_VNODE_DELETE — アクターによるディレクトリからの項目の削除を承認します。vpは削除する項目であり、dvpは削除する項目が置かれているディレクトリです。

  • KAUTH_VNODE_APPEND_DATAKAUTH_VNODE_ADD_SUBDIRECTORYも同様) — vnodeがディレクトリの場合は、アクターによるディレクトリへのディレクトリの追加を承認します。ディレクトリでなければ、このアクションは、アクターによるファイルへのデータの追加を承認することを意図しています。しかし、この側面は現在、実装されていません。

  • KAUTH_VNODE_DELETE_CHILD — ディレクトリのACLで使用される場合、このパーミッションは、アクターによるディレクトリからの項目の削除が可能かどうかを制御します。つまり、アクターが項目を削除できるためには、項目に対するKAUTH_VNODE_DELETEパーミッションと項目の親ディレクトリに対するKAUTH_VNODE_DELETE_CHILDパーミッションを設定する必要があります。

    ただしKauthリスナーは、めったにこのアクションを確認しません。アクターが項目を削除するときには、カーネルは単に項目自体に関するKAUTH_VNODE_DELETEアクションを承認します。親ディレクトリに対する別のKAUTH_VNODE_DELETE_CHILDアクションは承認しません。vnodeスコープのデフォルトリスナーが、親ディレクトリに対するKAUTH_VNODE_DELETE_CHILDパーミッションをKauthには渡さずに、直接チェックします。これによりパフォーマンスが向上し、競合の可能性も回避できます。

    一方、_RMFILE_OKフラグによるaccessシステムコールに応えて、KAUTH_VNODE_DELETE_CHILDアクションが生成されます。

  • KAUTH_VNODE_READ_ATTRIBUTES — アクターによるvnodeの標準属性(タイムスタンプなど)の読み取りを承認します。

  • KAUTH_VNODE_WRITE_ATTRIBUTES — アクターによるvnodeの標準属性(タイムスタンプなど)の変更を承認します。

  • KAUTH_VNODE_READ_EXTATTRIBUTES — アクターによるvnodeの拡張属性(リソースフォークなど、getxattrによってアクセスされる属性)の読み取りを承認します。

  • KAUTH_VNODE_WRITE_EXTATTRIBUTES — アクターによるvnodeの拡張属性(リソースフォークなど、getxattrによってアクセスされる属性)の変更または追加を承認します。

  • KAUTH_VNODE_READ_SECURITY — アクターによるvnodeのACLの読み取りを承認します。

  • KAUTH_VNODE_WRITE_SECURITY — アクターによるvnodeのACLの変更を承認します。

  • KAUTH_VNODE_TAKE_OWNERSHIP — アクターによるvnodeの所有権の変更を承認します。

  • KAUTH_VNODE_SYNCHRONIZE — 他のプラットフォームとの互換性のために定義されたACLパーミッションを示します。このアクションは確保されていますが、Mac OS Xによるテストは行われません。そのため、Kauthアクションとしては使用されません。

  • KAUTH_VNODE_LINKTARGET — アクターによるvnodeへ新しいハードリンクの作成を承認します。

  • KAUTH_VNODE_CHECKIMMUTABLE — アクターによるファイルの変更を承認します(SF_IMMUTABLEの場合。chflagsを参照)。このフラグを設定するのは、ファイルを変更できるかどうか、ほかのチェックはすでに済んでいたが、変更不可のファイルであったために変更に失敗した場合です。

  • KAUTH_VNODE_ACCESS — 特別なフラグです。このフラグがセットされていると、承認リクエストは絶対的ではなく、(たとえば、accessシステムコールを満たすための)通知的なものになります。リスナーはこのフラグを使用して、通知的なケースでの余分な作業を回避できます。

  • KAUTH_VNODE_NOIMMUTABLE — 特別なフラグです。このフラグはKAUTH_VNODE_WRITE_SECURITYビット(他のビットはなし)とともにリスナーに渡され、アクターが1つ以上の変更不可のフラグを変更したいこと、およびリクエストを承認する際にこれらのフラグの状態を考慮すべきでないことを指示します。

vnodeスコープの落とし穴

前述したとおりvnodeスコープ内のアクションは、ビットフィールドです。そのため、vnodeスコープリスナーを呼び出して、複数のアクションを同時に承認することができます。

vnodeスコープは多忙を極めています。vnodeスコープリスナーの処理が遅いと、すべてのファイルシステム操作が非常に遅くなります。vnodeスコープリスナーを設定する場合は、できるだけ効率的なものにする必要があります。

vnodeスコープリスナーを作成するときには、すべてのファイルシステム操作が承認リクエストを引き起こすわけではないことを認識しておく必要があります。たとえば、アクターがディレクトリに対するKAUTH_VNODE_SEARCHのリクエストに成功すると、システムはリクエストごとにリスナーを呼び出さなくても、その結果をキャッシュして以降のリクエストを承認することができます。

リスナーを作成する場合の落とし穴の詳細については、「リスナーの落とし穴」を参照してください。

先頭に戻る

リスナーの落とし穴

リスナーを作成する際には、いくつかの重要な点に留意する必要があります。以降のセクションではこれらについて説明します。

コンテキスト

ほとんどの場合、リスナーはユーザスレッドによって呼び出されます。つまり、ユーザスレッドがシステムコールを呼び出し、その作業のためにカーネルに入ることになり、Kauthリクエストが発されます。そのため、現在のスレッドまたはプロセスに基づいて、アクターに関する情報を得ることができます。たとえば、proc_selfを呼び出して現在のプロセスへの参照を取得し、そこから有用な情報を取得することができます。

しかし、これは行わないようにしてください。代わりに、リスナーはアクターのクレデンシャル(credentialsパラメータでリスナーに渡される)に基づいて、そしてvnodeスコープでリッスンしている場合はVFSコンテキストに基づいて決定を行う必要があります。

これを推奨するのには2つの理由があります。

  • カーネル全体の設計を考えると、リスナーが、現在のスレッドのような暗黙的なパラメータではなく、入力パラメータに基づいて決定を下すほうがきれいです。

  • 場合によっては、ユーザの代わりに別のスレッドでカーネル操作を実行できます。この種の操作は、非同期入出力ではすでに行われており、そこでは非同期のファイルシステムリクエストを実行する非同期入出力スレッドのプールがカーネルによって維持されています。Mac OS X 10.4 Tigerでは、これらのスレッドは実際には承認リクエストを行いませんが、長期的な方向性ははっきりしています。

デッドロックの回避

リスナーは操作を実行するスレッドによって呼び出されるため、リクエストを処理する間、スレッドをブロックできます。ただし、これは両刃の剣です。リスナーはリクエストを外部エージェント(ユーザ空間デーモンなど)に渡し、結果が戻るまでブロックすることができます。しかし、そうすることでシステムのデッドロックという大きなリスクが生じます。

この問題は、vnodeファイル操作スコープのリスナーを作成するときに最もよく起こります。典型的な例としては次のようなことが考えられます。

  1. ファイル操作スコープにリスナーを設定します。

  2. 通常のプロセスがファイルを開きます。

  3. リスナーが呼び出されKAUTH_FILEOP_OPENが渡されます。リスナーはユーザ空間デーモンにリクエストを渡して、結果を待ちます。

  4. ユーザ空間デーモンが、システムデーモンへのリモートプロシージャ呼び出し(RPC)を行うシステムルーチンを呼び出します。たとえば、getpwuidを呼び出します。これは実際にはlookupd内に実装されています。

  5. システムデーモンがファイルを開きます。

  6. これによってカーネルがリスナーを呼び出し、サイクルが最初から繰り返されます。

この問題は見た目よりもはるかに深刻です。特に、次の点に注意してください。

  • システムデーモンは他のシステムデーモンを呼び出します。たとえば、lookupdDirectoryServiceに依存しており、このデーモンはまた他のデーモンに依存していることが考えられます。

  • 依存関係構造はリリースごとに異なるため、考えられる一連のシステムデーモンを単純にハードコードするわけにはいきません。アップルは新しい依存関係をいつでも追加する可能性があります。

  • デーモンがシステムデーモンにまったく依存しないようにするのは不可能です。ページング可能メモリにアクセスするたびに、ページングファイルの割り当て処理のトリガとなることが考えられますが、これはdynamic_pagerに依存しています。

このようなデッドロックを避けるには、いろいろな方法があります。最もよいのは、rootとして実行しているスレッドからリクエストを受け取った場合(アクターのクレデンシャルのkauth_cred_getuidが0を返す場合)に、リスナーが何も処理を実行しないようにすることです。重要なシステムデーモンはすべて、rootとして実行する必要があるため、これによって上記のステップ6でデッドロックを回避できます。

注:グループのメンバーシップ解決デーモンmemberdも同様の問題に直面し、まったく同じ方法でこの問題を回避しています。つまり、スレッドがrootとして実行している場合、システムはmemberdを呼び出しません。

ただし、この手法はあらゆる場合に適切であるとはいえません。たとえば、アンチウイルススキャナは特に、権限を昇格した状態で実行しているスレッドによって開かれているファイルをスキャンする必要があります。この場合、唯一の正しい解決方法は、スキャナを完全にカーネル内で機能させることです。

先頭に戻る

パフォーマンス

一部のKauthスコープは非常に多忙です。つまり、一般的なシステムはそのスコープで頻繁に承認リクエストを行います。たとえば、ファイルをコピーするシステムは、1秒間に何千ものvnodeスコープ承認リクエストを行います。スコープにリスナーを登録すると、リスナーはリクエストのたびに呼び出されます。リスナーの処理が遅いと、システムのパフォーマンスは著しく低下します。

Mac OS X 10.4 Tigerでは、多忙なスコープはvnodeスコープファイル操作スコープの2つです。これらのスコープのどちらかにリスナーを設定する場合は、リスナーがシステムパフォーマンス全体に与える影響を必ず測定し、それに応じてリスナーを最適化します。

先頭に戻る

Kauthの活用法

このセクションでは、Kauthを使用して、よくリクエストされるいくつかの機能を実装する方法について説明します。

デバッガの拒否

Kauthを使用して、ユーザによるプロセスへのデバッガのアタッチを防ぐような、カーネル拡張を実装することができます。必要なことは、KAUTH_SCOPE_PROCESSスコープにリスナーを登録し、KAUTH_PROCESS_CANTRACEアクションを見張るだけです。リスト11に、root以外のすべてのユーザによるデバッグを拒否するリスナーの例を示します。同様の手法を用いて、特定のプロセスへのデバッガのアタッチを防ぐことも可能です。

リスト11:デバッガの拒否

static int KauthDenyDebugListener(
    kauth_cred_t    credential,
    void *          idata,
    kauth_action_t  action,
    uintptr_t       arg0,
    uintptr_t       arg1,
    uintptr_t       arg2,
    uintptr_t       arg3
)
    // このリスナーをKAUTH_SCOPE_PROCESスコープに登録する。
    // システムは、このスコープ内でアクションを承認する必要があるときには、
    // このリスナーを呼び出す。
    // KAUTH_PROCESS_CANTRACEアクションを見張り、リクエスト元のユーザがrootでない場合は
    // 拒否する。
{
    int     result;

    result = KAUTH_RESULT_DEFER;

    switch (action) {
        case KAUTH_PROCESS_CANTRACE:
            {
                proc_t  targetProc;
                int *   errPtr;

                targetProc = (proc_t) arg0;
                errPtr     = (int *)  arg1;

                if (kauth_cred_getuid(credential) != 0) {
                    printf(
                        "Denied P_TRACE from %d to %d by %d.\n",
                        proc_selfpid(),
                        proc_pid(targetProc),
                        kauth_cred_getuid(credential)
                    );
                    *errPtr = EPERM;
                    result = KAUTH_RESULT_DENY;
                }
            }
            break;
        default:
            // 何もしない
            break;
    }

    return result;
}

注:プログラムがデバッガによって起動された場合、Kauthは呼び出されません。このケースを検出するには、テクニカルQ&A QA1361「Detecting the Debugger」で説明されている手法を使用します。

先頭に戻る

アンチウイルススキャナ

Kauthを使用すると、「アクセス時」および「変更後」のファイルスキャン機能をサポートするアンチウイルスプログラムを実装できます。後者は簡単で、必要なことはKAUTH_SCOPE_FILEOPスコープにリスナーを登録し、KAUTH_FILEOP_CLOSEアクションに注目するだけです。変更されたファイルが閉じられようとしているのを確認したら、そのファイルをユーザ空間のデーモンに渡してスキャンすることができます。スキャンはバックグラウンドで非同期に進行するので、デッドロックに関しては問題になりません。

「アクセス時」のスキャン機能の実装は、もう少し困難です。どのようなアプローチを取るかは、ファイルをいつでも修正できるかどうかによって異なります。修正できるのであれば、(KAUTH_SCOPE_FILEOPの)KAUTH_FILEOP_OPENをリッスンして、ファイルが開かれた直後にスキャンできます。ただし、リスナーの結果は常に無視されるので、そのファイルへのアクターのアクセスを拒否する方法はありません。

ファイルを常に修正できるわけではなく、そのためファイルへのアクターのアクセスを拒否する必要がある場合は、KAUTH_SCOPE_VNODEスコープで適切なアクションをリッスンする必要があります。ファイルをスキャンして感染を検出したのに修正できない場合は、KAUTH_RESULT_DENYを返して、アクターがそのファイルを使用できないようにする必要があります。

「アクセス時」のこれら2つのアプローチで両方とも難しいのは、デッドロックの回避です。この問題の詳細については、「リスナーの実装」を参照してください。

先頭に戻る

新しいカーネルサブシステム

まったく新しいカーネルサブシステム(高度なプロトコルスタックなど)を実装する場合は、Kauthを使って承認を実装することができます。これには7つのステップがあります。

  1. スコープ名を決めます。前述の組み込まれているスコープで説明しているように、逆DNS表記法を使用してください。

  2. アクションのセットを決めます。列挙(ファイル操作スコープで使用)またはビットマスク(vnodeスコープで使用)を使うことができます。

  3. アクションごとに、そのアクションに適したリクエスト固有の引数(arg0arg3型)を決める必要があります。スコープ内のすべてのアクションの引数が同じであれば最も簡単ですが、同じであることは必須ではありません。

  4. スコープのデフォルトリスナーを作成します。このリスナーは、以下に基づいて承認決定を行える必要があります。

    • アクターのID(リスナーのcredentialsパラメータで示される)

    • リクエストされるアクション

    • リクエスト固有の引数

    リスナーは<sys/kauth.h>に定義されたアクセサ関数を使用して、クレデンシャルから情報を抽出できます。

  5. スコープを作成し、kauth_register_scopeを使って、リスナーをデフォルトリスナーとして登録します。

  6. kauth_authorize_actionに対する、以下の機能を備えたスコープ固有のラッパー関数を作成します。

    • 前のステップで作成したスコープへの参照を渡す

    • スコープ固有の引数をkauth_authorize_actionで使用する汎用引数(arg0arg3)にキャストする

  7. カーネルサブシステムの適切な場所でスコープ固有のラッパー関数を呼び出し、個々のアクションを承認します。

先頭に戻る

KauthORamaサンプルコード

サンプルコード「KauthORama」はKauthを探求するための素晴らしいツールです。これを使って、任意のスコープにダミーリスナーを登録できます。このリスナーは必ずKAUTH_RESULT_DEFERを返すため、承認決定に影響を及ぼすことはありませんが、承認リクエストのレコードは出力します。これを使って、Kauthが高レベルの操作(ディレクトリのリスト表示やファイルのコピーなど)をどのようにやり取りするかを確認できます。このサンプルのRead Meファイルには、これを行うための説明があります。

先頭に戻る

ドキュメント改訂履歴

日付メモ
2006-03-21非ASCII文字に関する問題を訂正。
2005-06-03kauth (Kernel Authorization)サブシステムと関連KPIについて説明。

掲載日: 2006-03-21




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.