← Documents Documentation/arch/x86/tdx.rst GitHub 원문 ↗

Linux 6.18.37 · Architecture

Intel Trust Domain Extensions (TDX)

TDX host 초기화, guest #VE와 memory model, attestation의 TDREPORT·Quote 흐름을 설명합니다.

Source pathDocumentation/arch/x86/tdx.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

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

1. 요약·해설

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

요약과 해설

tdx.rst:1-446

TDX는 SEAM 안의 CPU-attested TDX module과 MKTME private KeyID로 guest register state와 memory를 host에서 격리합니다. host kernel은 VMXON, per-CPU `tdx_cpu_enable()`, module-wide `tdx_enable()` 순서를 지키고 TDX-usable memory, hotplug, erratum과 power-state 제약을 관리합니다.

guest에서는 host에 register state를 노출하지 않도록 `#VE` handler와 `TDCALL`이 CPUID, MSR, MMIO를 중재합니다. private/shared memory의 trust boundary와 accept 절차를 지켜야 하며, attestation은 MRTD·RTMR measurement를 담은 `TDREPORT`를 SGX Quoting Enclave가 원격 검증 가능한 `Quote`로 바꾸는 두 단계로 진행됩니다.

2. 영어 원문 전체

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

원문 전체 펼치기
1 .. SPDX-License-Identifier: GPL-2.0
2
3 =====================================
4 Intel Trust Domain Extensions (TDX)
5 =====================================
6
7 Intel's Trust Domain Extensions (TDX) protect confidential guest VMs from
8 the host and physical attacks by isolating the guest register state and by
9 encrypting the guest memory. In TDX, a special module running in a special
10 mode sits between the host and the guest and manages the guest/host
11 separation.
12
13 TDX Host Kernel Support
14 =======================
15
16 TDX introduces a new CPU mode called Secure Arbitration Mode (SEAM) and
17 a new isolated range pointed by the SEAM Ranger Register (SEAMRR). A
18 CPU-attested software module called 'the TDX module' runs inside the new
19 isolated range to provide the functionalities to manage and run protected
20 VMs.
21
22 TDX also leverages Intel Multi-Key Total Memory Encryption (MKTME) to
23 provide crypto-protection to the VMs. TDX reserves part of MKTME KeyIDs
24 as TDX private KeyIDs, which are only accessible within the SEAM mode.
25 BIOS is responsible for partitioning legacy MKTME KeyIDs and TDX KeyIDs.
26
27 Before the TDX module can be used to create and run protected VMs, it
28 must be loaded into the isolated range and properly initialized. The TDX
29 architecture doesn't require the BIOS to load the TDX module, but the
30 kernel assumes it is loaded by the BIOS.
31
32 TDX boot-time detection
33 -----------------------
34
35 The kernel detects TDX by detecting TDX private KeyIDs during kernel
36 boot. Below dmesg shows when TDX is enabled by BIOS::
37
38 [..] virt/tdx: BIOS enabled: private KeyID range: [16, 64)
39
40 TDX module initialization
41 ---------------------------------------
42
43 The kernel talks to the TDX module via the new SEAMCALL instruction. The
44 TDX module implements SEAMCALL leaf functions to allow the kernel to
45 initialize it.
46
47 If the TDX module isn't loaded, the SEAMCALL instruction fails with a
48 special error. In this case the kernel fails the module initialization
49 and reports the module isn't loaded::
50
51 [..] virt/tdx: module not loaded
52
53 Initializing the TDX module consumes roughly ~1/256th system RAM size to
54 use it as 'metadata' for the TDX memory. It also takes additional CPU
55 time to initialize those metadata along with the TDX module itself. Both
56 are not trivial. The kernel initializes the TDX module at runtime on
57 demand.
58
59 Besides initializing the TDX module, a per-cpu initialization SEAMCALL
60 must be done on one cpu before any other SEAMCALLs can be made on that
61 cpu.
62
63 The kernel provides two functions, tdx_enable() and tdx_cpu_enable() to
64 allow the user of TDX to enable the TDX module and enable TDX on local
65 cpu respectively.
66
67 Making SEAMCALL requires VMXON has been done on that CPU. Currently only
68 KVM implements VMXON. For now both tdx_enable() and tdx_cpu_enable()
69 don't do VMXON internally (not trivial), but depends on the caller to
70 guarantee that.
71
72 To enable TDX, the caller of TDX should: 1) temporarily disable CPU
73 hotplug; 2) do VMXON and tdx_enable_cpu() on all online cpus; 3) call
74 tdx_enable(). For example::
75
76 cpus_read_lock();
77 on_each_cpu(vmxon_and_tdx_cpu_enable());
78 ret = tdx_enable();
79 cpus_read_unlock();
80 if (ret)
81 goto no_tdx;
82 // TDX is ready to use
83
84 And the caller of TDX must guarantee the tdx_cpu_enable() has been
85 successfully done on any cpu before it wants to run any other SEAMCALL.
86 A typical usage is do both VMXON and tdx_cpu_enable() in CPU hotplug
87 online callback, and refuse to online if tdx_cpu_enable() fails.
88
89 User can consult dmesg to see whether the TDX module has been initialized.
90
91 If the TDX module is initialized successfully, dmesg shows something
92 like below::
93
94 [..] virt/tdx: 262668 KBs allocated for PAMT
95 [..] virt/tdx: module initialized
96
97 If the TDX module failed to initialize, dmesg also shows it failed to
98 initialize::
99
100 [..] virt/tdx: module initialization failed ...
101
102 TDX Interaction to Other Kernel Components
103 ------------------------------------------
104
105 TDX Memory Policy
106 ~~~~~~~~~~~~~~~~~
107
108 TDX reports a list of "Convertible Memory Region" (CMR) to tell the
109 kernel which memory is TDX compatible. The kernel needs to build a list
110 of memory regions (out of CMRs) as "TDX-usable" memory and pass those
111 regions to the TDX module. Once this is done, those "TDX-usable" memory
112 regions are fixed during module's lifetime.
113
114 To keep things simple, currently the kernel simply guarantees all pages
115 in the page allocator are TDX memory. Specifically, the kernel uses all
116 system memory in the core-mm "at the time of TDX module initialization"
117 as TDX memory, and in the meantime, refuses to online any non-TDX-memory
118 in the memory hotplug.
119
120 Physical Memory Hotplug
121 ~~~~~~~~~~~~~~~~~~~~~~~
122
123 Note TDX assumes convertible memory is always physically present during
124 machine's runtime. A non-buggy BIOS should never support hot-removal of
125 any convertible memory. This implementation doesn't handle ACPI memory
126 removal but depends on the BIOS to behave correctly.
127
128 CPU Hotplug
129 ~~~~~~~~~~~
130
131 TDX module requires the per-cpu initialization SEAMCALL must be done on
132 one cpu before any other SEAMCALLs can be made on that cpu. The kernel
133 provides tdx_cpu_enable() to let the user of TDX to do it when the user
134 wants to use a new cpu for TDX task.
135
136 TDX doesn't support physical (ACPI) CPU hotplug. During machine boot,
137 TDX verifies all boot-time present logical CPUs are TDX compatible before
138 enabling TDX. A non-buggy BIOS should never support hot-add/removal of
139 physical CPU. Currently the kernel doesn't handle physical CPU hotplug,
140 but depends on the BIOS to behave correctly.
141
142 Note TDX works with CPU logical online/offline, thus the kernel still
143 allows to offline logical CPU and online it again.
144
145 Erratum
146 ~~~~~~~
147
148 The first few generations of TDX hardware have an erratum. A partial
149 write to a TDX private memory cacheline will silently "poison" the
150 line. Subsequent reads will consume the poison and generate a machine
151 check.
152
153 A partial write is a memory write where a write transaction of less than
154 cacheline lands at the memory controller. The CPU does these via
155 non-temporal write instructions (like MOVNTI), or through UC/WC memory
156 mappings. Devices can also do partial writes via DMA.
157
158 Theoretically, a kernel bug could do partial write to TDX private memory
159 and trigger unexpected machine check. What's more, the machine check
160 code will present these as "Hardware error" when they were, in fact, a
161 software-triggered issue. But in the end, this issue is hard to trigger.
162
163 If the platform has such erratum, the kernel prints additional message in
164 machine check handler to tell user the machine check may be caused by
165 kernel bug on TDX private memory.
166
167 Kexec
168 ~~~~~~~
169
170 Currently kexec doesn't work on the TDX platforms with the aforementioned
171 erratum. It fails when loading the kexec kernel image. Otherwise it
172 works normally.
173
174 Interaction vs S3 and deeper states
175 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
176
177 TDX cannot survive from S3 and deeper states. The hardware resets and
178 disables TDX completely when platform goes to S3 and deeper. Both TDX
179 guests and the TDX module get destroyed permanently.
180
181 The kernel uses S3 for suspend-to-ram, and use S4 and deeper states for
182 hibernation. Currently, for simplicity, the kernel chooses to make TDX
183 mutually exclusive with S3 and hibernation.
184
185 The kernel disables TDX during early boot when hibernation support is
186 available::
187
188 [..] virt/tdx: initialization failed: Hibernation support is enabled
189
190 Add 'nohibernate' kernel command line to disable hibernation in order to
191 use TDX.
192
193 ACPI S3 is disabled during kernel early boot if TDX is enabled. The user
194 needs to turn off TDX in the BIOS in order to use S3.
195
196 TDX Guest Support
197 =================
198 Since the host cannot directly access guest registers or memory, much
199 normal functionality of a hypervisor must be moved into the guest. This is
200 implemented using a Virtualization Exception (#VE) that is handled by the
201 guest kernel. A #VE is handled entirely inside the guest kernel, but some
202 require the hypervisor to be consulted.
203
204 TDX includes new hypercall-like mechanisms for communicating from the
205 guest to the hypervisor or the TDX module.
206
207 New TDX Exceptions
208 ------------------
209
210 TDX guests behave differently from bare-metal and traditional VMX guests.
211 In TDX guests, otherwise normal instructions or memory accesses can cause
212 #VE or #GP exceptions.
213
214 Instructions marked with an '*' conditionally cause exceptions. The
215 details for these instructions are discussed below.
216
217 Instruction-based #VE
218 ~~~~~~~~~~~~~~~~~~~~~
219
220 - Port I/O (INS, OUTS, IN, OUT)
221 - HLT
222 - MONITOR, MWAIT
223 - WBINVD, INVD
224 - VMCALL
225 - RDMSR*,WRMSR*
226 - CPUID*
227
228 Instruction-based #GP
229 ~~~~~~~~~~~~~~~~~~~~~
230
231 - All VMX instructions: INVEPT, INVVPID, VMCLEAR, VMFUNC, VMLAUNCH,
232 VMPTRLD, VMPTRST, VMREAD, VMRESUME, VMWRITE, VMXOFF, VMXON
233 - ENCLS, ENCLU
234 - GETSEC
235 - RSM
236 - ENQCMD
237 - RDMSR*,WRMSR*
238
239 RDMSR/WRMSR Behavior
240 ~~~~~~~~~~~~~~~~~~~~
241
242 MSR access behavior falls into three categories:
243
244 - #GP generated
245 - #VE generated
246 - "Just works"
247
248 In general, the #GP MSRs should not be used in guests. Their use likely
249 indicates a bug in the guest. The guest may try to handle the #GP with a
250 hypercall but it is unlikely to succeed.
251
252 The #VE MSRs are typically able to be handled by the hypervisor. Guests
253 can make a hypercall to the hypervisor to handle the #VE.
254
255 The "just works" MSRs do not need any special guest handling. They might
256 be implemented by directly passing through the MSR to the hardware or by
257 trapping and handling in the TDX module. Other than possibly being slow,
258 these MSRs appear to function just as they would on bare metal.
259
260 CPUID Behavior
261 ~~~~~~~~~~~~~~
262
263 For some CPUID leaves and sub-leaves, the virtualized bit fields of CPUID
264 return values (in guest EAX/EBX/ECX/EDX) are configurable by the
265 hypervisor. For such cases, the Intel TDX module architecture defines two
266 virtualization types:
267
268 - Bit fields for which the hypervisor controls the value seen by the guest
269 TD.
270
271 - Bit fields for which the hypervisor configures the value such that the
272 guest TD either sees their native value or a value of 0. For these bit
273 fields, the hypervisor can mask off the native values, but it can not
274 turn *on* values.
275
276 A #VE is generated for CPUID leaves and sub-leaves that the TDX module does
277 not know how to handle. The guest kernel may ask the hypervisor for the
278 value with a hypercall.
279
280 #VE on Memory Accesses
281 ----------------------
282
283 There are essentially two classes of TDX memory: private and shared.
284 Private memory receives full TDX protections. Its content is protected
285 against access from the hypervisor. Shared memory is expected to be
286 shared between guest and hypervisor and does not receive full TDX
287 protections.
288
289 A TD guest is in control of whether its memory accesses are treated as
290 private or shared. It selects the behavior with a bit in its page table
291 entries. This helps ensure that a guest does not place sensitive
292 information in shared memory, exposing it to the untrusted hypervisor.
293
294 #VE on Shared Memory
295 ~~~~~~~~~~~~~~~~~~~~
296
297 Access to shared mappings can cause a #VE. The hypervisor ultimately
298 controls whether a shared memory access causes a #VE, so the guest must be
299 careful to only reference shared pages it can safely handle a #VE. For
300 instance, the guest should be careful not to access shared memory in the
301 #VE handler before it reads the #VE info structure (TDG.VP.VEINFO.GET).
302
303 Shared mapping content is entirely controlled by the hypervisor. The guest
304 should only use shared mappings for communicating with the hypervisor.
305 Shared mappings must never be used for sensitive memory content like kernel
306 stacks. A good rule of thumb is that hypervisor-shared memory should be
307 treated the same as memory mapped to userspace. Both the hypervisor and
308 userspace are completely untrusted.
309
310 MMIO for virtual devices is implemented as shared memory. The guest must
311 be careful not to access device MMIO regions unless it is also prepared to
312 handle a #VE.
313
314 #VE on Private Pages
315 ~~~~~~~~~~~~~~~~~~~~
316
317 An access to private mappings can also cause a #VE. Since all kernel
318 memory is also private memory, the kernel might theoretically need to
319 handle a #VE on arbitrary kernel memory accesses. This is not feasible, so
320 TDX guests ensure that all guest memory has been "accepted" before memory
321 is used by the kernel.
322
323 A modest amount of memory (typically 512M) is pre-accepted by the firmware
324 before the kernel runs to ensure that the kernel can start up without
325 being subjected to a #VE.
326
327 The hypervisor is permitted to unilaterally move accepted pages to a
328 "blocked" state. However, if it does this, page access will not generate a
329 #VE. It will, instead, cause a "TD Exit" where the hypervisor is required
330 to handle the exception.
331
332 Linux #VE handler
333 -----------------
334
335 Just like page faults or #GP's, #VE exceptions can be either handled or be
336 fatal. Typically, an unhandled userspace #VE results in a SIGSEGV.
337 An unhandled kernel #VE results in an oops.
338
339 Handling nested exceptions on x86 is typically nasty business. A #VE
340 could be interrupted by an NMI which triggers another #VE and hilarity
341 ensues. The TDX #VE architecture anticipated this scenario and includes a
342 feature to make it slightly less nasty.
343
344 During #VE handling, the TDX module ensures that all interrupts (including
345 NMIs) are blocked. The block remains in place until the guest makes a
346 TDG.VP.VEINFO.GET TDCALL. This allows the guest to control when interrupts
347 or a new #VE can be delivered.
348
349 However, the guest kernel must still be careful to avoid potential
350 #VE-triggering actions (discussed above) while this block is in place.
351 While the block is in place, any #VE is elevated to a double fault (#DF)
352 which is not recoverable.
353
354 MMIO handling
355 -------------
356
357 In non-TDX VMs, MMIO is usually implemented by giving a guest access to a
358 mapping which will cause a VMEXIT on access, and then the hypervisor
359 emulates the access. That is not possible in TDX guests because VMEXIT
360 will expose the register state to the host. TDX guests don't trust the host
361 and can't have their state exposed to the host.
362
363 In TDX, MMIO regions typically trigger a #VE exception in the guest. The
364 guest #VE handler then emulates the MMIO instruction inside the guest and
365 converts it into a controlled TDCALL to the host, rather than exposing
366 guest state to the host.
367
368 MMIO addresses on x86 are just special physical addresses. They can
369 theoretically be accessed with any instruction that accesses memory.
370 However, the kernel instruction decoding method is limited. It is only
371 designed to decode instructions like those generated by io.h macros.
372
373 MMIO access via other means (like structure overlays) may result in an
374 oops.
375
376 Shared Memory Conversions
377 -------------------------
378
379 All TDX guest memory starts out as private at boot. This memory can not
380 be accessed by the hypervisor. However, some kernel users like device
381 drivers might have a need to share data with the hypervisor. To do this,
382 memory must be converted between shared and private. This can be
383 accomplished using some existing memory encryption helpers:
384
385 * set_memory_decrypted() converts a range of pages to shared.
386 * set_memory_encrypted() converts memory back to private.
387
388 Device drivers are the primary user of shared memory, but there's no need
389 to touch every driver. DMA buffers and ioremap() do the conversions
390 automatically.
391
392 TDX uses SWIOTLB for most DMA allocations. The SWIOTLB buffer is
393 converted to shared on boot.
394
395 For coherent DMA allocation, the DMA buffer gets converted on the
396 allocation. Check force_dma_unencrypted() for details.
397
398 Attestation
399 ===========
400
401 Attestation is used to verify the TDX guest trustworthiness to other
402 entities before provisioning secrets to the guest. For example, a key
403 server may want to use attestation to verify that the guest is the
404 desired one before releasing the encryption keys to mount the encrypted
405 rootfs or a secondary drive.
406
407 The TDX module records the state of the TDX guest in various stages of
408 the guest boot process using the build time measurement register (MRTD)
409 and runtime measurement registers (RTMR). Measurements related to the
410 guest initial configuration and firmware image are recorded in the MRTD
411 register. Measurements related to initial state, kernel image, firmware
412 image, command line options, initrd, ACPI tables, etc are recorded in
413 RTMR registers. For more details, as an example, please refer to TDX
414 Virtual Firmware design specification, section titled "TD Measurement".
415 At TDX guest runtime, the attestation process is used to attest to these
416 measurements.
417
418 The attestation process consists of two steps: TDREPORT generation and
419 Quote generation.
420
421 TDX guest uses TDCALL[TDG.MR.REPORT] to get the TDREPORT (TDREPORT_STRUCT)
422 from the TDX module. TDREPORT is a fixed-size data structure generated by
423 the TDX module which contains guest-specific information (such as build
424 and boot measurements), platform security version, and the MAC to protect
425 the integrity of the TDREPORT. A user-provided 64-Byte REPORTDATA is used
426 as input and included in the TDREPORT. Typically it can be some nonce
427 provided by attestation service so the TDREPORT can be verified uniquely.
428 More details about the TDREPORT can be found in Intel TDX Module
429 specification, section titled "TDG.MR.REPORT Leaf".
430
431 After getting the TDREPORT, the second step of the attestation process
432 is to send it to the Quoting Enclave (QE) to generate the Quote. TDREPORT
433 by design can only be verified on the local platform as the MAC key is
434 bound to the platform. To support remote verification of the TDREPORT,
435 TDX leverages Intel SGX Quoting Enclave to verify the TDREPORT locally
436 and convert it to a remotely verifiable Quote. Method of sending TDREPORT
437 to QE is implementation specific. Attestation software can choose
438 whatever communication channel available (i.e. vsock or TCP/IP) to
439 send the TDREPORT to QE and receive the Quote.
440
441 References
442 ==========
443
444 TDX reference material is collected here:
445
446 https://www.intel.com/content/www/us/en/developer/articles/technical/intel-trust-domain-extensions.html
447

