ストレージ・プロテクト・キー

ストレージ・プロテクト・キーは、ユーザー・プログラムの信頼性を向上させるメカニズムを提供します。

プロテクト・キーは、メモリー・ページに適用され、細分度のページ・レベルで動作します。これは、1 つ以上のページを読み取りまたは書き込み保護するために使用できる mprotect サブルーチンに似ています。 ただし、ストレージ・キーを使用すると、データのセクションを特定のレベルの読み取りおよび書き込みアクセスの保護に指定することができます。 ストレージ・キーによる保護は、データ・ページだけではなく、スレッド試行のアクセスの機能でもあります。 大規模プログラムでは使用できないデータにアクセスするために特定のコード・パスを使用可能にすることができます。これにより、重要なプログラム・データがカプセル化され、不測の障害から保護することができます。

キーで保護されたページへのアクセスは実行スレッドの属性であるため、このメカニズムはマルチスレッド・アプリケーションまで拡張されますが、1:1 (またはシステム有効範囲) pthreads のみ使用するという制限があります。 mprotect サブルーチン・アプローチは、マルチスレッド環境では確実に機能しません。これは、任意のスレッドへのアクセスを認可したい場合に、すべてのスレッドの保護を除去する必要があるためです。 2 つのメカニズムは同時に使用でき、両方とも完全に実行されるため、プロテクト・キーにより別の方法で許可されていたとしても、ユーザーのプログラムは書き込み保護されたページに書き込むことはできません。

プロテクト・キーの使用例は、以下のとおりです。
  • ユーザー・プログラムの私用データを完全にカプセル化し、選択されたコード・パスのみにアクセスを限定します。
  • ユーザー・プログラムの私用データを、常に読み取りアクセスを許可して実行する一方で、データの変更時のみ書き込みアクセスを許可することで不測の障害から保護します。 この方法は、コア・エンジンのコードで信頼性のないコードに対する呼び出しが許可されている場合に特に有効です。
  • 複数の秘密鍵が使用可能な場合、さらに細分化されたデータ保護が可能です。

キーによる保護を考慮してアプリケーションを設計することで、デバッグを単純化することができます。 ページのプロテクト・キーの設定とアクティブ・ユーザー・キーセットの設定は両方ともシステム・コールであるため、比較的消費が多い操作です。 プログラムを設計する際は、これらの操作の頻度が過多にならないようにしてください。

ユーザー・プロテクト・キー

プロテクト・キーを使用する際は、以下のガイドラインと考慮事項が適用されます。
  • カーネルから読み取り専用でエクスポートされるページは、プログラムから引き続き見えます。 これらのページには、UKEY_SYSTEM のプロテクト・キーがあります。 このプロテクト・キーはユーザー・プログラムの制御下にあるプロテクト・キーではありませんが、ユーザー・プログラムから常にアクセス可能です。
  • ユーザー・プログラムのすべてのメモリー・ページには、最初にユーザー公開鍵が割り当てられています。 前述のように、キー 0 のストレージへのアクセスは常に許可され、これがユーザー公開鍵 となります。
  • プロテクト・キーは、通常かつ共有のデータに対してのみ設定できます。 例えば、ライブラリー・データ、カーネルと共有されるロー・メモリー、またはプログラム・テキストは保護することができません。
  • 基礎となるハードウェアと管理選択に応じた、限定数のユーザー秘密鍵のみが使用可能です (通常 1 つのみ)。 ユーザー・プログラムが 1 ページ以上のページに秘密鍵を割り当てると、これらのページのデータはデフォルトで使用できなくなります。 アクセス権限が必要なコード・パスを新しいサービスへの呼び出しで囲んでアクティブ・ユーザー・キーセットを管理することで、このデータに対する読み取りアクセスまたは書き込みアクセスを明示的に許可する必要があります。
  • 物理的ハードウェアのほうが、ユーザー・プロテクト・キーとして使用できないプロテクト・キーをさらにサポートします。
  • プロテクト・キーをページに割り当てるための特権は必要ありません。 ページに対する現行の書き込みアクセスのみが必要となります。
  • プロテクト・キーによる実行権限の制御はありません。

