存储器保护密钥
存储器保护密钥为您提供了一个增加程序可靠性的机制。
保护键适用于内存页面并在页面详细程度级别工作,类似于 mprotect 子例程,可用于读/写保护一个或多个页面。 然而,使用存储密钥,您可以标记您的数据段为特定级别的读访问和写访问保护。 通过存储密钥保护不仅是用于数据页面的功能,而且还是用于线程尝试访问的功能。 您可以启用某些定义好的代码路径来访问对更大的程序不可用的数据,从而包括关键的程序数据并保护这些数据不受意外的破坏。
因为对使用密钥保护的页面的访问是运行中的线程的一个属性,所以该机制自然扩展到多线程应用程序,但是限制这些应用程序仅使用 1:1(或系统范围)pthread。 mprotect 子例程方法在多线程环境中无法可靠工作,因为当您要授予对任何线程的访问权时,必须除去对所有线程的保护。 您可以同时使用两种机制,两种机制都能完全执行;因此,即使保护密钥会另外允许,程序也不能写入一个写保护的页面。
- 完全包括程序的专用数据,限制仅对选择的代码路径进行访问。
- 通过始终运行经授权的读访问来保护程序的私有数据不受意外破坏,但是只在您想要修改这些数据时授予写访问权。 这对于核心引擎中的代码允许调用不信任的代码时尤为有用。
- 当多个专用密钥可用时,可以使用更详细的数据保护。
您可以考虑通过使用密钥保护设计您的应用程序来简化调试。 设置页面的保护密钥和设置活动用户密钥集都是系统调用,因此都是消耗大的操作。 设计的程序不要太频繁使用这些操作。
用户保护密钥
- 在您的程序中将能继续看到从内核导出的只读页面。 这些页面具有 UKEY_SYSTEM 保护密钥。 该保护密钥不是在您的程序控制下的保护密钥,但是您的程序始终可以访问它。
- 所有的程序内存页面初始都具有分配给它们的用户公用密钥。 如上面所提示的,对密钥 0 存储的访问始终是经授权的,使得该密钥成为用户公用密钥。
- 您可以设置保护密钥仅用于普通的和共享的数据。 例如,您不能保护库数据、与内核共享的低内存或程序文本。
- 根据底层的硬件和管理选项,只有有限数量的用户专用密钥(通常只有一个)可用。 当您的程序将专用密钥分配给它的一个或多个页面时,缺省情况下这些页面中的数据不再可用。 您必须通过围绕需要调用新服务进行访问的代码路径来明确授予对这些数据的读或写访问权,以管理您的活动用户密钥集。
- 物理硬件可能支持不能作为用户保护密钥使用的其他保护密钥。
- 不需要特殊特权就可以将保护密钥分配给页面。 唯一的要求是当前对该页面具有写访问权。
- 对保护密钥的执行权限没有控制。
如果您的程序访问密钥保护的数据时违反了其活动用户密钥集中注明的访问权,那么程序将接收到一个 SIGSEGV 信号,表示已经违例读或写保护页面。 如果选择处理该信号,那么请注意调用信号处理程序时没有访问专用密钥。 在引用密钥保护数据前,信号处理代码必须对活动用户密钥集添加任何需要的访问权。
由 fork 系统调用创建的子进程从逻辑上继承其父进程的内存和运行状态。 这包含与每个页面关联的保护密钥和派生时父线程的活动用户密钥集。
用户密钥保护的区域
密钥保护的系统先决条件
- 在提供存储密钥保护的物理硬件上运行
- 在 64 位内核上运行
- 启用使用用户保护密钥
密钥保护的程序先决条件
- 使用 ukey_enable 子例程声明自身用户密钥感知,并确定有多少用户保护密钥可用 (如果有)。
- 组织页边界上它保护的数据。
- 使用 ukey_protect 子例程将专用密钥分配给要保护的每个页面。
- 如果您保护 malloc'd 数据,那么记得在释放该数据之前取消其保护。
- 使用 ukeyset_init 子例程准备一个或多个密钥集。
- 可以使用 ukeyset_add_key 子例程将必需的密钥添加到密钥集,以根据需要启用将来的读或写访问。
- 使用 ukeyset_activate 子例程使密钥集处于活动状态,以授予密钥集中定义的访问权。
- 包含任何 M:N(进程范围)pthread
- 能够对其执行检查点(例如,环境中有 CHECKPOINT=yes)
- 信号处理程序接收到 ucontext_t 结构。 前一个活动用户密钥集在 ucontext_t.__extctx.__ukeys 中,一个包含 64 位用户密钥集值的两个整数的数组
- 使用定义的 __EXTABI__ 编译的用户上下文结构(由 setcontext、getcontext、makecontext 和 swapcontext 使用)
子例程
| 子例程 | 描述 |
|---|---|
| 系统配置 | 与 _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 位寄存器,每对一个密钥,最多 32 个密钥,编号从 0 到 31。
- 每对的第一位表示对相应编号密钥的写访问权。
- 类似的,每对的第二位表示对相应编号密钥的读访问权。
- 位值为 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);
}