3. 한국어 전문 번역

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

TDX host 기반 구조

1-31

이 문서는 `SPDX-License-Identifier: GPL-2.0`으로 배포됩니다. Intel Trust Domain Extensions(TDX)는 guest register state를 격리하고 guest memory를 암호화해 confidential guest VM을 host와 물리 공격으로부터 보호합니다. TDX에서는 특별한 mode로 실행되는 전용 module이 host와 guest 사이에 위치해 둘의 분리를 관리합니다.

TDX는 Secure Arbitration Mode(SEAM)라는 새 CPU mode와 SEAM Ranger Register(SEAMRR)가 가리키는 격리 범위를 도입합니다. CPU가 attestation한 software인 `TDX module`은 이 격리 범위 안에서 실행되며 protected VM을 관리하고 실행하는 기능을 제공합니다.

VM의 cryptographic protection에는 Intel Multi-Key Total Memory Encryption(MKTME)도 활용합니다. TDX는 MKTME KeyID 일부를 SEAM mode에서만 access할 수 있는 TDX private KeyID로 예약하며, legacy MKTME KeyID와 TDX KeyID의 partition은 BIOS가 담당합니다.

protected VM을 만들고 실행하기 전에 TDX module을 격리 범위에 load하고 올바르게 initialize해야 합니다. TDX architecture 자체는 BIOS가 module을 load하도록 요구하지 않지만 kernel은 BIOS가 이미 load했다고 가정합니다.