ユーザー・プログラムがキーで保護されたデータに、そのアクティブ・ユーザー・キーセットで示されたアクセス権限に違反してアクセスした場合、読み取りまたは書き込み保護されたページに違反した場合のように、プログラムは SIGSEGV シグナルを受け取ります。 このシグナルを処理する場合は、シグナル・ハンドラーが秘密鍵へのアクセスなしで起動することに注意してください。 シグナル処理コードは、キーで保護されたデータを参照する前に、アクティブ・ユーザー・キーセットに対して必要なアクセス権限を追加します。

fork システム・コールによって作成された子プロセスは、その親のメモリーと実行状態を論理的に継承します。 この場合、各ページに関連付けられたプロテクト・キーと、fork 時の親スレッドのアクティブ・ユーザー・キーセットも含まれます。

ユーザー・キーで保護される領域

ユーザー・プロテクト・キーは、以下の領域のページを保護できます。
  • データ領域
  • デフォルト・スタック領域
  • mmaped 領域
  • shmat() サブルーチンに接続された共用メモリー (下記を除く)
  • ページの以下のカテゴリーでは、プロテクト・キーを使用できません。
    • shmatファイルおよびピン留めされた共有メモリー
    • 大規模 (ページ不可) ページ
    • プログラム・テキスト
    • カーネルと読み取り専用で共有されるロー・メモリー

キー保護に対するシステムの前提条件

ストレージ・キー保護は、AIX®カーネルがアプリケーション・プログラムで使用できるようにした、ハードウェア固有の特権ページ保護メカニズムである。 この機能を使用するには、システムは以下の条件を満たす必要があります。
  • ストレージ・キー保護を提供する物理的ハードウェアで実行されている
  • 64 ビット・カーネルを実行している
  • ユーザー・プロテクト・キーを使用できる

キー保護に対するプログラムの前提条件

ユーザー・キーを使用するには、プログラムは以下の条件を満たす必要があります。
  • ukey_enable サブルーチンを使用して、ユーザー・キー認識を宣言し、使用可能なユーザー・プロテクト・キーがあれば、それらの数を判別します。
  • ページ境界上の保護データを編成します。
  • ukey_protect サブルーチンを使用して、保護したい各ページに秘密鍵を割り当てます。
  • malloc'd データを保護している場合、そのデータを解放する際は、その前に忘れずに保護を解除してください。
  • ukeyset_init サブルーチンを使用して、1 つ以上の鍵セットを準備します。
  • 必要に応じて将来の読み取りまたは書き込みアクセスを可能にするために、 ukeyset_add_key サブルーチンを使用して、必要なキーをキー・セットに追加することができます。
  • ukeyset_activate サブルーチンを使用してキー・セットをアクティブにし、キー・セットに定義されているアクセス権を付与します。
ユーザー・プログラムは以下を行うことはできません。
  • M:N (プロセス有効範囲) pthreads を組み込む
  • チェックポイントの実行を可能にする (例えば、環境内で CHECKPOINT=yes と指定する)
注: プログラムがユーザー・キーを認識する場合、そのプログラムには、アクティブ・ユーザー・キーセットを表すために追加のコンテキストが関連付けられます。 これについては、以下で確認できます。
  • ucontext_t 構造を受け取るシグナル・ハンドラー。 前回のアクティブ・ユーザー・キーセットは、64 ビットのユーザー・キーセット値を含む 2 整数の配列の ucontext_t.__extctx.__ukeys にあります。
  • 定義済みの __EXTABI__ でコンパイルされたユーザー・コンテキスト構造 (setcontextgetcontextmakecontextswapcontext により使用)

サブルーチン

