Linux v6.18.37 · 개념과 코드 읽기

System call entry: SVC, SYSCALL과 ECALL

이 코드는 어떤 문제를 푸나요?

사용자 함수가 커널 함수를 일반 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는 이 함수 전후의 공통 경로와 함께 읽고, 스택 무작위화의 적용 시점도 구분하십시오.

그림으로 보는 변화

arm64 invoke_syscall은 현재 스택 offset 적용 후 유효 번호에서 테이블 함수를, 범위 밖에서 do_ni_syscall을 호출하고 결과 저장에서 합류한 뒤 다음 offset을 선택함.
add_random_kstack_offset() 뒤 번호를 검사합니다. 범위 안은 테이블 함수를, 범위 밖은 do_ni_syscall()을 호출합니다. 두 결과를 공통으로 기록한 뒤 다음 offset을 고릅니다. 고정 원문 44–65행.
arm64 invoke_syscall 번호 검사와 합류 경로: 조건 또는 번호 검사

1. 현재 offset 적용과 번호 검사

1. 현재 offset 적용과 번호 검사

add_random_kstack_offset()(44행)을 적용하고 scno < sc_nr(46행)로 갈라집니다.

2. 두 호출 경로

유효 번호만 array_index_nospec()으로 테이블 함수를 골라 __invoke_syscall()을 실행합니다(48–49행). 범위 밖은 do_ni_syscall()(51행)로 갑니다.

3. 결과 합류와 다음 호출 준비

어느 분기에서든 ret을 syscall_set_return_value()(54행)에 전달한 뒤 choose_random_kstack_offset()(65행)으로 다음 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

해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고

맨 위로 ↑