boot-time TDX 감지

32-39

kernel은 boot 중 TDX private KeyID를 감지해 TDX를 판별합니다. BIOS에서 TDX를 활성화하면 dmesg에 private KeyID 범위가 다음과 같이 나타납니다.

[..] virt/tdx: BIOS enabled: private KeyID range: [16, 64)

TDX module과 CPU 초기화

40-101

kernel은 새 `SEAMCALL` instruction으로 TDX module과 통신합니다. TDX module은 kernel이 module을 initialize할 수 있도록 `SEAMCALL` leaf function을 구현합니다.

TDX module이 load되지 않았다면 `SEAMCALL`이 특별한 error로 실패합니다. 이때 kernel은 module initialization을 실패 처리하고 module이 load되지 않았다고 보고합니다.

[..] virt/tdx: module not loaded

TDX module initialization은 TDX memory용 metadata로 system RAM의 약 1/256을 소비합니다. TDX module 자체와 이 metadata를 initialize하는 데 추가 CPU 시간도 필요하며 둘 다 무시할 수 없는 비용입니다. 따라서 kernel은 runtime에 demand가 생길 때 TDX module을 initialize합니다.

module initialization과 별도로, 어떤 CPU에서든 다른 `SEAMCALL`을 실행하기 전에 그 CPU에서 per-CPU initialization `SEAMCALL`을 한 번 수행해야 합니다.