プロテクト・キーを使用するために、以下の新しい AIX カーネル・サブルーチンが提供されています。
サブルーチン 説明
システム構成 _SC_AIX_UKEYS とともに使用して、サポートされるユーザー・キーの数を判別します (旧バージョンの AIXで呼び出すことができます)。
「ukey_enable」 ユーザー・キー認識プログラミング環境をプロセスに対して有効にし、使用可能なユーザー・キーの数を報告します。
「ukeyset_init」 ユーザー・キーセットを初期化し、ユーザーの秘密鍵 (複数可) に対するアクセス権限セットを示します。
「ukeyset_add_key」 指定されたキーの読み取りアクセスまたは書き込みアクセス (あるいはその両方) をキーセットに追加します。
「ukeyset_remove_key」 指定されたキーの読み取りアクセスまたは書き込みアクセス (あるいはその両方) をキーセットから除去します。
「ukeyset_add_set」 あるキーセット内のすべてのアクセス権限を別のキーセットに追加します。
「ukeyset_remove_set」 あるキーセット内のすべてのアクセス権限を別のキーセットから除去します。
「ukeyset_activate」 キーセット内のアクセス権限を実行スレッドに適用します。
「ukeyset_ismember」 指定のアクセス権限がキーセットに含まれているかどうかテストします。
「ukey_setjmp」 アクティブ・キーセットを保持する setjmp の拡張形式 (ukey_jmp_buf 構造を使用します)。
pthread_attr_getukeyset_np pthread のキーセット属性を取得します。
「pthread_attr_setukeyset_np」 pthread のキーセット属性を設定します。
「ukey_protect」 ユーザー・メモリーのページ境界に合った範囲にユーザー・プロテクト・キーを設定します。
ukey_getkey 指定されたアドレスのユーザー・プロテクト・キーを検索します。

デバッグ

dbx コマンドは、保護キーの制限付きサポートを追加します。
  • 実行プログラムをデバッグする場合:
    • ukeyset サブコマンドは、アクティブなキーセットを表示します。
    • ukeyvalue サブコマンドは、指定されたメモリー位置に関連付けられた保護キーを表示します。
  • コア・ファイルをデバッグする場合、 ukeyexcept サブコマンドは、アクティブ・キー・セット、キー例外の有効アドレス、および関係するストレージ・キーを報告します。

ハードウェアの詳細

キー認識スレッドの実行コンテキストにおけるアクティブ・ユーザー・キーセットは、 ukeyset_t 抽象データ・タイプによって表されるフォーマットで、実際のハードウェア権限マスク・レジスター (AMR) に相当します。 この情報はデバッグ目的にのみ適用されます。 ユーザーのアクティブ・ユーザー・キーセットを設定するには、定義済みのプログラミング・サービスのみを使用してください。
  • AMR は 32 ビットのペアから構成される 64 ビット・レジスターです。キーごとに 1 ペアあり、0 から 31 までの番号が付けられた最大 32 個のキーがあります。
    • 各ペアの先頭ビットは、対応する番号付きキーへの書き込みアクセスを表します。
    • 各ペアの 2 番目のビットは、同様に、対応する番号付きキーへの読み取りアクセスを表します。
  • ビット値 0 は対応するアクセスを許可し、ビット値 1 はそのアクセスを拒否します。
  • キー 0 へのアクセスを許可するビット・ペアは、ユーザー・プログラムでは制御されません。 ユーザー・キー 0 はユーザー公開鍵 であり、アクティブ・ユーザー・キーセットの設定に関係なく、すべてのスレッドがこのキーのデータに対する全アクセス権限を常に保持しています。
  • その他のすべてのビット・ペアは、使用状況に応じて、適切なデータを保護するために使用できるユーザー秘密鍵 を表します。

サンプル・プログラム

ユーザー・キー認識プログラムのサンプルを次に示します。

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <sys/ukeys.h>
#include <sys/syspest.h>
#include <sys/signal.h> 
#include <sys/vminfo.h>

#define ROUND_UP(size,psize)    ((size)+(psize)-1 & ~((psize)-1))

/*
 * This is an example skeleton for a user key aware program.
 *
 * The private_data_1 structure will map a malloc'd key protected area
 * which the main program can access freely, while the "untrusted"
 * subroutine will only have read access.
 */
struct private_data_1 {
        int some_data;
};
struct private_data_1 *p1;      /* pointer to structure for protected data */

ukeyset_t keyset1RW;            /* keyset to give signal handler access */

/*
 * The untrusted function here should successfully read protected data.
 *
 * When the count is 0, it will just return so the caller can write
 * the incremented value back to the protected field.
 *
 * When the count is 1, it will try to update the protected field itself.
 * This should result in a SIGSEGV.
 */
int untrusted(struct private_data_1 *p1) {
        int count = p1->some_data;      /* We can read protected data */
        if (count == 1)
                p1->some_data = count;  /* But should not be able to write it */
        return count + 1;
}

