ストレージ・プロテクト・キー
ストレージ・プロテクト・キーは、ユーザー・プログラムの信頼性を向上させるメカニズムを提供します。
プロテクト・キーは mprotect サブルーチンと同様に、メモリー・ページ数と細分化されたページ・レベルの作業に適用されます。プロテクト・キーを使用すると、1 ページ以上のページの読み取り保護または書き込み保護を行うことができます。 ただし、ストレージ・キーを使用すると、データのセクションを特定のレベルの読み取りおよび書き込みアクセスの保護に指定することができます。 ストレージ・キーによる保護は、データ・ページだけではなく、スレッド試行のアクセスの機能でもあります。 大規模プログラムでは使用できないデータにアクセスするために特定のコード・パスを使用可能にすることができます。これにより、重要なプログラム・データがカプセル化され、不測の障害から保護することができます。
キーで保護されたページへのアクセスは実行スレッドの属性であるため、このメカニズムはマルチスレッド・アプリケーションまで拡張されますが、1:1 (またはシステム有効範囲) pthreads のみ使用するという制限があります。 マルチスレッド環境では mprotect サブルーチン方法が機能しないことがあります。これは任意のスレッドにアクセス権を許可する場合に、すべてのスレッドの保護を除去しなければならないためです。 2 つのメカニズムは同時に使用でき、両方とも完全に実行されるため、プロテクト・キーにより別の方法で許可されていたとしても、ユーザーのプログラムは書き込み保護されたページに書き込むことはできません。
- ユーザー・プログラムの私用データを完全にカプセル化し、選択されたコード・パスのみにアクセスを限定します。
- ユーザー・プログラムの私用データを、常に読み取りアクセスを許可して実行する一方で、データの変更時のみ書き込みアクセスを許可することで不測の障害から保護します。 この方法は、コア・エンジンのコードで信頼性のないコードに対する呼び出しが許可されている場合に特に有効です。
- 複数の秘密鍵が使用可能な場合、さらに細分化されたデータ保護が可能です。
キーによる保護を考慮してアプリケーションを設計することで、デバッグを単純化することができます。 ページのプロテクト・キーの設定とアクティブ・ユーザー・キーセットの設定は両方ともシステム・コールであるため、比較的消費が多い操作です。 プログラムを設計する際は、これらの操作の頻度が過多にならないようにしてください。
ユーザー・プロテクト・キー
- カーネルから読み取り専用でエクスポートされるページは、プログラムから引き続き見えます。 これらのページには、UKEY_SYSTEM のプロテクト・キーがあります。 このプロテクト・キーはユーザー・プログラムの制御下にあるプロテクト・キーではありませんが、ユーザー・プログラムから常にアクセス可能です。
- ユーザー・プログラムのすべてのメモリー・ページには、最初にユーザー公開鍵が割り当てられています。 前述のように、キー 0 のストレージへのアクセスは常に許可され、これがユーザー公開鍵 となります。
- プロテクト・キーは、通常かつ共用のデータに対してのみ設定できます。 例えば、ライブラリー・データ、カーネルと共用されるロー・メモリー、またはプログラム・テキストは保護することができません。
- 基礎となるハードウェアと管理選択に応じた、限定数のユーザー秘密鍵のみが使用可能です (通常 1 つのみ)。 ユーザー・プログラムが 1 ページ以上のページに秘密鍵を割り当てると、これらのページのデータはデフォルトで使用できなくなります。 アクセス権限が必要なコード・パスを新しいサービスへの呼び出しで囲んでアクティブ・ユーザー・キーセットを管理することで、このデータに対する読み取りアクセスまたは書き込みアクセスを明示的に許可する必要があります。
- 物理的ハードウェアのほうが、ユーザー・プロテクト・キーとして使用できないプロテクト・キーをさらにサポートします。
- プロテクト・キーをページに割り当てるための特権は必要ありません。 ページに対する現行の書き込みアクセスのみが必要となります。
- プロテクト・キーによる実行権限の制御はありません。
ユーザー・プログラムがキーで保護されたデータに、そのアクティブ・ユーザー・キーセットで示されたアクセス権限に違反してアクセスした場合、読み取りまたは書き込み保護されたページに違反した場合のように、プログラムは SIGSEGV シグナルを受け取ります。 このシグナルを処理する場合は、シグナル・ハンドラーが秘密鍵へのアクセスなしで起動することに注意してください。 シグナル処理コードは、キーで保護されたデータを参照する前に、アクティブ・ユーザー・キーセットに対して必要なアクセス権限を追加します。
fork システム・コールで作成される子プロセスは、その親のメモリーと実行状態を論理的に継承します。 この場合、各ページに関連付けられたプロテクト・キーと、fork 時の親スレッドのアクティブ・ユーザー・キーセットも含まれます。
ユーザー・キーで保護される領域
キー保護に対するシステムの前提条件
- ストレージ・キー保護を提供する物理的ハードウェアで実行されている
- 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__ でコンパイルされたユーザー・コンテキスト構造 (setcontext、getcontext、makecontext、swapcontext により使用)
サブルーチン
| サブルーチン | 説明 |
|---|---|
| sysconf | _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 | 指定されたアドレスのユーザー・プロテクト・キーを検索します。 |
デバッグ
- 実行プログラムをデバッグする場合:
- ukeyset サブコマンドにより、アクティブ・キーセットが表示されます。
- ukeyvalue サブコマンドにより、指定のメモリー・ロケーションに関連付けられたプロテクト・キーが表示されます。
- コア・ファイルをデバッグする場合、ukeyexcept サブコマンドにより、アクティブ・キーセット、キー例外の有効アドレス、および関連するストレージ・キーが報告されます。
ハードウェアの詳細
- 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);
}