kernel은 TDX 사용자가 TDX module 전체와 local CPU의 TDX를 각각 활성화할 수 있도록 `tdx_enable()`과 `tdx_cpu_enable()`을 제공합니다.

`SEAMCALL`을 실행하려면 해당 CPU에서 `VMXON`이 먼저 완료되어야 합니다. 현재 `VMXON`을 구현하는 것은 KVM뿐입니다. `VMXON`을 내부에서 처리하는 일이 간단하지 않으므로 현재 `tdx_enable()`과 `tdx_cpu_enable()`은 caller가 이를 보장한다고 가정합니다.

TDX를 활성화하는 caller는 1) CPU hotplug를 잠시 비활성화하고, 2) online CPU 모두에서 `VMXON`과 `tdx_enable_cpu()`를 수행한 뒤, 3) `tdx_enable()`을 호출해야 합니다. 예시는 다음과 같습니다.

cpus_read_lock();
on_each_cpu(vmxon_and_tdx_cpu_enable());
ret = tdx_enable();
cpus_read_unlock();
if (ret)
        goto no_tdx;
// TDX is ready to use

TDX caller는 어느 CPU에서든 다른 `SEAMCALL`을 실행하기 전에 `tdx_cpu_enable()`이 성공했음을 반드시 보장해야 합니다. 일반적인 사용법은 CPU hotplug online callback에서 `VMXON`과 `tdx_cpu_enable()`을 모두 실행하고, `tdx_cpu_enable()`이 실패한 CPU의 online 전환을 거부하는 것입니다.

