← Documents Documentation/core-api/protection-keys.rst GitHub 원문 ↗

Linux 6.18.37 · Core API

Memory Protection Keys

x86_64 PKRU와 arm64 POR_EL0 기반 memory protection key, 관련 system call, 권한 변경 예제와 mprotect 호환 동작을 설명합니다.

Source pathDocumentation/core-api/protection-keys.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약과 해설

protection-keys.rst:1-120

Memory Protection Keys는 page table을 다시 쓰지 않고 thread별 CPU register로 memory 접근 권한을 바꾸는 기능입니다. x86_64는 16개 key와 `PKRU`, arm64는 8개 key와 `POR_EL0`을 사용합니다.

`pkey_alloc()`, `pkey_mprotect()`, `pkey_free()`로 key의 수명과 memory mapping을 관리하고, architecture별 `pkey_set()` wrapper로 현재 thread의 권한을 빠르게 변경합니다.

Protection 위반은 `SIGSEGV`와 `SEGV_PKERR`로 보고되며 일반 `mprotect()` 위반의 `SEGV_ACCERR`와 구분됩니다. Arm64는 execute permission도 제어하지만 x86_64는 data access에만 적용합니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 .. SPDX-License-Identifier: GPL-2.0
2
3 ======================
4 Memory Protection Keys
5 ======================
6
7 Memory Protection Keys provide a mechanism for enforcing page-based
8 protections, but without requiring modification of the page tables when an
9 application changes protection domains.
10
11 Pkeys Userspace (PKU) is a feature which can be found on:
12 * Intel server CPUs, Skylake and later
13 * Intel client CPUs, Tiger Lake (11th Gen Core) and later
14 * Future AMD CPUs
15 * arm64 CPUs implementing the Permission Overlay Extension (FEAT_S1POE)
16
17 x86_64
18 ======
19 Pkeys work by dedicating 4 previously Reserved bits in each page table entry to
20 a "protection key", giving 16 possible keys.
21
22 Protections for each key are defined with a per-CPU user-accessible register
23 (PKRU). Each of these is a 32-bit register storing two bits (Access Disable
24 and Write Disable) for each of 16 keys.
25
26 Being a CPU register, PKRU is inherently thread-local, potentially giving each
27 thread a different set of protections from every other thread.
28
29 There are two instructions (RDPKRU/WRPKRU) for reading and writing to the
30 register. The feature is only available in 64-bit mode, even though there is
31 theoretically space in the PAE PTEs. These permissions are enforced on data
32 access only and have no effect on instruction fetches.
33
34 arm64
35 =====
36
37 Pkeys use 3 bits in each page table entry, to encode a "protection key index",
38 giving 8 possible keys.
39
40 Protections for each key are defined with a per-CPU user-writable system
41 register (POR_EL0). This is a 64-bit register encoding read, write and execute
42 overlay permissions for each protection key index.
43
44 Being a CPU register, POR_EL0 is inherently thread-local, potentially giving
45 each thread a different set of protections from every other thread.
46
47 Unlike x86_64, the protection key permissions also apply to instruction
48 fetches.
49
50 Syscalls
51 ========
52
53 There are 3 system calls which directly interact with pkeys::
54
55 int pkey_alloc(unsigned long flags, unsigned long init_access_rights)
56 int pkey_free(int pkey);
57 int pkey_mprotect(unsigned long start, size_t len,
58 unsigned long prot, int pkey);
59
60 Before a pkey can be used, it must first be allocated with pkey_alloc(). An
61 application writes to the architecture specific CPU register directly in order
62 to change access permissions to memory covered with a key. In this example
63 this is wrapped by a C function called pkey_set().
64 ::
65
66 int real_prot = PROT_READ|PROT_WRITE;
67 pkey = pkey_alloc(0, PKEY_DISABLE_WRITE);
68 ptr = mmap(NULL, PAGE_SIZE, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
69 ret = pkey_mprotect(ptr, PAGE_SIZE, real_prot, pkey);
70 ... application runs here
71
72 Now, if the application needs to update the data at 'ptr', it can
73 gain access, do the update, then remove its write access::
74
75 pkey_set(pkey, 0); // clear PKEY_DISABLE_WRITE
76 *ptr = foo; // assign something
77 pkey_set(pkey, PKEY_DISABLE_WRITE); // set PKEY_DISABLE_WRITE again
78
79 Now when it frees the memory, it will also free the pkey since it
80 is no longer in use::
81
82 munmap(ptr, PAGE_SIZE);
83 pkey_free(pkey);
84
85 .. note:: pkey_set() is a wrapper around writing to the CPU register.
86 Example implementations can be found in
87 tools/testing/selftests/mm/pkey-{arm64,powerpc,x86}.h
88
89 Behavior
90 ========
91
92 The kernel attempts to make protection keys consistent with the
93 behavior of a plain mprotect(). For instance if you do this::
94
95 mprotect(ptr, size, PROT_NONE);
96 something(ptr);
97
98 you can expect the same effects with protection keys when doing this::
99
100 pkey = pkey_alloc(0, PKEY_DISABLE_WRITE | PKEY_DISABLE_READ);
101 pkey_mprotect(ptr, size, PROT_READ|PROT_WRITE, pkey);
102 something(ptr);
103
104 That should be true whether something() is a direct access to 'ptr'
105 like::
106
107 *ptr = foo;
108
109 or when the kernel does the access on the application's behalf like
110 with a read()::
111
112 read(fd, ptr, 1);
113
114 The kernel will send a SIGSEGV in both cases, but si_code will be set
115 to SEGV_PKERR when violating protection keys versus SEGV_ACCERR when
116 the plain mprotect() permissions are violated.
117
118 Note that kernel accesses from a kthread (such as io_uring) will use a default
119 value for the protection key register and so will not be consistent with
120 userspace's value of the register or mprotect().
121

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

Memory Protection Keys 개요

1-16

SPDX 라이선스 식별자는 GPL-2.0입니다.

Memory Protection Keys

Memory Protection Keys는 page table을 수정하지 않고도 application이 protection domain을 바꿀 때 page 기반 보호를 강제하는 메커니즘입니다.

사용자 공간 protection key 기능인 Pkeys Userspace(PKU)는 다음 CPU에서 제공됩니다.

  • Skylake 이후의 Intel server CPU
  • Tiger Lake(11세대 Core) 이후의 Intel client CPU
  • 향후 AMD CPU
  • Permission Overlay Extension인 `FEAT_S1POE`를 구현한 arm64 CPU

x86_64 구현

17-33

x86_64

Pkey는 각 page table entry에서 이전에 Reserved였던 4비트를 protection key에 할당하여 16개의 key를 제공합니다.

각 key의 보호 설정은 CPU별 사용자 접근 가능 register인 `PKRU`로 정의합니다. `PKRU`는 32비트 register이며 16개 key 각각에 Access Disable과 Write Disable 두 비트를 저장합니다.

CPU register이므로 `PKRU`는 본질적으로 thread-local이며 각 thread가 다른 모든 thread와 서로 다른 보호 설정을 가질 수 있습니다.

Register를 읽고 쓰는 명령은 `RDPKRU`와 `WRPKRU` 두 개입니다. 이론적으로 PAE PTE에도 공간이 있지만 기능은 64비트 mode에서만 사용할 수 있습니다. 권한은 data access에만 적용되고 instruction fetch에는 영향을 주지 않습니다.

arm64 구현

34-49

arm64

Pkey는 각 page table entry의 3비트로 protection key index를 encoding하여 8개의 key를 제공합니다.

각 key의 보호 설정은 CPU별 사용자 쓰기 가능 system register인 `POR_EL0`으로 정의합니다. 이 64비트 register는 protection key index마다 read, write, execute overlay permission을 encoding합니다.

CPU register이므로 `POR_EL0`도 본질적으로 thread-local이며 각 thread가 서로 다른 보호 설정을 가질 수 있습니다.

x86_64와 달리 arm64의 protection key permission은 instruction fetch에도 적용됩니다.

시스템 호출과 사용 예

50-88

시스템 호출 (Syscalls)

Pkey와 직접 상호작용하는 system call은 세 개입니다.

int pkey_alloc(unsigned long flags, unsigned long init_access_rights)
int pkey_free(int pkey);
int pkey_mprotect(unsigned long start, size_t len,
                  unsigned long prot, int pkey);

Pkey를 사용하려면 먼저 `pkey_alloc()`으로 할당해야 합니다. Application은 key가 적용된 memory의 접근 권한을 바꾸기 위해 architecture별 CPU register에 직접 씁니다. 다음 예에서는 이 동작을 `pkey_set()`이라는 C 함수로 감쌉니다.

int real_prot = PROT_READ|PROT_WRITE;
pkey = pkey_alloc(0, PKEY_DISABLE_WRITE);
ptr = mmap(NULL, PAGE_SIZE, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
ret = pkey_mprotect(ptr, PAGE_SIZE, real_prot, pkey);
... application runs here

이후 application이 `ptr`의 데이터를 갱신해야 하면 접근 권한을 얻고 데이터를 수정한 뒤 write access를 다시 제거할 수 있습니다.

pkey_set(pkey, 0); // clear PKEY_DISABLE_WRITE
*ptr = foo; // assign something
pkey_set(pkey, PKEY_DISABLE_WRITE); // set PKEY_DISABLE_WRITE again

Memory를 해제할 때는 pkey도 더 이상 사용하지 않으므로 함께 해제합니다.

munmap(ptr, PAGE_SIZE);
pkey_free(pkey);

`pkey_set()`은 CPU register 쓰기를 감싸는 wrapper입니다. 예제 구현은 `tools/testing/selftests/mm/pkey-{arm64,powerpc,x86}.h`에서 확인할 수 있습니다.

mprotect()와의 동작 일관성

89-120

동작 (Behavior)

커널은 protection key가 일반 `mprotect()`와 일관된 방식으로 동작하도록 합니다. 예를 들어 다음 코드가 있다고 가정합니다.

mprotect(ptr, size, PROT_NONE);
something(ptr);

Protection key로도 다음과 같이 같은 효과를 기대할 수 있습니다.

pkey = pkey_alloc(0, PKEY_DISABLE_WRITE | PKEY_DISABLE_READ);
pkey_mprotect(ptr, size, PROT_READ|PROT_WRITE, pkey);
something(ptr);

이는 `something()`이 다음처럼 `ptr`에 직접 접근하는 경우에도 성립해야 합니다.

*ptr = foo;

또한 다음 `read()`처럼 커널이 application을 대신해 접근하는 경우에도 성립해야 합니다.

read(fd, ptr, 1);

두 경우 모두 커널은 `SIGSEGV`를 보냅니다. Protection key 위반이면 `si_code`가 `SEGV_PKERR`, 일반 `mprotect()` 권한 위반이면 `SEGV_ACCERR`로 설정됩니다.

`io_uring` 같은 kthread의 커널 접근은 protection key register의 기본값을 사용하므로 사용자 공간 register 값이나 `mprotect()` 동작과 일치하지 않습니다.