요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
===============================================
Guarded Control Stack support for AArch64 Linux
===============================================
This document outlines briefly the interface provided to userspace by Linux in
order to support use of the ARM Guarded Control Stack (GCS) feature.
This is an outline of the most important features and issues only and not
intended to be exhaustive.
1. General
-----------
* GCS is an architecture feature intended to provide greater protection
against return oriented programming (ROP) attacks and to simplify the
implementation of features that need to collect stack traces such as
profiling.
* When GCS is enabled a separate guarded control stack is maintained by the
PE which is writeable only through specific GCS operations. This
stores the call stack only, when a procedure call instruction is
performed the current PC is pushed onto the GCS and on RET the
address in the LR is verified against that on the top of the GCS.
* When active the current GCS pointer is stored in the system register
GCSPR_EL0. This is readable by userspace but can only be updated
via specific GCS instructions.
* The architecture provides instructions for switching between guarded
control stacks with checks to ensure that the new stack is a valid
target for switching.
* The functionality of GCS is similar to that provided by the x86 Shadow
Stack feature, due to sharing of userspace interfaces the ABI refers to
shadow stacks rather than GCS.
* Support for GCS is reported to userspace via HWCAP_GCS in the aux vector
AT_HWCAP entry.
* GCS is enabled per thread. While there is support for disabling GCS
at runtime this should be done with great care.
* GCS memory access faults are reported as normal memory access faults.
* GCS specific errors (those reported with EC 0x2d) will be reported as
SIGSEGV with a si_code of SEGV_CPERR (control protection error).
* GCS is supported only for AArch64.
* On systems where GCS is supported GCSPR_EL0 is always readable by EL0
regardless of the GCS configuration for the thread.
* The architecture supports enabling GCS without verifying that return values
in LR match those in the GCS, the LR will be ignored. This is not supported
by Linux.
2. Enabling and disabling Guarded Control Stacks
-------------------------------------------------
* GCS is enabled and disabled for a thread via the PR_SET_SHADOW_STACK_STATUS
prctl(), this takes a single flags argument specifying which GCS features
should be used.
* When set PR_SHADOW_STACK_ENABLE flag allocates a Guarded Control Stack
and enables GCS for the thread, enabling the functionality controlled by
GCSCRE0_EL1.{nTR, RVCHKEN, PCRSEL}.
* When set the PR_SHADOW_STACK_PUSH flag enables the functionality controlled
by GCSCRE0_EL1.PUSHMEn, allowing explicit GCS pushes.
* When set the PR_SHADOW_STACK_WRITE flag enables the functionality controlled
by GCSCRE0_EL1.STREn, allowing explicit stores to the Guarded Control Stack.
* Any unknown flags will cause PR_SET_SHADOW_STACK_STATUS to return -EINVAL.
* PR_LOCK_SHADOW_STACK_STATUS is passed a bitmask of features with the same
values as used for PR_SET_SHADOW_STACK_STATUS. Any future changes to the
status of the specified GCS mode bits will be rejected.
* PR_LOCK_SHADOW_STACK_STATUS allows any bit to be locked, this allows
userspace to prevent changes to any future features.
* There is no support for a process to remove a lock that has been set for
it.
* PR_SET_SHADOW_STACK_STATUS and PR_LOCK_SHADOW_STACK_STATUS affect only the
thread that called them, any other running threads will be unaffected.
* New threads inherit the GCS configuration of the thread that created them.
* GCS is disabled on exec().
* The current GCS configuration for a thread may be read with the
PR_GET_SHADOW_STACK_STATUS prctl(), this returns the same flags that
are passed to PR_SET_SHADOW_STACK_STATUS.
* If GCS is disabled for a thread after having previously been enabled then
the stack will remain allocated for the lifetime of the thread. At present
any attempt to reenable GCS for the thread will be rejected, this may be
revisited in future.
* It should be noted that since enabling GCS will result in GCS becoming
active immediately it is not normally possible to return from the function
that invoked the prctl() that enabled GCS. It is expected that the normal
usage will be that GCS is enabled very early in execution of a program.
3. Allocation of Guarded Control Stacks
----------------------------------------
* When GCS is enabled for a thread a new Guarded Control Stack will be
allocated for it of half the standard stack size or 2 gigabytes,
whichever is smaller.
* When a new thread is created by a thread which has GCS enabled then a
new Guarded Control Stack will be allocated for the new thread with
half the size of the standard stack.
* When a stack is allocated by enabling GCS or during thread creation then
the top 8 bytes of the stack will be initialised to 0 and GCSPR_EL0 will
be set to point to the address of this 0 value, this can be used to
detect the top of the stack.
* Additional Guarded Control Stacks can be allocated using the
map_shadow_stack() system call.
* Stacks allocated using map_shadow_stack() can optionally have an end of
stack marker and cap placed at the top of the stack. If the flag
SHADOW_STACK_SET_TOKEN is specified a cap will be placed on the stack,
if SHADOW_STACK_SET_MARKER is not specified the cap will be the top 8
bytes of the stack and if it is specified then the cap will be the next
8 bytes. While specifying just SHADOW_STACK_SET_MARKER by itself is
valid since the marker is all bits 0 it has no observable effect.
* Stacks allocated using map_shadow_stack() must have a size which is a
multiple of 8 bytes larger than 8 bytes and must be 8 bytes aligned.
* An address can be specified to map_shadow_stack(), if one is provided then
it must be aligned to a page boundary.
* When a thread is freed the Guarded Control Stack initially allocated for
that thread will be freed. Note carefully that if the stack has been
switched this may not be the stack currently in use by the thread.
4. Signal handling
--------------------
* A new signal frame record gcs_context encodes the current GCS mode and
pointer for the interrupted context on signal delivery. This will always
be present on systems that support GCS.
* The record contains a flag field which reports the current GCS configuration
for the interrupted context as PR_GET_SHADOW_STACK_STATUS would.
* The signal handler is run with the same GCS configuration as the interrupted
context.
* When GCS is enabled for the interrupted thread a signal handling specific
GCS cap token will be written to the GCS, this is an architectural GCS cap
with the token type (bits 0..11) all clear. The GCSPR_EL0 reported in the
signal frame will point to this cap token.
* The signal handler will use the same GCS as the interrupted context.
* When GCS is enabled on signal entry a frame with the address of the signal
return handler will be pushed onto the GCS, allowing return from the signal
handler via RET as normal. This will not be reported in the gcs_context in
the signal frame.
5. Signal return
-----------------
When returning from a signal handler:
* If there is a gcs_context record in the signal frame then the GCS flags
and GCSPR_EL0 will be restored from that context prior to further
validation.
* If there is no gcs_context record in the signal frame then the GCS
configuration will be unchanged.
* If GCS is enabled on return from a signal handler then GCSPR_EL0 must
point to a valid GCS signal cap record, this will be popped from the
GCS prior to signal return.
* If the GCS configuration is locked when returning from a signal then any
attempt to change the GCS configuration will be treated as an error. This
is true even if GCS was not enabled prior to signal entry.
* GCS may be disabled via signal return but any attempt to enable GCS via
signal return will be rejected.
6. ptrace extensions
---------------------
* A new regset NT_ARM_GCS is defined for use with PTRACE_GETREGSET and
PTRACE_SETREGSET.
* The GCS mode, including enable and disable, may be configured via ptrace.
If GCS is enabled via ptrace no new GCS will be allocated for the thread.
* Configuration via ptrace ignores locking of GCS mode bits.
7. ELF coredump extensions
---------------------------
* NT_ARM_GCS notes will be added to each coredump for each thread of the
dumped process. The contents will be equivalent to the data that would
have been read if a PTRACE_GETREGSET of the corresponding type were
executed for each thread when the coredump was generated.
8. /proc extensions
--------------------
* Guarded Control Stack pages will include "ss" in their VmFlags in
/proc/<pid>/smaps.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
문서 범위
1-12이 문서는 ARM Guarded Control Stack(GCS) 기능을 사용하도록 AArch64 Linux가 userspace에 제공하는 interface를 간략히 설명합니다. 가장 중요한 기능과 주의점의 개요이며 모든 세부사항을 포괄하려는 문서는 아닙니다.
GCS 기본 동작과 오류 보고
13-60GCS는 return-oriented programming(ROP) 공격에 대한 보호를 강화하고 profiling처럼 stack trace를 수집하는 기능의 구현을 단순화하기 위한 architecture 기능입니다.
GCS가 활성화되면 PE는 일반 data stack과 별도의 guarded control stack을 유지합니다. 이 stack은 전용 GCS operation으로만 쓸 수 있고 call stack만 저장합니다. Procedure call instruction을 실행하면 현재 PC를 GCS에 push하며, `RET`에서는 `LR`의 address를 GCS top 값과 비교해 검증합니다.
활성 GCS pointer는 `GCSPR_EL0` system register에 저장됩니다. Userspace가 읽을 수 있지만 전용 GCS instruction으로만 갱신할 수 있습니다. Architecture는 새 stack이 유효한 switch target인지 검사하면서 GCS 사이를 전환하는 instruction도 제공합니다.
기능은 x86 Shadow Stack과 유사하고 userspace interface를 공유하므로 ABI에서는 GCS보다 `shadow stack`이라는 용어를 사용합니다. 지원 여부는 auxiliary vector의 `AT_HWCAP` entry에 있는 `HWCAP_GCS`로 보고됩니다.
- GCS는 thread별로 활성화합니다. Runtime에 끌 수 있지만 매우 신중해야 합니다.
- GCS memory access fault는 일반 memory access fault로 보고됩니다.
- EC `0x2d`로 보고되는 GCS 전용 오류는 `SIGSEGV`, `si_code = SEGV_CPERR`인 control-protection error로 전달됩니다.
- GCS는 AArch64에서만 지원됩니다.
- GCS 지원 system의 `GCSPR_EL0`는 해당 thread의 GCS 설정과 무관하게 EL0에서 항상 읽을 수 있습니다.
- Architecture에는 `LR`과 GCS의 return value를 비교하지 않고 GCS를 활성화하는 mode가 있지만 Linux는 이를 지원하지 않습니다.
GCS 활성화·잠금·상태 조회
61-112Thread의 GCS는 `PR_SET_SHADOW_STACK_STATUS` `prctl()`로 활성화하거나 비활성화합니다. 단일 flags 인자가 사용할 GCS 기능을 지정합니다.
| Operation 또는 flag | 효과 |
|---|---|
| `PR_SHADOW_STACK_ENABLE` | GCS를 할당·활성화하고 `GCSCRE0_EL1.{nTR, RVCHKEN, PCRSEL}` 기능을 켭니다. |
| `PR_SHADOW_STACK_PUSH` | `GCSCRE0_EL1.PUSHMEn`을 켜 명시적 GCS push를 허용합니다. |
| `PR_SHADOW_STACK_WRITE` | `GCSCRE0_EL1.STREn`을 켜 GCS에 대한 명시적 store를 허용합니다. |
| 알 수 없는 flag | `PR_SET_SHADOW_STACK_STATUS`가 `-EINVAL`을 반환합니다. |
| `PR_LOCK_SHADOW_STACK_STATUS` | 같은 bitmask의 mode bit를 잠가 이후 상태 변경을 거부합니다. |
| `PR_GET_SHADOW_STACK_STATUS` | 현재 thread의 설정을 `PR_SET_SHADOW_STACK_STATUS`와 같은 flag 형식으로 반환합니다. |
`PR_LOCK_SHADOW_STACK_STATUS`는 아직 정의되지 않은 미래 기능 bit도 잠글 수 있어 userspace가 향후 기능 변경까지 막을 수 있습니다. Process가 이미 설정한 lock을 제거하는 기능은 없습니다.
`PR_SET_SHADOW_STACK_STATUS`와 `PR_LOCK_SHADOW_STACK_STATUS`는 호출 thread에만 영향을 주고 다른 실행 thread는 바뀌지 않습니다. 새 thread는 생성한 thread의 GCS 설정을 상속하지만 `exec()`에서는 GCS가 비활성화됩니다.
한번 활성화했다가 끈 GCS stack은 thread lifetime 동안 계속 할당되어 있습니다. 현재는 그 thread에서 GCS를 다시 활성화하려는 시도가 거부되지만 미래에 바뀔 수 있습니다.
GCS 활성화는 즉시 효력이 생기므로 일반적으로 이를 켠 `prctl()` 호출 함수에서 정상적으로 return할 수 없습니다. 프로그램 실행의 아주 이른 단계에서 활성화하는 사용법이 예상됩니다.
Guarded Control Stack 할당
113-150Thread에서 GCS를 활성화하면 표준 stack 크기의 절반과 2GiB 중 작은 크기로 새 GCS를 할당합니다. GCS가 활성화된 thread가 새 thread를 만들면 새 thread에는 표준 stack 절반 크기의 별도 GCS가 할당됩니다.
활성화 또는 thread 생성으로 stack을 할당할 때 top 8 byte를 `0`으로 초기화하고 `GCSPR_EL0`가 이 `0`의 address를 가리키게 합니다. 이 값으로 stack top을 감지할 수 있습니다.
추가 GCS는 `map_shadow_stack()` system call로 할당할 수 있습니다. `SHADOW_STACK_SET_TOKEN`을 지정하면 stack top에 cap을 놓습니다. `SHADOW_STACK_SET_MARKER`가 없으면 cap은 top 8 byte이고, 함께 지정하면 marker 다음 8 byte에 cap이 놓입니다. Marker만 지정하는 것도 유효하지만 marker가 all-zero라 관찰 가능한 효과는 없습니다.
`map_shadow_stack()` 크기는 8 byte보다 크면서 8 byte 배수여야 하고 8-byte aligned여야 합니다. Address를 지정한다면 page boundary에 정렬해야 합니다.
Thread가 해제되면 처음 그 thread에 할당된 GCS가 해제됩니다. Stack을 전환했다면 이것이 현재 thread가 사용 중인 stack과 다를 수 있다는 점에 주의해야 합니다.
Signal 전달 시 GCS 상태
151-176새 signal-frame record `gcs_context`는 signal 전달로 중단된 context의 현재 GCS mode와 pointer를 인코딩합니다. GCS 지원 system에서는 항상 존재하며, flag field는 `PR_GET_SHADOW_STACK_STATUS`와 같은 형식으로 중단 context의 설정을 보고합니다.
Signal handler는 중단된 context와 같은 GCS 설정과 같은 GCS를 사용합니다. 중단 thread에서 GCS가 활성화되어 있으면 signal 전용 GCS cap token을 GCS에 씁니다. 이는 token type인 bit `0..11`이 모두 clear된 architecture GCS cap이며, signal frame의 `GCSPR_EL0`는 이 cap token을 가리킵니다.
Signal 진입 시 GCS가 활성화되어 있으면 signal return handler address가 든 frame을 GCS에 push하므로 handler가 평소처럼 `RET`으로 돌아갈 수 있습니다. 이 frame은 signal frame의 `gcs_context`에는 보고되지 않습니다.
Signal return 검증
177-200Signal handler에서 돌아올 때 다음 규칙을 적용합니다.
- Signal frame에 `gcs_context`가 있으면 추가 검증 전에 그 context에서 GCS flag와 `GCSPR_EL0`를 복원합니다.
- `gcs_context`가 없으면 GCS 설정은 변경되지 않습니다.
- Return 시 GCS가 활성화되어 있다면 `GCSPR_EL0`는 유효한 GCS signal cap record를 가리켜야 하며, signal return 전에 이 record를 pop합니다.
- GCS 설정이 잠겨 있으면 signal 진입 전에 GCS가 꺼져 있었더라도 설정을 바꾸려는 모든 시도를 오류로 처리합니다.
- Signal return으로 GCS를 끌 수는 있지만 켜려는 시도는 거부됩니다.
ptrace 확장
201-212`PTRACE_GETREGSET`과 `PTRACE_SETREGSET`에서 사용할 새 regset `NT_ARM_GCS`가 정의됩니다. `ptrace`로 enable/disable을 포함한 GCS mode를 설정할 수 있지만, 이 방법으로 활성화해도 thread에 새 GCS를 할당하지 않습니다. `ptrace` 설정은 GCS mode bit의 lock을 무시합니다.
ELF coredump 확장
213-222Dump 대상 process의 각 thread마다 `NT_ARM_GCS` note가 coredump에 추가됩니다. 내용은 coredump 생성 시 각 thread에 대응하는 type으로 `PTRACE_GETREGSET`을 수행했을 때 읽을 data와 같습니다.
/proc 확장
223-227Guarded Control Stack page는 `/proc/<pid>/smaps`의 `VmFlags`에 `ss`를 포함합니다.
요약과 해설
gcs.rst:1-227GCS는 call 시 return PC를 별도 protected stack에 기록하고 `RET`에서 `LR`과 비교해 ROP 공격을 어렵게 합니다. Linux ABI는 x86과 interface를 공유하므로 shadow stack 명칭을 사용하며 thread별 상태, signal 복원과 debugger 우회를 명시적으로 규정합니다.
일반 call stack과 별도 GCS가 return address를 교차 검증합니다.
상태 제어와 관찰 경로를 역할별로 정리합니다.