사용자는 dmesg에서 TDX module initialization 여부를 확인할 수 있습니다. 성공하면 PAMT에 allocate한 크기와 initialization 완료가 다음처럼 표시됩니다.

[..] virt/tdx: 262668 KBs allocated for PAMT
[..] virt/tdx: module initialized

TDX module initialization이 실패한 경우에도 dmesg가 실패를 알립니다.

[..] virt/tdx: module initialization failed ...

memory policy와 physical·logical hotplug

102-143

TDX는 `Convertible Memory Region`(CMR) 목록으로 TDX와 호환되는 memory를 kernel에 알립니다. kernel은 CMR 안에서 `TDX-usable` memory region 목록을 구성해 TDX module에 전달해야 합니다. 한번 설정한 `TDX-usable` region은 module의 lifetime 동안 고정됩니다.

현재 kernel은 구현을 단순하게 유지하기 위해 page allocator의 모든 page가 TDX memory임을 보장합니다. 구체적으로 TDX module initialization 시점에 core-mm에 있는 system memory 전부를 TDX memory로 사용하며, memory hotplug에서 non-TDX memory를 online하는 것은 거부합니다.

TDX는 convertible memory가 machine runtime 내내 물리적으로 존재한다고 가정합니다. 정상적인 BIOS라면 어떤 convertible memory도 hot-remove하도록 지원해서는 안 됩니다. 현재 구현은 ACPI memory removal을 직접 처리하지 않고 BIOS가 올바르게 동작한다고 가정합니다.

