이 코드는 어떤 문제를 푸나요?
사용자 함수가 커널 함수를 일반 call로 직접 호출하는 것은 아닙니다. arm64의 SVC, x86의 SYSCALL, RISC-V의 ECALL이 권한 경계를 넘긴 뒤 번호를 검사해 서비스 함수를 고릅니다. 아래는 arm64 시스템 호출 번호를 테이블에 연결하고 반환값을 기록하는 함수입니다.
읽을 범위: v6.18.37 · arch/arm64/kernel/syscall.c · invoke_syscall 38–66행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.
먼저 알아둘 개념
시스템 호출 번호
arm64는 x8, x86-64는 rax, RISC-V는 a7을 사용하는 ABI를 구분해야 합니다. C 함수 인수 번호와 같은 개념이 아닙니다.
array_index_nospec
범위 검사 이후 추측 실행이 부적절한 인덱스로 테이블을 접근하지 않도록 인덱스를 제한하는 도구입니다.
반환 레지스터
커널 C 함수가 얻은 결과를 pt_regs에 기록하면 최종 사용자 복귀 때 해당 레지스터로 반영됩니다.
처음 읽을 때
번호 5가 테이블의 어느 항목을 선택하고 결과가 어느 저장 프레임으로 돌아가는지 따라가십시오.
더 깊이 살펴볼 때
seccomp·ptrace·restart는 이 함수 전후의 공통 경로와 함께 읽고, 스택 무작위화의 적용 시점도 구분하십시오.
그림으로 보는 변화

1. 번호 검사
호출 번호가 테이블 범위 안인지 검사합니다.
화살표는 정상 서비스와 미구현 처리로 갈리는 분기입니다.
2. 서비스 실행
안전하게 제한한 인덱스로 함수 포인터를 얻어 호출합니다.
여기서는 커널 내부의 C 호출이 수행됩니다.
3. 결과와 다음 진입 준비
pt_regs에 반환값을 남기고 다음 스택 offset을 고릅니다.
즉시 사용자 모드로 점프하는 함수는 아닙니다.
invoke_syscall를 한 줄씩 읽기
줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.
static void invoke_syscall(struct pt_regs *regs, unsigned int scno,
unsigned int sc_nr,
const syscall_fn_t syscall_table[])
{
long ret;
add_random_kstack_offset();
if (likely(scno < sc_nr)) {
syscall_fn_t syscall_fn;
syscall_fn = syscall_table[array_index_nospec(scno, sc_nr)];
ret = __invoke_syscall(regs, syscall_fn);
} else {
ret = do_ni_syscall(regs, scno);
}
syscall_set_return_value(current, regs, 0, ret);
/*
* This value will get limited by KSTACK_OFFSET_MAX(), which is 10
* bits. The actual entropy will be further reduced by the compiler
* when applying stack alignment constraints: the AAPCS mandates a
* 16-byte aligned SP at function boundaries, which will remove the
* 4 low bits from any entropy chosen here.
*
* The resulting 6 bits of entropy is seen in SP[9:4].
*/
choose_random_kstack_offset(get_random_u16());
}static void invoke_syscall(struct pt_regs *regs, unsigned int scno,저장 레지스터 프레임과 사용자 시스템 호출 번호를 받습니다.
unsigned int sc_nr,유효한 테이블 항목 수를 인수로 받습니다. 번호가 이 범위를 넘지 않는지 검사할 기준입니다.
const syscall_fn_t syscall_table[])현재 ABI에 대응하는 시스템 호출 함수 포인터 테이블을 받습니다. 호환 ABI에는 다른 표가 사용될 수 있습니다.
long ret;서비스 함수의 결과를 보관할 long 변수를 선언합니다.
add_random_kstack_offset();이번 진입에 사용할 커널 스택 offset 무작위화를 적용합니다. 사용자 인수 버퍼의 주소를 바꾸는 작업은 아닙니다.
if (likely(scno < sc_nr)) {번호가 테이블의 항목 수 미만인지 확인합니다. likely는 정상 호출이 흔하다는 컴파일러 힌트이지 검사를 생략하는 명령이 아닙니다.
syscall_fn_t syscall_fn;선택할 함수 포인터를 보관할 변수를 선언합니다.
syscall_fn = syscall_table[array_index_nospec(scno, sc_nr)];범위가 확인된 번호에도 array_index_nospec을 적용해 추측 실행의 테이블 범위 밖 접근을 제한합니다.
ret = __invoke_syscall(regs, syscall_fn);선택한 시스템 호출 wrapper에 regs를 전달하고 결과를 받습니다.
} else {번호가 유효한 범위를 벗어난 경우 처리 경로로 갈립니다.
ret = do_ni_syscall(regs, scno);미구현 또는 호환 처리 경로를 통해 해당 번호의 결과를 얻습니다. 임의 주소를 함수로 실행하지 않습니다.
syscall_set_return_value(current, regs, 0, ret);현재 task의 사용자 복귀 레지스터 상태에 결과를 기록합니다. 실제 사용자 레지스터 복원은 뒤의 진입 복귀 코드가 맡습니다.
choose_random_kstack_offset(get_random_u16());다음 호출에서 쓸 무작위 스택 offset을 선택합니다. 주석처럼 정렬 제약 때문에 난수 모든 비트가 유효한 위치 변화로 남는 것은 아닙니다.
함께 생각해 볼 질문
범위를 벗어난 번호를 호출하면 임의 함수가 실행됩니까?
이 경로는 범위를 검사하고 미구현 처리로 보냅니다.
시스템 호출의 C 반환값은 어디로 갑니까?
syscall_set_return_value가 저장 프레임에 반영하고 복귀 코드가 사용자 레지스터로 복원합니다.
SVC immediate가 Linux syscall 번호입니까?
일반 AArch64 Linux ABI에서는 x8이 번호이며 SVC immediate와 구분합니다.
출처와 읽은 범위
Linux stable v6.18.37 · arch/arm64/kernel/syscall.c
해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고
