01 · QUESTION
무엇을 확인할 것인가
비동기 signal이 syscall 또는 user instruction을 중단했을 때 원래 PC, stack, mask, FPU/vector state는 어디에 보관되고 어떤 검증 뒤 신뢰되는가?
exit-to-user path가 deliverable signal을 고르고 architecture setup_rt_frame()이 user stack 또는 altstack에 ABI frame을 만든다. handler return trampoline은 rt_sigreturn syscall로 들어와 frame을 copy/validate한 뒤 pt_regs와 signal mask를 복원한다.
kernel-owned pt_regs에서 user-writable signal frame으로 상태를 복사하는 순간 신뢰 경계가 바뀐다. sigreturn은 user가 수정한 frame을 읽으므로 privilege bit, canonical address, reserved field를 반드시 검증한다.
02 · CONTRACT
공통 규약과 아키텍처별 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | rt_sigframe과 extensible context record chain | record size overflow, duplicate magic, 잘못된 VL 또는 PSTATE privilege bit를 허용하면 out-of-bounds read와 privilege corruption 위험이 생긴다. | SP 16-byte alignment, frame record magic/size, FPSIMD/SVE VL, PC/LR, PSTATE와 sigmask를 확인한다. |
| x86-64 | ucontext, sigcontext와 XSAVE fpstate pointer | noncanonical RIP/RSP, 잘못된 CS/SS, forged xfeatures 또는 shadow-stack token 불일치는 sigreturn fault로 막아야 한다. | frame rsp, uc_mcontext, fpstate magic/size/xfeatures, CS/SS/RFLAGS, CET SSP와 restorer 주소를 본다. |
| RISC-V | rt_sigframe의 sigcontext와 vector state datap pointer | VLEN 기반 size overflow, vector datap pointer가 frame 밖을 가리키는 경우 또는 SSTATUS.SPP 조작을 검증하지 않으면 memory corruption과 privilege 문제가 생긴다. | EPC/SP/RA, a0-a2, sigmask, fp fcsr, vector vlenb/vl/vtype/vstart와 SSTATUS를 확인한다. |
03 · DIAGRAMS
분기, 저장 필드, 오류 경계
arm64
- 배치
- FP/SIMD 상태 저장 →
get_sigframe()(L1545–1548) - 프레임
uc_stack·sigcontext·mask,SA_SIGINFO이면 siginfo(L1553–1559)- 진입
err == 0일 때만setup_return(); PC/SP/x0 등 세부 설정은 signal.c:1475–1506
x86-64
- 조건
SA_RESTORER와 사용자 프레임 접근 확인(L171–179)- 프레임
- ucontext·sigcontext·mask를 쓰고, 조건부 siginfo와 shadow stack을 준비(L181–199)
- 진입
- 이후 RDI=signal, RSI=&info, RDX=&uc, RIP=handler, RSP=frame(L201–212)
RISC-V native
- 배치
- frame 크기 계산 → SP 차감·16바이트 정렬 →
access_ok()(L307–325) - 프레임
- siginfo·ucontext·sigcontext·mask 쓰기 결과를
err에 모음(L327–336) - 진입
- MMU별 RA를 정한 후 EPC/SP/a0–a2 설정(L339–369)
pt_regs와 복원용 mask를 사용자 메모리로 내보낸 뒤 처리기용 레지스터를 따로 설정합니다. 이 프레임은 사용자 코드가 변경할 수 있으므로 복원은 별도 경계입니다.uc_stack + setup_sigframe()가 저장하는 레지스터·masksiginfo는 SA_SIGINFO일 때만 복사합니다(L1556–1559). 확장 record의 실제 쓰기는 signal.c:1271–1402.pretcode + uc_mcontext + uc_sigmasksiginfo는 SA_SIGINFO 조건부입니다(L188–195). FP 저장영역 포인터는 unsafe_put_sigcontext()에 전달됩니다(L189).uc_stack + uc_mcontext + uc_sigmask이 구현은 siginfo를 항상 복사합니다(L327–334). 선택적 FP/vector 저장은 signal.c:268–289.rt_sigreturn 경로입니다.get_sigframe() 실패(L1547)→setup_rt_frame()이 1을 반환(L1548)→handler용 setup_return() 호출 전err 누적(L1553–1559)→err == 0이 아니면 진입 준비 생략(L1561–1562)→기존 접근 상태 복원(L1569–1574)-EFAULT→EPC/SP/a0–a2 설정 전(L365–369)04 · SOURCE
Linux 6.18.37 원본 코드와 주요 구문 해설
소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 원문 전체 발췌를 보존하고, 개별 해설은 근거가 있는 구문에만 붙였습니다.
arm64 · Linux 6.18.37
rt_sigframe과 extensible context record chain
sigcontext 뒤 FPSIMD, ESR, SVE, ZA, ZT 같은 _aarch64_ctx record를 magic/size로 연결한다. setup_rt_frame()은 VL에 따라 frame 크기를 계산하고 handler x0-x2와 PC/LR/SP를 설정한다.
원본 코드: arch/arm64/kernel/signal.c:1529-1609
1529 task_smstop_sm(current);
1530 current->thread.svcr &= ~SVCR_ZA_MASK;
1531 write_sysreg_s(0, SYS_TPIDR2_EL0);
1532 }
1533
1534 return 0;
1535}
1536
1537static int setup_rt_frame(int usig, struct ksignal *ksig, sigset_t *set,
1538 struct pt_regs *regs)
1539{
1540 struct rt_sigframe_user_layout user;
1541 struct rt_sigframe __user *frame;
1542 struct user_access_state ua_state = {};
1543 int err = 0;
1544
1545 fpsimd_save_and_flush_current_state();
1546
1547 if (get_sigframe(&user, ksig, regs))
1548 return 1;
1549
1550 save_reset_user_access_state(&ua_state);
1551 frame = user.sigframe;
1552
1553 __put_user_error(0, &frame->uc.uc_flags, err);
1554 __put_user_error(NULL, &frame->uc.uc_link, err);
1555
1556 err |= __save_altstack(&frame->uc.uc_stack, regs->sp);
1557 err |= setup_sigframe(&user, regs, set, &ua_state);
1558 if (ksig->ka.sa.sa_flags & SA_SIGINFO)
1559 err |= copy_siginfo_to_user(&frame->info, &ksig->info);
1560
1561 if (err == 0)
1562 err = setup_return(regs, ksig, &user, usig);
1563
1564 /*
1565 * We must not fail if setup_return() succeeded - see comment at the
1566 * beginning of setup_return().
1567 */
1568
1569 if (err == 0)
1570 set_handler_user_access_state();
1571 else
1572 restore_user_access_state(&ua_state);
1573
1574 return err;
1575}
1576
1577static void setup_restart_syscall(struct pt_regs *regs)
1578{
1579 if (is_compat_task())
1580 compat_setup_restart_syscall(regs);
1581 else
1582 regs->regs[8] = __NR_restart_syscall;
1583}
1584
1585/*
1586 * OK, we're invoking a handler
1587 */
1588static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
1589{
1590 sigset_t *oldset = sigmask_to_save();
1591 int usig = ksig->sig;
1592 int ret;
1593
1594 rseq_signal_deliver(ksig, regs);
1595
1596 /*
1597 * Set up the stack frame
1598 */
1599 if (is_compat_task()) {
1600 if (ksig->ka.sa.sa_flags & SA_SIGINFO)
1601 ret = compat_setup_rt_frame(usig, ksig, oldset, regs);
1602 else
1603 ret = compat_setup_frame(usig, ksig, oldset, regs);
1604 } else {
1605 ret = setup_rt_frame(usig, ksig, oldset, regs);
1606 }
1607
1608 /*
1609 * Check that the resulting registers are actually sane.주요 구문 해설
원본 81줄은 그대로 표시하며, 검토한 45개 구문에 설명을 붙였습니다.
task_smstop_sm(current);저장된 태스크 상태에 SMSTOP SM의 효과를 적용하여 신호 처리기가 SME streaming 모드 밖에서 시작하게 합니다. streaming 모드였다면 벡터 상태 등을 규정된 값으로 바꾸고 SM 비트를 지웁니다.
current->thread.svcr &= ~SVCR_ZA_MASK;저장된 SVCR에서 ZA 활성 비트를 지웁니다. 중단된 코드가 ZA를 사용했어도 신호 처리기는 ZA가 비활성인 상태로 시작해야 합니다.
write_sysreg_s(0, SYS_TPIDR2_EL0);TPIDR2_EL0를 0으로 만들어 신호 처리기가 중단된 코드의 SME 지연 저장 상태 포인터를 그대로 사용하지 않게 합니다. 앞에서 원래 상태를 신호 프레임에 보관한 뒤 처리기 진입 상태를 준비하는 부분입니다.
return 0;신호 처리기 진입용 레지스터와 CPU 기능 상태 준비를 성공했음을 반환합니다.
static int setup_rt_frame(int usig, struct ksignal *ksig, sigset_t *set,신호 번호 usig, 처리기 설정 ksig와 복원할 신호 마스크 set으로 AArch64 신호 프레임을 만듭니다.
struct pt_regs *regs)중단된 사용자 레지스터를 regs로 받습니다. 원래 상태는 프레임에 저장하고 regs 자체는 처리기 진입용으로 바꿉니다.
struct rt_sigframe_user_layout user;기본 신호 프레임과 확장 문맥이 사용자 스택의 어느 위치에 놓일지 담는 배치 정보입니다.
struct rt_sigframe __user *frame;사용자 스택의 기본 rt_sigframe을 가리킬 포인터입니다. __user 표식은 일반 커널 포인터처럼 직접 접근해서는 안 됨을 나타냅니다.
struct user_access_state ua_state = {};프레임을 쓰기 위해 임시로 바꾸는 사용자 접근 권한의 원래 값을 보관합니다. 빈 초기값에서 지원되는 POE 상태를 채웁니다.
int err = 0;여러 사용자 메모리 쓰기의 오류를 모을 변수입니다. 모두 성공하면 0이 유지되어 처리기 진입 준비를 계속합니다.
fpsimd_save_and_flush_current_state();현재 태스크의 FP·SIMD 등 상태를 메모리에 저장하고 CPU에 남아 있는 상태와의 연결을 정리합니다. 이후 신호 프레임이 최신 값을 담을 수 있게 하는 작업이며, 일반 데이터 캐시를 비운다는 뜻은 아닙니다.
if (get_sigframe(&user, ksig, regs))스택 위치와 확장 상태 크기를 반영한 프레임 배치를 구합니다. 실패하면 불완전한 주소로 프레임을 쓰기 전에 중단합니다.
return 1;신호 프레임 배치를 구하지 못했으므로 비영(1) 실패를 반환합니다. 호출자가 신호 설정 실패로 처리합니다.
save_reset_user_access_state(&ua_state);POE를 지원하면 기존 POR_EL0 접근 권한을 ua_state에 저장하고 신호 프레임을 쓸 수 있도록 임시로 권한을 엽니다. 사용자 보호 키 설정이 프레임 작성 자체를 가로막지 않게 준비합니다.
frame = user.sigframe;계산한 배치 중 기본 신호 프레임 주소를 꺼냅니다. 다음 줄부터 이 주소에 ucontext와 siginfo를 기록합니다.
__put_user_error(0, &frame->uc.uc_flags, err);사용자 신호 프레임의 uc_flags를 0으로 초기화합니다. __put_user_error는 쓰기 오류를 err에 누적하므로, 이 필드를 쓰지 못했으면 뒤에서 정상 처리기 진입을 중단할 수 있습니다.
__put_user_error(NULL, &frame->uc.uc_link, err);신호 프레임의 uc_link를 NULL로 기록해 연결된 다음 사용자 문맥이 없음을 나타냅니다. 사용자 메모리 쓰기 실패는 앞선 오류와 함께 err에 남깁니다.
err |= __save_altstack(&frame->uc.uc_stack, regs->sp);중단 당시 SP를 기준으로 대체 신호 스택의 설정과 사용 상태를 uc_stack에 저장합니다. 신호 처리기가 끝난 뒤 sigreturn에서 원래 스택 상태를 복구할 자료입니다.
err |= setup_sigframe(&user, regs, set, &ua_state);중단된 레지스터, 신호 마스크와 확장 상태를 계산해 둔 user 프레임 배치에 저장합니다. ua_state에는 임시 변경 전 사용자 접근 권한이 담겨 있으므로 그 상태도 복귀 때 되살릴 수 있게 전달합니다.
if (ksig->ka.sa.sa_flags & SA_SIGINFO)처리기가 세 인자 형식의 SA_SIGINFO를 요청했는지 검사합니다. 요청했을 때만 발생 원인 등의 siginfo를 프레임에 복사합니다.
err |= copy_siginfo_to_user(&frame->info, &ksig->info);SA_SIGINFO 처리기에 전달할 신호 번호·발생 원인 등의 siginfo를 사용자 프레임에 복사합니다. 접근 오류가 있으면 err에 반영하여 불완전한 프레임으로 처리기를 실행하지 않게 합니다.
if (err == 0)프레임의 모든 사용자 메모리 쓰기가 성공했는지 확인합니다. 실패가 누적됐다면 PC·SP를 처리기용으로 바꾸지 않습니다.
err = setup_return(regs, ksig, &user, usig);프레임 작성에 오류가 없을 때 PC·SP·인자·복귀 주소 등 regs를 신호 처리기 진입 상태로 바꿉니다. usig는 처리기에 전달할 신호 번호이며, 설정에 실패하면 err를 통해 알립니다.
if (err == 0)프레임 작성과 처리기 진입 준비까지 성공했는지 확인합니다. 성공이면 처리기용 접근 권한을 적용하고, 실패면 원래 권한으로 복구합니다.
set_handler_user_access_state();프레임과 처리기 진입 준비에 성공했으므로 POR_EL0를 신호 처리기용 기본 접근 권한으로 설정합니다. 이 시점 이후에는 임시 권한으로 사용자 메모리를 더 접근하지 않습니다.
else신호 설정이 실패한 경로입니다. 임시로 완화했던 사용자 접근 권한을 원래 값으로 되돌립니다.
restore_user_access_state(&ua_state);신호 프레임을 만들지 못한 경우 ua_state에 보관한 기존 사용자 접근 권한을 되돌립니다. 성공 경로와 달리 처리기용 권한으로 바꾸지 않고, 중단된 코드의 상태를 유지합니다.
return err;신호 프레임 준비의 성공 0 또는 누적된 실패값을 반환합니다. 이 결과로 상위 신호 코드가 실제 전달 가능 여부를 판단합니다.
static void setup_restart_syscall(struct pt_regs *regs)재시작이 필요한 시스템 콜을 restart_syscall로 다시 호출하도록 번호 레지스터를 준비합니다. 사용자 ABI에 따라 번호를 놓는 위치가 다릅니다.
if (is_compat_task())현재 태스크가 32비트 호환 ABI인지 검사합니다. AArch32이면 호환 번호와 레지스터 규약을 사용합니다.
compat_setup_restart_syscall(regs);32비트 호환 태스크에 맞는 레지스터에 restart_syscall 번호를 준비합니다. 다시 시작해야 하는 시스템 콜이 AArch64의 x8 규약이 아니라 호환 ABI의 시스템 콜 규약을 따르도록 합니다.
else64비트 AArch64 태스크의 경로입니다. 시스템 콜 번호를 x8에 넣는 규약을 따릅니다.
regs->regs[8] = __NR_restart_syscall;복귀 후 재실행할 시스템 콜 번호를 x8의 저장값에 넣습니다. 원래 번호 대신 restart_syscall이 재시작용 상태를 처리하게 합니다.
static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)선택된 신호를 사용자 처리기에 전달할 준비를 합니다. rseq 처리, ABI별 프레임 작성, 레지스터 유효성 확인과 단일 단계 상태 반영을 연결합니다.
sigset_t *oldset = sigmask_to_save();신호 복귀 때 복원할 차단 신호 집합을 고릅니다. 보통 current->blocked를 쓰지만 임시 신호 마스크를 되돌려야 하는 상황이면 saved_sigmask를 선택하므로, 처리 시점의 마스크와 항상 같지는 않습니다.
int usig = ksig->sig;전달할 Linux 신호 번호를 usig에 보관합니다. ABI별 프레임 작성과 처리기 첫 인자에 같은 번호를 사용합니다.
int ret;사용자 시그널 프레임 구성의 성공·실패를 담을 ret를 선언합니다. 뒤에서 프레임 생성 결과와 사용자 레지스터 유효성 검사 결과를 합쳐 signal_setup_done에 전달합니다.
rseq_signal_deliver(ksig, regs);등록된 rseq 실행 구간이 신호로 중단됐음을 처리합니다. 필요한 경우 복귀 PC를 해당 구간의 중단 처리 지점으로 옮긴 뒤 그 레지스터 상태로 신호 프레임을 구성하게 합니다.
if (is_compat_task()) {AArch32 호환 태스크이면 32비트 형식으로 프레임을 만듭니다. 그렇지 않으면 아래 AArch64 rt 프레임 경로를 사용합니다.
if (ksig->ka.sa.sa_flags & SA_SIGINFO)호환 태스크의 처리기가 SA_SIGINFO를 요구하는지 검사합니다. 요구하면 확장 rt 프레임을, 아니면 전통적인 호환 프레임을 선택합니다.
ret = compat_setup_rt_frame(usig, ksig, oldset, regs);32비트 호환 태스크가 SA_SIGINFO를 요청했으므로 siginfo와 ucontext가 포함된 실시간 시그널 프레임을 구성합니다. 기존 시그널 마스크·레지스터를 저장하고 핸들러 인자를 준비한 결과를 ret에 받습니다.
else호환 태스크가 SA_SIGINFO를 요청하지 않은 경우입니다. 옛 arm32 처리기가 기대하는 기본 신호 프레임을 만듭니다.
ret = compat_setup_frame(usig, ksig, oldset, regs);SA_SIGINFO를 쓰지 않는 32비트 호환 태스크에 기존 형식의 시그널 프레임을 만듭니다. 저장 문맥과 복귀 코드를 준비하고 핸들러로 들어갈 레지스터를 설정한 성공·실패를 기록합니다.
} else {native AArch64 태스크의 경로입니다. SA_SIGINFO 여부와 무관하게 AArch64 rt 프레임 형식을 사용합니다.
ret = setup_rt_frame(usig, ksig, oldset, regs);64비트 태스크의 사용자 스택에 실시간 시그널 프레임을 구성합니다. 원래 문맥과 시그널 마스크를 저장하고 핸들러로 진입할 사용자 레지스터를 설정하며, 오류 여부를 ret에 기록합니다.
x86-64 · Linux 6.18.37
ucontext, sigcontext와 XSAVE fpstate pointer
x64 rt frame은 pretcode, ucontext, siginfo와 fpstate area를 user stack에 둔다. x64_setup_rt_frame()은 pt_regs를 sigcontext에 복사하고 handler RDI/RSI/RDX와 RIP/RSP를 설정한다.
원본 코드: arch/x86/kernel/signal_64.c:156-228
156 flags = UC_SIGCONTEXT_SS;
157
158 if (likely(user_64bit_mode(regs)))
159 flags |= UC_STRICT_RESTORE_SS;
160
161 return flags;
162}
163
164int x64_setup_rt_frame(struct ksignal *ksig, struct pt_regs *regs)
165{
166 sigset_t *set = sigmask_to_save();
167 struct rt_sigframe __user *frame;
168 void __user *fp = NULL;
169 unsigned long uc_flags;
170
171 /* x86-64 should always use SA_RESTORER. */
172 if (!(ksig->ka.sa.sa_flags & SA_RESTORER))
173 return -EFAULT;
174
175 frame = get_sigframe(ksig, regs, sizeof(struct rt_sigframe), &fp);
176 uc_flags = frame_uc_flags(regs);
177
178 if (!user_access_begin(frame, sizeof(*frame)))
179 return -EFAULT;
180
181 /* Create the ucontext. */
182 unsafe_put_user(uc_flags, &frame->uc.uc_flags, Efault);
183 unsafe_put_user(0, &frame->uc.uc_link, Efault);
184 unsafe_save_altstack(&frame->uc.uc_stack, regs->sp, Efault);
185
186 /* Set up to return from userspace. If provided, use a stub
187 already in userspace. */
188 unsafe_put_user(ksig->ka.sa.sa_restorer, &frame->pretcode, Efault);
189 unsafe_put_sigcontext(&frame->uc.uc_mcontext, fp, regs, set, Efault);
190 unsafe_put_sigmask(set, frame, Efault);
191 user_access_end();
192
193 if (ksig->ka.sa.sa_flags & SA_SIGINFO) {
194 if (copy_siginfo_to_user(&frame->info, &ksig->info))
195 return -EFAULT;
196 }
197
198 if (setup_signal_shadow_stack(ksig))
199 return -EFAULT;
200
201 /* Set up registers for signal handler */
202 regs->di = ksig->sig;
203 /* In case the signal handler was declared without prototypes */
204 regs->ax = 0;
205
206 /* This also works for non SA_SIGINFO handlers because they expect the
207 next argument after the signal number on the stack. */
208 regs->si = (unsigned long)&frame->info;
209 regs->dx = (unsigned long)&frame->uc;
210 regs->ip = (unsigned long) ksig->ka.sa.sa_handler;
211
212 regs->sp = (unsigned long)frame;
213
214 /*
215 * Set up the CS and SS registers to run signal handlers in
216 * 64-bit mode, even if the handler happens to be interrupting
217 * 32-bit or 16-bit code.
218 *
219 * SS is subtle. In 64-bit mode, we don't need any particular
220 * SS descriptor, but we do need SS to be valid. It's possible
221 * that the old SS is entirely bogus -- this can happen if the
222 * signal we're trying to deliver is #GP or #SS caused by a bad
223 * SS value. We also have a compatibility issue here: DOSEMU
224 * relies on the contents of the SS register indicating the
225 * SS value at the time of the signal, even though that code in
226 * DOSEMU predates sigreturn's ability to restore SS. (DOSEMU
227 * avoids relying on sigreturn to restore SS; instead it uses
228 * a trampoline.) So we do our best: if the old SS was valid,주요 구문 해설
원본 73줄은 그대로 표시하며, 검토한 35개 구문에 설명을 붙였습니다.
flags = UC_SIGCONTEXT_SS;XSAVE 확장 상태를 제공하지 않는 경우에도 SS 레지스터 문맥이 포함됨을 UC_SIGCONTEXT_SS로 표시합니다.
if (likely(user_64bit_mode(regs)))신호가 실제 64비트 사용자 코드 실행 중에 발생했는지 검사합니다. 그 경우 복귀 때 SS를 엄격하게 복원하도록 추가 표시합니다.
flags |= UC_STRICT_RESTORE_SS;64비트 문맥에서 저장한 SS의 엄격한 복원을 요구합니다. 오래된 32·16비트 문맥과의 호환 복원 규칙을 적용하지 않게 합니다.
return flags;확장 FP 상태와 SS 복원 규칙을 나타내는 비트들을 반환합니다. 이 값이 사용자 ucontext의 uc_flags에 기록됩니다.
int x64_setup_rt_frame(struct ksignal *ksig, struct pt_regs *regs)x86-64 신호 처리기용 rt 프레임과 진입 레지스터를 준비합니다. 원래 사용자 상태를 저장하고 처리기에서 sigreturn으로 돌아올 수 있게 합니다.
sigset_t *set = sigmask_to_save();신호 처리기에서 복귀한 뒤 적용할 신호 마스크를 선택합니다. 임시로 변경한 마스크의 복원이 예약되어 있으면 saved_sigmask를, 아니면 현재 blocked 집합을 프레임에 저장합니다.
struct rt_sigframe __user *frame;사용자 스택에 만들 rt_sigframe의 주소를 담습니다. get_sigframe이 정한 위치를 이후 접근 검사와 쓰기에 사용합니다.
void __user *fp = NULL;프레임에 연계되는 사용자 FP 상태 저장 영역의 주소입니다. 처음 NULL로 두고 get_sigframe이 저장 위치를 정하면 갱신합니다.
unsigned long uc_flags;프레임이 담는 확장 상태와 SS 복원 규칙을 표시할 비트 묶음입니다. frame_uc_flags가 계산하여 uc_flags 필드에 기록합니다.
if (!(ksig->ka.sa.sa_flags & SA_RESTORER))처리기가 복귀용 sa_restorer를 제공했는지 검사합니다. x86-64 신호 ABI는 이 사용자 복귀 코드가 있어야 rt_sigreturn을 요청할 수 있습니다.
return -EFAULT;필수 복귀 코드가 없으므로 신호 프레임 설정을 -EFAULT로 실패 처리합니다.
frame = get_sigframe(ksig, regs, sizeof(struct rt_sigframe), &fp);red-zone, altstack, fpstate 크기를 반영해 새 user RSP를 계산한다.
uc_flags = frame_uc_flags(regs);이 프레임에 확장 FP 상태와 SS 복구 정보를 어떻게 해석할지 나타내는 uc_flags를 만듭니다. XSAVE 지원 여부와 중단된 코드가 64비트 모드인지에 따라 UC_FP_XSTATE·UC_STRICT_RESTORE_SS 등을 정합니다.
if (!user_access_begin(frame, sizeof(*frame)))프레임 전체가 허용된 사용자 주소 범위인지 검사하고 쓰기 구간을 엽니다. 실제 페이지 쓰기 오류는 이어지는 unsafe 접근의 Efault 경로에서도 처리합니다.
return -EFAULT;프레임 주소 범위로 사용자 접근을 시작할 수 없으므로 -EFAULT를 반환합니다.
unsafe_put_user(uc_flags, &frame->uc.uc_flags, Efault);계산한 uc_flags를 사용자 프레임에 기록합니다. 앞의 user_access_begin으로 연 접근 구간에서 수행하며, 오류가 나면 Efault로 이동해 프레임 작성 실패를 처리합니다.
unsafe_put_user(0, &frame->uc.uc_link, Efault);uc_link에 0을 써 연결된 다음 사용자 문맥이 없음을 표시합니다. 쓰기 도중 사용자 페이지 접근이 실패하면 Efault 경로로 빠집니다.
unsafe_save_altstack(&frame->uc.uc_stack, regs->sp, Efault);중단 당시 SP를 바탕으로 대체 신호 스택의 설정과 상태를 uc_stack에 기록합니다. sigreturn이 원래 신호 스택 상태를 복구할 수 있도록 하며, 기록 실패는 Efault로 처리합니다.
already in userspace. */사용자 공간에 이미 있는 복귀용 stub을 사용한다는 앞줄 주석의 끝입니다. 다음 줄에서 sa_restorer를 프레임의 반환 주소로 기록합니다.
unsafe_put_user(ksig->ka.sa.sa_restorer, &frame->pretcode, Efault);사용자가 등록한 sa_restorer 주소를 프레임의 복귀 주소에 넣습니다. 신호 처리기가 반환하면 이 사용자 코드가 rt_sigreturn을 요청하게 하며, 커널이 여기서 restorer를 직접 호출하는 것은 아닙니다.
unsafe_put_sigcontext(&frame->uc.uc_mcontext, fp, regs, set, Efault);현재 integer register와 trap metadata를 user-visible sigcontext로 복사한다.
unsafe_put_sigmask(set, frame, Efault);복귀 때 되살릴 신호 차단 집합 set을 프레임의 uc_sigmask에 기록합니다. 64비트 신호 집합을 사용자 메모리에 쓰다가 오류가 나면 Efault로 이동합니다.
user_access_end();신호 프레임 필드들을 쓰기 위해 열었던 사용자 메모리 접근 구간을 닫습니다. 이후의 siginfo 복사와 shadow stack 준비가 이 unsafe 접근 구간 밖에서 진행되도록 경계를 정합니다.
if (ksig->ka.sa.sa_flags & SA_SIGINFO) {SA_SIGINFO 처리기일 때만 siginfo 내용을 복사합니다. 번호 하나만 받는 처리기에는 이 추가 자료의 전달을 요구하지 않습니다.
if (copy_siginfo_to_user(&frame->info, &ksig->info))사용자 프레임에 신호 발생 정보를 복사하고 실패 여부를 검사합니다. 쓰기 실패면 처리기로 진입하지 않습니다.
return -EFAULT;siginfo 복사를 완료하지 못했으므로 -EFAULT로 신호 설정 실패를 반환합니다.
if (setup_signal_shadow_stack(ksig))사용 중인 사용자 shadow stack에도 신호 호출과 복귀에 필요한 상태를 준비합니다. 실패하면 일반 스택만 준비된 채 처리기를 실행하지 않습니다.
return -EFAULT;shadow stack 준비가 실패했으므로 신호 전달 설정을 -EFAULT로 중단합니다.
regs->di = ksig->sig;신호 번호를 저장된 RDI에 넣습니다. x86-64 함수 호출 규약의 첫 정수 인자로 처리기에 전달됩니다.
regs->ax = 0;프로토타입 없는 처리기 호출과의 호환성을 위해 RAX를 0으로 둡니다. 이 호출에서 인자로 전달하는 벡터 레지스터가 없다는 상태도 맞춥니다.
next argument after the signal number on the stack. */SA_SIGINFO를 쓰지 않는 처리기와도 이 인자 준비가 호환된다는 주석의 끝입니다. 아래 RSI·RDX에는 추가 정보 주소를 두지만 한 인자 처리기는 사용하지 않습니다.
regs->si = (unsigned long)&frame->info;두 번째 인자 RSI에 사용자 프레임의 siginfo 주소를 넣습니다. SA_SIGINFO 처리기가 신호 발생 원인을 읽는 포인터입니다.
regs->dx = (unsigned long)&frame->uc;세 번째 인자 RDX에 ucontext 주소를 넣습니다. 처리기는 중단된 레지스터와 신호 마스크 등의 문맥을 이 자료에서 볼 수 있습니다.
regs->ip = (unsigned long) ksig->ka.sa.sa_handler;IRET/SYSRET 이후 signal handler를 실행하도록 saved instruction pointer를 바꾼다.
regs->sp = (unsigned long)frame;사용자 SP를 새 신호 프레임 시작으로 바꿉니다. 사용자 모드 복귀 후 처리기가 이 스택과 저장된 restorer 주소로 실행됩니다.
RISC-V · Linux 6.18.37
rt_sigframe의 sigcontext와 vector state datap pointer
integer pt_regs와 FP state를 frame에 저장하고 vector 사용 task는 vstate header와 datap 영역을 정렬해 추가한다. handler a0-a2와 RA trampoline, EPC/SP를 새 값으로 바꾼다.
원본 코드: arch/riscv/kernel/signal.c:307-381
307 sp = sigsp(sp, ksig) - framesize;
308
309 /* Align the stack frame. */
310 sp &= ~0xfUL;
311
312 return (void __user *)sp;
313}
314
315static int setup_rt_frame(struct ksignal *ksig, sigset_t *set,
316 struct pt_regs *regs)
317{
318 struct rt_sigframe __user *frame;
319 long err = 0;
320 unsigned long __maybe_unused addr;
321 size_t frame_size = get_rt_frame_size(false);
322
323 frame = get_sigframe(ksig, regs, frame_size);
324 if (!access_ok(frame, frame_size))
325 return -EFAULT;
326
327 err |= copy_siginfo_to_user(&frame->info, &ksig->info);
328
329 /* Create the ucontext. */
330 err |= __put_user(0, &frame->uc.uc_flags);
331 err |= __put_user(NULL, &frame->uc.uc_link);
332 err |= __save_altstack(&frame->uc.uc_stack, regs->sp);
333 err |= setup_sigcontext(frame, regs);
334 err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));
335 if (err)
336 return -EFAULT;
337
338 /* Set up to return from userspace. */
339#ifdef CONFIG_MMU
340 regs->ra = (unsigned long)VDSO_SYMBOL(
341 current->mm->context.vdso, rt_sigreturn);
342#else
343 /*
344 * For the nommu case we don't have a VDSO. Instead we push two
345 * instructions to call the rt_sigreturn syscall onto the user stack.
346 */
347 if (copy_to_user(&frame->sigreturn_code, __user_rt_sigreturn,
348 sizeof(frame->sigreturn_code)))
349 return -EFAULT;
350
351 addr = (unsigned long)&frame->sigreturn_code;
352 /* Make sure the two instructions are pushed to icache. */
353 flush_icache_range(addr, addr + sizeof(frame->sigreturn_code));
354
355 regs->ra = addr;
356#endif /* CONFIG_MMU */
357
358 /*
359 * Set up registers for signal handler.
360 * Registers that we don't modify keep the value they had from
361 * user-space at the time we took the signal.
362 * We always pass siginfo and mcontext, regardless of SA_SIGINFO,
363 * since some things rely on this (e.g. glibc's debug/segfault.c).
364 */
365 regs->epc = (unsigned long)ksig->ka.sa.sa_handler;
366 regs->sp = (unsigned long)frame;
367 regs->a0 = ksig->sig; /* a0: signal number */
368 regs->a1 = (unsigned long)(&frame->info); /* a1: siginfo pointer */
369 regs->a2 = (unsigned long)(&frame->uc); /* a2: ucontext pointer */
370
371#if DEBUG_SIG
372 pr_info("SIG deliver (%s:%d): sig=%d pc=%p ra=%p sp=%p\n",
373 current->comm, task_pid_nr(current), ksig->sig,
374 (void *)regs->epc, (void *)regs->ra, frame);
375#endif
376
377 return 0;
378}
379
380static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
381{주요 구문 해설
원본 75줄은 그대로 표시하며, 검토한 43개 구문에 설명을 붙였습니다.
sp = sigsp(sp, ksig) - framesize;SA_ONSTACK 등에 따라 일반 또는 대체 스택의 기준 SP를 선택한 뒤 프레임 크기를 뺍니다. 낮은 주소 방향으로 자라는 스택에서 프레임 공간을 확보합니다.
sp &= ~0xfUL;SP의 하위 4비트를 지워 주소를 16바이트 경계로 내립니다. 처리기 진입이 RISC-V ABI의 스택 정렬을 지키게 합니다.
return (void __user *)sp;계산한 스택 위치를 사용자 프레임 포인터로 반환합니다. 여기서는 위치만 정하며 실제 자료 쓰기는 호출자에서 수행합니다.
static int setup_rt_frame(struct ksignal *ksig, sigset_t *set,신호 설정과 복귀용 마스크를 받아 RISC-V 사용자 스택에 rt 신호 프레임을 구성합니다.
struct pt_regs *regs)중단된 레지스터 상태를 받습니다. 프레임에 기존 값을 저장한 뒤 이 regs의 PC·SP·인자를 처리기 진입 상태로 바꿉니다.
struct rt_sigframe __user *frame;사용자 신호 프레임의 위치를 담습니다. get_sigframe으로 주소를 구한 뒤 접근 가능 범위를 검사합니다.
long err = 0;프레임 구성 중 생기는 복사 오류를 누적합니다. 모든 쓰기가 성공했을 때만 0이 유지됩니다.
unsigned long __maybe_unused addr;MMU가 없는 구성에서 스택에 만든 sigreturn 명령의 주소를 보관합니다. MMU 구성에서는 쓰지 않아도 되므로 __maybe_unused를 붙입니다.
size_t frame_size = get_rt_frame_size(false);현재 태스크에 필요한 신호 프레임 크기를 구합니다. false를 넘겨 가능한 모든 확장 상태의 최대 크기 대신 현재 활성화된 벡터 상태 등을 반영하고, ABI가 요구하는 16바이트 정렬에 맞춥니다.
frame = get_sigframe(ksig, regs, frame_size);기존 사용자 SP와 SA_ONSTACK 설정에 따라 프레임을 놓을 사용자 스택 위치를 정합니다. 힙 메모리를 할당하는 함수가 아니며, 대체 스택 경계를 넘는 등의 문제는 뒤의 접근 검사에서 실패하도록 반환합니다.
if (!access_ok(frame, frame_size))계산한 프레임 전체가 허용된 사용자 주소 범위인지 검사합니다. 실제 페이지가 쓰기 가능한지는 이어지는 복사에서도 확인됩니다.
return -EFAULT;프레임 주소 범위가 잘못되었으므로 -EFAULT를 반환하고 사용자 메모리에 기록하지 않습니다.
err |= copy_siginfo_to_user(&frame->info, &ksig->info);신호의 번호와 원인 등의 siginfo를 사용자 프레임에 복사합니다. 실패를 err에 누적하여 필요한 정보가 빠진 채 신호 처리기를 실행하지 않게 합니다.
err |= __put_user(0, &frame->uc.uc_flags);신호 프레임의 uc_flags를 0으로 초기화합니다. __put_user의 오류 반환값을 err에 합쳐 이후 프레임 작성 실패 여부를 판단합니다.
err |= __put_user(NULL, &frame->uc.uc_link);uc_link를 NULL로 설정해 연결된 다음 문맥이 없음을 나타냅니다. 사용자 페이지 쓰기가 실패하면 그 오류도 err에 남습니다.
err |= __save_altstack(&frame->uc.uc_stack, regs->sp);중단 당시 SP를 기준으로 대체 신호 스택의 설정과 현재 사용 상태를 uc_stack에 보관합니다. 신호 처리 후 원래 스택 상태로 복귀할 때 사용하는 정보입니다.
err |= setup_sigcontext(frame, regs);중단된 일반 레지스터와 지원·활성 상태에 따른 FP·벡터 문맥을 신호 프레임에 저장합니다. 신호 처리기가 끝난 뒤 sigreturn이 이 자료로 사용자 실행 상태를 복원합니다.
err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));복귀 때 되살릴 신호 차단 집합 set을 프레임의 uc_sigmask로 복사합니다. __copy_to_user는 복사하지 못한 바이트 수를 반환하므로, 0이 아니면 err가 실패를 나타냅니다.
if (err)siginfo·ucontext·레지스터·신호 마스크 저장 중 하나라도 실패했는지 검사합니다. 실패한 프레임으로 복귀 레지스터를 준비하지 않습니다.
return -EFAULT;프레임 저장을 완료하지 못했으므로 사용자 메모리 오류 -EFAULT를 반환합니다.
#ifdef CONFIG_MMUMMU가 있는 커널은 시그널 handler의 복귀 주소를 사용자 vDSO의 rt_sigreturn으로 정합니다. MMU가 없으면 vDSO가 없으므로 아래 분기에서 복귀 시스템 호출 명령 두 개를 사용자 스택에 직접 넣습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
regs->ra = (unsigned long)VDSO_SYMBOL(신호 처리기의 복귀 주소 RA를 vDSO의 rt_sigreturn 진입점으로 설정합니다. 처리기가 반환하면 이 사용자 코드가 커널에 문맥 복원을 요청합니다.
current->mm->context.vdso, rt_sigreturn);현재 mm에 매핑된 vDSO 기준주소와 rt_sigreturn 심볼 오프셋을 합쳐 실제 사용자 복귀 주소를 구합니다.
#elseMMU가 없는 구성에는 vDSO가 없어 사용자 스택의 시그널 프레임에 rt_sigreturn 요청 명령 두 개를 복사합니다. handler의 복귀 주소는 그 코드가 되며, 커널은 복귀 시스템 호출로 원래 문맥을 복원합니다.
if (copy_to_user(&frame->sigreturn_code, __user_rt_sigreturn,vDSO가 없는 MMU 미사용 구성에서는 rt_sigreturn을 요청하는 짧은 명령열을 사용자 스택의 sigreturn_code에 복사합니다.
sizeof(frame->sigreturn_code)))복사 길이를 프레임에 마련된 sigreturn_code 크기로 지정합니다. 복사되지 않은 바이트가 남으면 조건이 참이 되어 오류를 반환합니다.
return -EFAULT;스택의 sigreturn 코드를 완전히 쓰지 못했으므로 프레임 준비를 -EFAULT로 실패 처리합니다.
addr = (unsigned long)&frame->sigreturn_code;사용자 스택에 쓴 sigreturn 코드의 시작 주소를 addr에 보관합니다. 명령 캐시 동기화와 처리기의 RA 설정에 같은 주소를 사용합니다.
flush_icache_range(addr, addr + sizeof(frame->sigreturn_code));MMU가 없는 구성에서 스택에 방금 써 넣은 sigreturn 코드 구간의 명령 캐시를 동기화합니다. 처리기의 복귀 주소가 이 코드이므로 CPU가 이전 명령 바이트가 아닌 새 두 명령을 실행할 수 있어야 합니다.
regs->ra = addr;처리기의 복귀 주소를 스택에 준비한 sigreturn 코드로 바꿉니다. MMU 미사용 환경에서 vDSO를 대신하는 반환 경로입니다.
#endif /* CONFIG_MMU */339행에서 시작한 선택 구간을 마칩니다. MMU가 있는 커널은 시그널 handler의 복귀 주소를 사용자 vDSO의 rt_sigreturn으로 정합니다. MMU가 없으면 vDSO가 없으므로 아래 분기에서 복귀 시스템 호출 명령 두 개를 사용자 스택에 직접 넣습니다.
regs->epc = (unsigned long)ksig->ka.sa.sa_handler;sret 뒤 signal handler에서 시작하도록 saved EPC를 교체한다.
regs->sp = (unsigned long)frame;저장된 SP를 새 신호 프레임 위치로 바꿉니다. 처리기는 원래 문맥을 보관한 프레임 아래쪽 스택을 사용하여 실행됩니다.
regs->a0 = ksig->sig; /* a0: signal number */RISC-V 함수 호출 규약의 첫 인자 a0에 신호 번호를 넣습니다.
regs->a1 = (unsigned long)(&frame->info); /* a1: siginfo pointer */두 번째 인자 a1에 사용자 프레임의 siginfo 주소를 넣습니다. 이 구현은 SA_SIGINFO 여부와 무관하게 해당 포인터를 준비합니다.
regs->a2 = (unsigned long)(&frame->uc); /* a2: ucontext pointer */세 번째 인자 a2에 ucontext 주소를 넣습니다. 신호 처리기는 이 자료로 중단된 실행 문맥을 살펴볼 수 있습니다.
#if DEBUG_SIGDEBUG_SIG는 시그널 전달 상황을 출력할 때 쓰는 이 파일의 디버깅 조건입니다. 켜면 task·시그널 번호·PC·복귀 주소·스택을 기록하고, 꺼지면 로그만 빠지며 시그널 프레임 구성은 그대로 수행합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
pr_info("SIG deliver (%s:%d): sig=%d pc=%p ra=%p sp=%p\n",DEBUG_SIG가 켜진 빌드에서 신호 전달 상태를 로그로 남깁니다. 태스크와 신호 번호, 처리기 PC·복귀 주소·스택을 함께 출력합니다.
current->comm, task_pid_nr(current), ksig->sig,진단 로그에 현재 태스크 이름·PID와 전달 신호 번호를 넘깁니다. 같은 신호가 어떤 태스크로 갔는지 구분할 자료입니다.
(void *)regs->epc, (void *)regs->ra, frame);진단 로그에 설정을 마친 PC·RA와 프레임 주소를 넘깁니다. 포인터 출력용 형변환이며 레지스터를 변경하지 않습니다.
#endif371행에서 시작한 선택 구간을 마칩니다. DEBUG_SIG는 시그널 전달 상황을 출력할 때 쓰는 이 파일의 디버깅 조건입니다. 켜면 task·시그널 번호·PC·복귀 주소·스택을 기록하고, 꺼지면 로그만 빠지며 시그널 프레임 구성은 그대로 수행합니다.
return 0;신호 프레임과 처리기 진입 레지스터를 성공적으로 준비했음을 0으로 반환합니다.
static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)선택된 신호의 rseq 상태를 처리한 뒤 사용자 ABI에 맞는 신호 프레임을 만듭니다. 끝에서는 준비 결과와 단일 단계 추적 상태를 공통 신호 코드에 알립니다.
05 · WORKED EXAMPLE
숫자로 검산하기
가변 vector state가 signal frame에 미치는 영향
기본 frame 1KiB, vector state 4KiB, altstack 8KiB에서 signal 두 개가 중첩된다고 가정한다.
- first정렬과 header를 제외해도 첫 frame은 약 5KiB를 사용한다.
- nested두 번째 frame도 같은 state를 저장하면 누적 약 10KiB로 8KiB altstack을 넘는다.
- failureget_sigframe 또는 user write가 실패하면 kernel은 SIGSEGV 처리 경로로 가며 손상된 일부 frame을 복원에 사용하지 않는다.
- mitigation실제 최대 VL/XSAVE feature를 기준으로 sigaltstack 크기를 산정하고 guard page를 둔다.
결론MINSIGSTKSZ 같은 고정 상수만으로 scalable state의 worst case를 보장할 수 없는 이유를 frame byte 수로 확인할 수 있다.
06 · DEEP DIVE
주요 동작 상세 분석
공통 커널 코드와 아키텍처별 함수의 역할 구분
exit-to-user path가 deliverable signal을 고르고 architecture setup_rt_frame()이 user stack 또는 altstack에 ABI frame을 만든다. handler return trampoline은 rt_sigreturn syscall로 들어와 frame을 copy/validate한 뒤 pt_regs와 signal mask를 복원한다.
kernel-owned pt_regs에서 user-writable signal frame으로 상태를 복사하는 순간 신뢰 경계가 바뀐다. sigreturn은 user가 수정한 frame을 읽으므로 privilege bit, canonical address, reserved field를 반드시 검증한다.
arm64: rt_sigframe과 extensible context record chain
sigcontext 뒤 FPSIMD, ESR, SVE, ZA, ZT 같은 _aarch64_ctx record를 magic/size로 연결한다. setup_rt_frame()은 VL에 따라 frame 크기를 계산하고 handler x0-x2와 PC/LR/SP를 설정한다.
user frame write를 모두 성공시킨 뒤 pt_regs를 handler context로 바꿔야 하며 MTE tag와 altstack alignment도 지켜야 한다. 디버깅할 때는 SP 16-byte alignment, frame record magic/size, FPSIMD/SVE VL, PC/LR, PSTATE와 sigmask를 확인한다.
x86-64: ucontext, sigcontext와 XSAVE fpstate pointer
x64 rt frame은 pretcode, ucontext, siginfo와 fpstate area를 user stack에 둔다. x64_setup_rt_frame()은 pt_regs를 sigcontext에 복사하고 handler RDI/RSI/RDX와 RIP/RSP를 설정한다.
red-zone 회피, 16-byte stack alignment, CET shadow stack와 XSAVE frame validation을 함께 적용한다. 디버깅할 때는 frame rsp, uc_mcontext, fpstate magic/size/xfeatures, CS/SS/RFLAGS, CET SSP와 restorer 주소를 본다.
RISC-V: rt_sigframe의 sigcontext와 vector state datap pointer
integer pt_regs와 FP state를 frame에 저장하고 vector 사용 task는 vstate header와 datap 영역을 정렬해 추가한다. handler a0-a2와 RA trampoline, EPC/SP를 새 값으로 바꾼다.
vector state size 계산과 user pointer write가 완료된 뒤 pt_regs를 publish하고 sigreturn에서 SSTATUS의 허용 bit만 복원한다. 디버깅할 때는 EPC/SP/RA, a0-a2, sigmask, fp fcsr, vector vlenb/vl/vtype/vstart와 SSTATUS를 확인한다.
객체를 사용하고 해제하는 주체와 시점을 확인한다
signal frame은 user stack memory에 있으며 nested signal마다 아래쪽으로 추가된다. altstack overflow, stack mapping 변경과 handler가 frame을 덮는 경우를 모두 오류로 처리해야 한다.
주소나 레지스터 값만 확인하면 갱신되지 않은 상태를 놓칠 수 있다. 상태를 만드는 코드, 다른 코드에 전달하는 시점, 사용하는 코드, 해제하는 시점을 함께 기록한다.
최대 지연 시간은 명령 하나의 실행 시간만으로 결정되지 않는다
fixed integer context 외에 SVE/SME, XSAVE/AMX, RISC-V vector처럼 가변 크기 state를 frame에 materialize하면 signal delivery와 sigreturn 시간이 state byte 수에 비례해 증가한다.
평균값뿐 아니라 인터럽트를 비활성화한 시간, 다른 CPU의 응답 시간, 펌웨어 호출 시간, 재시도 횟수를 따로 확인해야 최대 지연의 원인을 찾을 수 있다.
07 · FAILURE
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | record size overflow, duplicate magic, 잘못된 VL 또는 PSTATE privilege bit를 허용하면 out-of-bounds read와 privilege corruption 위험이 생긴다. | SP 16-byte alignment, frame record magic/size, FPSIMD/SVE VL, PC/LR, PSTATE와 sigmask를 확인한다. |
| x86-64 | noncanonical RIP/RSP, 잘못된 CS/SS, forged xfeatures 또는 shadow-stack token 불일치는 sigreturn fault로 막아야 한다. | frame rsp, uc_mcontext, fpstate magic/size/xfeatures, CS/SS/RFLAGS, CET SSP와 restorer 주소를 본다. |
| RISC-V | VLEN 기반 size overflow, vector datap pointer가 frame 밖을 가리키는 경우 또는 SSTATUS.SPP 조작을 검증하지 않으면 memory corruption과 privilege 문제가 생긴다. | EPC/SP/RA, a0-a2, sigmask, fp fcsr, vector vlenb/vl/vtype/vstart와 SSTATUS를 확인한다. |
08 · LAB
재현과 계측 절차
- handler에서 ucontext를 출력하고 GDB pt_regs와 비교해 signal delivery가 바꾼 PC/SP/argument를 확인한다.
- vector state를 dirty하게 만든 뒤 altstack 크기를 줄여 frame allocation 실패 경로를 재현한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 위치
- arm64arch/arm64/kernel/signal.c:1529-1609
- x86-64arch/x86/kernel/signal_64.c:156-228
- RISC-Varch/riscv/kernel/signal.c:307-381
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.