TDX module은 각 CPU에서 다른 `SEAMCALL`을 실행하기 전에 per-CPU initialization `SEAMCALL`이 완료되어야 한다고 요구합니다. kernel은 TDX 사용자가 새 CPU를 TDX 작업에 쓰려 할 때 이를 수행할 수 있도록 `tdx_cpu_enable()`을 제공합니다.

TDX는 physical ACPI CPU hotplug를 지원하지 않습니다. machine boot 중 TDX를 활성화하기 전에 boot 시점에 존재하는 logical CPU가 모두 TDX-compatible인지 검증합니다. 정상적인 BIOS는 physical CPU hot-add 또는 hot-removal을 지원해서는 안 됩니다. 현재 kernel은 physical CPU hotplug를 처리하지 않고 BIOS의 올바른 동작에 의존합니다.

반면 TDX는 logical CPU의 online/offline과 함께 동작하므로 kernel은 logical CPU를 offline했다가 다시 online하는 것을 허용합니다.

erratum, kexec와 전원 상태

144-195

초기 몇 세대 TDX hardware에는 erratum이 있습니다. TDX private memory cacheline에 partial write를 하면 해당 line이 조용히 `poison`되고, 이후 read가 poison을 소비하면서 machine check가 발생합니다.

partial write란 cacheline보다 작은 write transaction이 memory controller에 도달하는 memory write입니다. CPU는 `MOVNTI` 같은 non-temporal write instruction이나 UC/WC memory mapping으로 이를 만들 수 있으며, device도 DMA를 통해 partial write를 수행할 수 있습니다.

이론적으로 kernel bug가 TDX private memory에 partial write를 수행해 예상하지 못한 machine check를 일으킬 수 있습니다. 더구나 실제로는 software가 촉발한 문제인데도 machine-check code는 이를 `Hardware error`로 표시합니다. 다만 이 문제는 실제로 촉발하기 어렵습니다.

platform에 이 erratum이 있다면 kernel은 machine-check handler에 추가 message를 출력해, TDX private memory에 대한 kernel bug가 machine check의 원인일 수 있음을 사용자에게 알립니다.

앞서 설명한 erratum이 있는 TDX platform에서는 현재 `kexec`가 동작하지 않으며 kexec kernel image를 load하는 단계에서 실패합니다. 이 erratum이 없으면 정상적으로 동작합니다.

TDX는 S3 이하의 더 깊은 power state를 통과해 유지될 수 없습니다. platform이 S3 이하로 들어가면 hardware가 reset되고 TDX를 완전히 비활성화하며 TDX guest와 TDX module이 모두 영구적으로 파괴됩니다.

kernel은 suspend-to-RAM에 S3를, hibernation에 S4 이하의 state를 사용합니다. 현재 kernel은 구현을 단순화하기 위해 TDX가 S3 및 hibernation과 상호 배타적이 되도록 합니다.

hibernation support를 사용할 수 있으면 kernel은 early boot 중 TDX를 비활성화하고 다음 message를 출력합니다.

[..] virt/tdx: initialization failed: Hibernation support is enabled

TDX를 사용하려면 `nohibernate` kernel command-line option을 추가해 hibernation을 비활성화해야 합니다. TDX가 활성화된 경우 ACPI S3도 kernel early boot에서 비활성화됩니다. S3를 사용하려면 BIOS에서 TDX를 꺼야 합니다.

TDX guest 지원 모델

196-206

host가 guest register나 memory에 직접 access할 수 없으므로 hypervisor의 일반 기능 상당 부분을 guest 안으로 옮겨야 합니다. guest kernel이 처리하는 Virtualization Exception(`#VE`)으로 이를 구현합니다. `#VE`는 guest kernel 안에서 전부 처리하지만 일부 경우에는 hypervisor와 상의해야 합니다.

TDX는 guest가 hypervisor 또는 TDX module과 통신할 수 있도록 hypercall과 비슷한 새 mechanism을 제공합니다.

instruction, MSR와 CPUID 예외

207-279

TDX guest는 bare-metal이나 전통적인 VMX guest와 다르게 동작합니다. 일반적으로 정상인 instruction 또는 memory access도 TDX guest에서는 `#VE`나 `#GP` exception을 일으킬 수 있습니다. 별표가 붙은 instruction은 조건에 따라 exception을 일으키며 세부 동작은 아래에서 설명합니다.

다음 instruction은 instruction 기반 `#VE`를 일으킵니다.

  • Port I/O: `INS`, `OUTS`, `IN`, `OUT`
  • `HLT`
  • `MONITOR`, `MWAIT`
  • `WBINVD`, `INVD`
  • `VMCALL`
  • `RDMSR*`, `WRMSR*`
  • `CPUID*`

다음 instruction은 instruction 기반 `#GP`를 일으킵니다.

  • 모든 VMX instruction: `INVEPT`, `INVVPID`, `VMCLEAR`, `VMFUNC`, `VMLAUNCH`, `VMPTRLD`, `VMPTRST`, `VMREAD`, `VMRESUME`, `VMWRITE`, `VMXOFF`, `VMXON`
  • `ENCLS`, `ENCLU`
  • `GETSEC`
  • `RSM`
  • `ENQCMD`
  • `RDMSR*`, `WRMSR*`