/*
 * Signal handler to catch the deliberate protection violation in the
 * untrusted function above when count == 1.
 * Note that the handler is entered with NO access to our private data.
 */
void handler(int signo, siginfo_t *sip, void *ucp) {
        printf("siginfo: signo %d code %d\n", sip->si_signo, sip->si_code);
        (void)ukeyset_activate(keyset1RW, UKA_REPLACE_KEYS);
        exit(1);
}

main() {
        int nkeys;
        int pagesize = 4096;            /* hardware data page size */
        int padded_protsize_1;          /* page padded size of protected data */
        struct vm_page_info page_info;

        ukey_t key1 = UKEY_PRIVATE1;
        ukeyset_t keyset1W, oldset;
        int rc;
        int count = 0;

        struct sigaction sa;

        /*
         * Attempt to become user key aware.
         */
        nkeys = ukey_enable();
        if (nkeys == -1) {
                perror("ukey_enable");
                exit(1);
        }
        assert(nkeys >= 2);

        /*
         * Determine the data region page size.
         */
        page_info.addr = (long)&p1;             /* address in data region */
        rc = vmgetinfo(&page_info, VM_PAGE_INFO, sizeof(struct vm_page_info));

        if (rc)
                perror("vmgetinfo");
        else
                pagesize = page_info.pagesize;  /* pick up actual page size */

        /*
         * We need to allocate page aligned, page padded storage
         * for any area that is going to be key protected.
         */
        padded_protsize_1 = ROUND_UP(sizeof(struct private_data_1), pagesize);
        rc = posix_memalign((void **)&p1, pagesize, padded_protsize_1);
        if (rc) {
                perror("posix_memalign");
                exit(1);
        }

        /*
         * Initialize the private data.
         * We can do this before protecting it if we want.
         *
         * Note that the pointer to the private data is in public storage.
         * We only protect the data itself.
         */
        p1->some_data = count;

        /*
         * Construct keysets to use to access the protected structure.
         * Note that these keysets will be in public storage.
         */
        rc = ukeyset_init(&keyset1W, 0);
        if (rc) {
                perror("ukeyset_init");
                exit(1);
        }

        rc = ukeyset_add_key(&keyset1W, key1, UK_WRITE);        /* WRITE */
        if (rc) {
                perror("ukeyset_add_key 1W");
                exit(1);
        }

        keyset1RW = keyset1W;
        rc = ukeyset_add_key(&keyset1RW, key1, UK_READ);        /* R/W */
        if (rc) {
                perror("ukeyset_add_key 1R");
                exit(1);
        }

        /*
         * Restrict access to the private data by applying a private key
         * to the page(s) containing it.
         */
        rc = ukey_protect(p1, padded_protsize_1, key1);
        if (rc) {
                perror("ukey_protect");
                exit(1);
        }

        /*
         * Allow our general code to reference the private data R/W.
         */
        oldset = ukeyset_activate(keyset1RW, UKA_ADD_KEYS);
        if (oldset == UKSET_INVALID) {
                printf("ukeyset_activate failed\n");
                exit(1);
        }

        /*
         * Set up a signal handler for SIGSEGV, to catch the deliberate
         * key violation in the untrusted code.
         */
        sa.sa_sigaction = handler;
        SIGINITSET(sa.sa_mask);
        sa.sa_flags = SA_SIGINFO;
        rc = sigaction(SIGSEGV, &sa, 0);
        if (rc) {
                perror("sigaction");
                exit(1);
        }

        /*
         * Program's main processing loop.
         */
        while (count < 2) {
                /*
                 * When we need to run "untrusted" code, change access
                 * to the private data to R/O by removing write access.
                 */
                (void)ukeyset_activate(keyset1W, UKA_REMOVE_KEYS);

                /*
                 * Call untrusted subroutine here.  It can only read
                 * the protected data passed to it.
                 */
                count = untrusted(p1);

                /*
                 * Restore our full access to private data.
                 */
                (void)ukeyset_activate(keyset1W, UKA_ADD_KEYS);

                p1->some_data = count;
        }
ukey_protect(p1, padded_protsize_1, UKEY_PUBLIC);
        free(p1);
        exit(0);
}