MSR access 동작은 다음 세 범주로 나뉩니다.

범주guest 동작
`#GP` generatedguest에서 사용하지 않아야 합니다. 사용했다면 guest bug일 가능성이 크며 hypercall로 처리하려 해도 성공 가능성이 낮습니다.
`#VE` generated대체로 hypervisor가 처리할 수 있으므로 guest가 hypercall로 `#VE` 처리를 요청할 수 있습니다.
`Just works`특별한 guest 처리가 필요 없습니다. hardware에 MSR을 직접 pass-through하거나 TDX module이 trap해 처리할 수 있으며, 느릴 가능성을 제외하면 bare metal과 같은 방식으로 동작합니다.

일부 CPUID leaf와 sub-leaf에서 guest `EAX/EBX/ECX/EDX`로 반환되는 virtualized bit field는 hypervisor가 구성할 수 있습니다. Intel TDX module architecture는 이런 경우 두 virtualization type을 정의합니다.

  • guest TD가 보는 값을 hypervisor가 직접 제어하는 bit field.
  • guest TD가 native value 또는 0만 보도록 hypervisor가 구성하는 bit field. hypervisor는 native value를 mask off할 수 있지만 값을 새로 turn on할 수는 없습니다.

TDX module이 처리 방법을 모르는 CPUID leaf와 sub-leaf에는 `#VE`가 발생합니다. guest kernel은 hypercall로 그 값을 hypervisor에 요청할 수 있습니다.

shared·private memory의 #VE

280-331

TDX memory는 본질적으로 private과 shared 두 종류입니다. private memory는 TDX protection을 모두 적용받고 hypervisor의 access로부터 content를 보호합니다. shared memory는 guest와 hypervisor가 공유하기 위한 것이며 완전한 TDX protection을 받지 않습니다.

TD guest는 page-table entry의 bit 하나로 memory access를 private 또는 shared로 취급할지 제어합니다. 이 방식은 guest가 sensitive information을 shared memory에 두어 untrusted hypervisor에 노출하지 않게 돕습니다.

shared mapping에 access하면 `#VE`가 발생할 수 있습니다. shared-memory access가 `#VE`를 일으킬지는 최종적으로 hypervisor가 제어하므로 guest는 `#VE`를 안전하게 처리할 수 있는 shared page만 참조해야 합니다. 예를 들어 `#VE` handler가 `TDG.VP.VEINFO.GET`으로 `#VE` info structure를 읽기 전에는 shared memory에 access하지 않도록 주의해야 합니다.

shared mapping content는 hypervisor가 완전히 제어합니다. guest는 hypervisor와 통신할 때만 shared mapping을 사용해야 하며 kernel stack 같은 sensitive memory를 절대 두어서는 안 됩니다. hypervisor-shared memory는 userspace에 mapping한 memory와 똑같이 취급하는 것이 좋은 원칙입니다. hypervisor와 userspace 모두 완전히 신뢰할 수 없습니다.

virtual device의 MMIO는 shared memory로 구현합니다. guest는 `#VE`를 처리할 준비가 된 경우에만 device MMIO region에 access해야 합니다.

private mapping access도 `#VE`를 일으킬 수 있습니다. 모든 kernel memory가 private memory이므로 이론적으로 kernel은 임의의 kernel-memory access에서 `#VE`를 처리해야 할 수도 있습니다. 이는 현실적으로 불가능하므로 TDX guest는 kernel이 memory를 사용하기 전에 모든 guest memory가 `accepted` 상태임을 보장합니다.

kernel이 `#VE` 없이 boot를 시작할 수 있도록 firmware가 kernel 실행 전에 보통 512M 정도의 memory를 미리 accept합니다.

hypervisor는 accepted page를 일방적으로 `blocked` state로 옮길 수 있습니다. 다만 이렇게 하면 page access가 `#VE`를 일으키지 않고 `TD Exit`을 발생시키며, hypervisor가 그 exception을 처리해야 합니다.

Linux #VE handler와 MMIO

332-375

page fault나 `#GP`와 마찬가지로 `#VE` exception은 처리할 수도 있고 fatal일 수도 있습니다. 일반적으로 처리하지 못한 userspace `#VE`는 `SIGSEGV`가 되고, 처리하지 못한 kernel `#VE`는 oops가 됩니다.

x86에서 nested exception 처리는 매우 까다롭습니다. `#VE` 처리 도중 NMI가 끼어들어 또 다른 `#VE`를 일으킬 수 있습니다. TDX `#VE` architecture는 이 상황을 조금 덜 위험하게 만드는 기능을 포함합니다.

`#VE`를 처리하는 동안 TDX module은 NMI를 포함한 모든 interrupt를 block합니다. 이 block은 guest가 `TDG.VP.VEINFO.GET` `TDCALL`을 실행할 때까지 유지되며, guest가 interrupt 또는 새 `#VE`를 언제 전달받을지 제어할 수 있게 합니다.

block이 유지되는 동안 guest kernel은 앞서 설명한 `#VE` 유발 가능 동작을 피해야 합니다. 이 상태에서 발생하는 모든 `#VE`는 복구할 수 없는 double fault(`#DF`)로 승격됩니다.

non-TDX VM의 MMIO는 보통 access 시 `VMEXIT`을 일으키는 mapping을 guest에 제공하고 hypervisor가 access를 emulate하는 방식으로 구현합니다. 그러나 `VMEXIT`은 register state를 host에 노출하므로 host를 신뢰하지 않는 TDX guest에서는 이 방식을 쓸 수 없습니다.

TDX의 MMIO region은 일반적으로 guest 안에서 `#VE` exception을 발생시킵니다. guest `#VE` handler가 guest 내부에서 MMIO instruction을 emulate한 뒤 guest state를 host에 노출하는 대신 통제된 `TDCALL`로 변환해 host에 보냅니다.

x86의 MMIO address는 특별한 physical address일 뿐이므로 이론적으로 memory에 access하는 어떤 instruction으로도 접근할 수 있습니다. 하지만 kernel instruction decoder는 제한적이며 `io.h` macro가 생성하는 형태의 instruction만 decode하도록 설계되었습니다.

structure overlay 같은 다른 방식으로 MMIO에 access하면 oops가 발생할 수 있습니다.

shared/private memory 변환

376-397

TDX guest memory는 boot 시 모두 private 상태로 시작하므로 hypervisor가 access할 수 없습니다. 그러나 device driver 같은 일부 kernel user는 hypervisor와 data를 공유해야 하므로 memory를 shared와 private 사이에서 변환해야 합니다. 기존 memory-encryption helper로 이를 수행할 수 있습니다.

  • `set_memory_decrypted()`는 page range를 shared로 변환합니다.
  • `set_memory_encrypted()`는 memory를 다시 private으로 변환합니다.

shared memory의 주 사용자는 device driver이지만 모든 driver를 수정할 필요는 없습니다. DMA buffer와 `ioremap()`이 변환을 자동으로 수행합니다.

TDX는 대부분의 DMA allocation에 SWIOTLB를 사용하며 SWIOTLB buffer는 boot 때 shared로 변환됩니다.

coherent DMA allocation에서는 allocation 시 DMA buffer를 변환합니다. 자세한 내용은 `force_dma_unencrypted()`를 확인하십시오.

TDREPORT와 Quote attestation

398-440

attestation은 guest에 secret을 provision하기 전에 다른 entity가 TDX guest의 trustworthiness를 검증하는 데 사용합니다. 예를 들어 key server는 encrypted rootfs나 secondary drive를 mount할 encryption key를 내주기 전에 attestation으로 원하는 guest가 맞는지 확인할 수 있습니다.

TDX module은 guest boot process의 여러 단계에서 build-time measurement register(MRTD)와 runtime measurement register(RTMR)를 사용해 TDX guest state를 기록합니다. guest initial configuration과 firmware image 관련 measurement는 MRTD에 기록합니다. initial state, kernel image, firmware image, command-line option, initrd, ACPI table 등의 measurement는 RTMR에 기록합니다.

자세한 예시는 TDX Virtual Firmware design specification의 `TD Measurement` section을 참고하십시오. TDX guest runtime에는 attestation process로 이러한 measurement를 attest합니다.

attestation process는 `TDREPORT` generation과 `Quote` generation의 두 단계로 구성됩니다.

단계입력과 검증 범위결과
`TDCALL[TDG.MR.REPORT]`사용자가 제공한 64-byte `REPORTDATA`, guest build·boot measurement와 platform security versionplatform-bound MAC으로 integrity를 보호하는 fixed-size `TDREPORT_STRUCT`
Quoting Enclave(QE)local platform에서만 MAC을 검증할 수 있는 `TDREPORT`Intel SGX QE가 local 검증 후 변환한 remotely verifiable `Quote`

TDX guest는 `TDCALL[TDG.MR.REPORT]`로 TDX module에서 `TDREPORT`(`TDREPORT_STRUCT`)를 받습니다. `TDREPORT`는 TDX module이 만드는 fixed-size data structure이며 build와 boot measurement 같은 guest-specific information, platform security version, integrity 보호용 MAC을 포함합니다.

사용자가 입력한 64-byte `REPORTDATA`도 `TDREPORT`에 포함됩니다. 일반적으로 attestation service가 제공한 nonce를 넣어 `TDREPORT`를 고유하게 검증할 수 있게 합니다. 세부 내용은 Intel TDX Module specification의 `TDG.MR.REPORT Leaf` section에 있습니다.

`TDREPORT`를 받은 뒤 두 번째 단계는 Quoting Enclave(QE)에 보내 `Quote`를 생성하는 것입니다. MAC key가 platform에 bind되어 있어 `TDREPORT`는 설계상 local platform에서만 검증할 수 있습니다.

remote verification을 지원하기 위해 TDX는 Intel SGX Quoting Enclave로 `TDREPORT`를 local에서 검증하고 remotely verifiable `Quote`로 변환합니다. `TDREPORT`를 QE에 보내는 방법은 implementation-specific입니다. attestation software는 `vsock`이나 `TCP/IP` 등 사용할 수 있는 어떤 communication channel로도 QE에 `TDREPORT`를 보내고 `Quote`를 받을 수 있습니다.

참고 자료

441-446

TDX reference material은 다음 Intel 페이지에 모여 있습니다.

https://www.intel.com/content/www/us/en/developer/articles/technical/intel-trust-domain-extensions.html