Process / Syscall · Linux 6.18.37 LTS · source + diagram note

execve

Linux v6.18.37 fs/exec.c에서 인자와 linux_binprm을 준비하고 binary handler로 들어가는 순서, 오류가 치명적으로 바뀌는 경계를 읽습니다.

새 task 생성과 실행 이미지 교체를 구분

execve 경로는 copy_process()처럼 새 task를 만드는 경로가 아니다. 이 페이지는 현재 태스크가 다음 프로그램 이미지를 준비하는 do_execveat_common()과, binary handler로 넘기는 bprm_execve()의 확인된 범위를 다룬다. 스레드 그룹 변화·PID 세부사항까지 “모두 그대로”라고 단정하지 않는다.

그림 1. 인자 준비부터 binary handler 진입까지
① linux_binprm 준비alloc_bprm(), argv/envp 개수, stack 한도를 검사한다.fs/exec.c L1811–1829
② 문자열 복사파일명, 환경 문자열, 인자를 순서대로 복사하고 빈 argv는 빈 argv[0]을 넣는다.L1831–1858
③ bprm_execveprepared bprm을 넘긴다. 호출 뒤 bprm을 해제한다.L1860–1866
이 그림은 do_execveat_common()의 순서다. 실제 image 교체와 handler별 load 세부사항은 이후 경로에 있다.

원문 범위와 이어 읽을 파일

아래 첫 블록은 do_execveat_common() 중 L1811–1829의 연속 발췌다. 함수 선언과 앞선 RLIMIT_NPROC 검사는 L1784–1810, 뒤의 문자열 복사와 반환은 L1831–1867을 따로 링크했다. 둘째 블록은 bprm_execve() 함수 전체 L1730–1782이다. 기존 글에 있던 네 인자 함수 선언과 파일 열기 코드는 이 버전의 bprm_execve() 본문이 아니다.

표시 발췌와 링크는 fs/exec.c의 호출 순서와 오류 경계에 한정된다. fs/binfmt_elf.c의 ELF 매핑·스택 설정, include/linux/binfmts.h의 구조체 전체, 특정 LSM 정책이나 실제 사용자 공간 진입은 이 두 코드 블록에서 확인되지 않는다.

도식 참고와 버전 경계: 문C 블로그의 Scheduler -5- (Core)와 Scheduler -16- (Load Balance 2) 중 sched_exec() 절을 참고했다. 두 글은 kernel v5.4 기준이며 이 페이지의 인자 복사, ELF 로더, mm 교체, no-return 경계를 증명하지 않는다. 그림 2는 스케줄러 측 호출과 이미지 로더 경로를 분리해서 독립적으로 다시 그렸고, 실제 호출·순서는 아래 v6.18.37 소스로 판단한다.

같은 태그의 원본 파일: fs/exec.c · fs/binfmt_elf.c · include/linux/binfmts.h. 이 페이지에서 두 코드 블록을 직접 대조한 파일은 fs/exec.c이며 뒤의 두 링크는 추가 읽기 위치다.

연속된 실제 코드 두 구간

1. do_execveat_common의 준비 부분

fs/exec.c:1811–1829 · alloc_bprm()부터 인자·환경 개수와 stack 한도 검사까지다. 함수 전체는 아니다.

	bprm = alloc_bprm(fd, filename, flags);
	if (IS_ERR(bprm)) {
		retval = PTR_ERR(bprm);
		goto out_ret;
	}

	retval = count(argv, MAX_ARG_STRINGS);
	if (retval < 0)
		goto out_free;
	bprm->argc = retval;

	retval = count(envp, MAX_ARG_STRINGS);
	if (retval < 0)
		goto out_free;
	bprm->envc = retval;

	retval = bprm_stack_limits(bprm);
	if (retval < 0)
		goto out_free;

여기서는 bprm->argc/envc를 설정하고 한도를 검사한다. 문자열 복사와 bprm_execve() 호출은 이 표시 블록 뒤 L1831–1860에 있다.

2. bprm_execve 전체

fs/exec.c:1730–1782 · credential 준비·unsafe 상태 확인·handler 진입과 성공/실패 분기를 보여 준다.

static int bprm_execve(struct linux_binprm *bprm)
{
	int retval;

	retval = prepare_bprm_creds(bprm);
	if (retval)
		return retval;

	/*
	 * Check for unsafe execution states before exec_binprm(), which
	 * will call back into begin_new_exec(), into bprm_creds_from_file(),
	 * where setuid-ness is evaluated.
	 */
	check_unsafe_exec(bprm);
	current->in_execve = 1;
	sched_mm_cid_before_execve(current);

	sched_exec();

	/* Set the unchanging part of bprm->cred */
	retval = security_bprm_creds_for_exec(bprm);
	if (retval || bprm->is_check)
		goto out;

	retval = exec_binprm(bprm);
	if (retval < 0)
		goto out;

	sched_mm_cid_after_execve(current);
	rseq_execve(current);
	/* execve succeeded */
	current->in_execve = 0;
	user_events_execve(current);
	acct_update_integrals(current);
	task_numa_free(current, false);
	return retval;

out:
	/*
	 * If past the point of no return ensure the code never
	 * returns to the userspace process.  Use an existing fatal
	 * signal if present otherwise terminate the process with
	 * SIGSEGV.
	 */
	if (bprm->point_of_no_return && !fatal_signal_pending(current))
		force_fatal_sig(SIGSEGV);

	sched_mm_cid_after_execve(current);
	rseq_set_notify_resume(current);
	current->in_execve = 0;

	return retval;
}

exec_binprm() 성공 뒤의 정리 경로와, point_of_no_return 이후 오류면 fatal signal을 요구하는 분기를 구분한다. 실제 flag를 세우는 코드는 begin_new_exec L1099–1120에 있다.

binary handler를 찾는 경로

bprm_execve()는 credentials 관련 준비와 확인 뒤 exec_binprm()을 호출한다(L1730–1756). exec_binprm()은 search_binary_handler()를 반복할 수 있고, 그 함수는 등록된 format handler의 load_binary()를 시도한다(L1651–1721). 이 근거만으로 특정 ELF 또는 script handler가 선택됐다고 말할 수는 없다.

그림 2. 스케줄러 호출과 binary handler 경로를 분리
스케줄러 쪽 호출bprm_execve()는 sched_exec()을 호출한다. CPU 배치의 내부 결정은 이 표시 코드 밖이다.L1743–1749
이미지 로더 쪽 호출credential/security 준비 뒤 exec_binprm()으로 들어간다.L1749–1756
bprm_execvecredential 준비와 보안 hook 뒤 exec_binprm() 호출.L1734–1756
exec_binprmsearch_binary_handler() 호출; interpreter가 있으면 바꿔 다시 시도한다.L1684–1721
search_binary_handler등록된 handler의 load_binary()를 호출한다. no-return 이후 또는 -ENOEXEC 외 결과면 멈춘다.L1651–1682
위 두 칸은 관찰 관점을 나눈 것이며 동시 실행 분기는 아니다. 성공 handler의 매핑/스택 구성은 해당 구현에서 확인해야 한다. exec_binprm() 안에서 handler 처리가 성공한 뒤, 함수 반환 전에는 L1723–1727의 exec trace hook이 호출된다.
위치확인되는 일아직 확인되지 않는 일근거
do_execveat_commonbprm과 인자·환경 준비 후 bprm_execve() 호출어느 format handler가 성공했는지L1811–1860
bprm_execvecredential·security 준비 후 exec_binprm() 호출loader 내부의 ELF 매핑L1730–1756
search_binary_handlerformat별 load_binary() 호출과 계속 시도할지 결정각 handler의 자체 구현L1651–1682

point-of-no-return은 mm 설치보다 앞

begin_new_exec()의 주석은 되돌릴 수 없는 경계라고 설명한다. 그 안에서 trace_sched_prepare_exec() 뒤 bprm->point_of_no_return = true를 세우고(L1093–1120), 뒤에 exec_mmap(bprm->mm)을 호출한다(L1152–1160). 기존 그림처럼 새 mm 설치 후에야 경계가 시작된다고 그리면 순서가 뒤집힌다.

그림 3. 커밋 경계 전후의 관찰과 실패
prepare trace hook옛 실행 이미지를 flush하기 전 호출된다. 주석은 이후 오류가 fatal이라고 설명한다.L1109–1115
point_of_no_return = true그 뒤 de_thread(), files 관련 처리 등이 이어진다.L1117–1136
exec_mmap더 뒤에 새 mm을 설치한다. 이 단계 뒤에야 no-return이 되는 것이 아니다.L1152–1160
성공 시exec_binprm() 내부의 handler 성공 경로에서, 반환 전에 trace_sched_process_exec() 호출.L1723–1727
이후 오류 시이미 fatal signal이 없다면 bprm_execve()의 오류 경로가 SIGSEGV를 강제한다.L1767–1781
이 그림은 fs/exec.c의 순서만 보인다. 특정 loader에서 begin_new_exec()에 도달하기 전/후 어떤 오류가 나는지는 해당 loader 소스를 읽어야 한다.
관찰 지점직접 알 수 있는 것알 수 없는 것근거
trace_sched_prepare_execbegin_new_exec()의 no-return flag 설정 직전 hook새 mm 설치 완료L1109–1120
trace_sched_process_execexec_binprm() 내부 handler 성공 뒤, 함수 반환 전 hook이전 실패의 내부 단계별 위치L1702–1727
bprm_execve 오류 반환no-return flag 뒤 오류일 때 fatal signal 처리 분기모든 loader에서 동일한 실패 원인L1767–1781

계측의 해석 범위

trace_sched_prepare_exec()와 trace_sched_process_exec()는 위치가 다른 두 hook이다. 후자는 성공 경로이므로 그 이벤트가 없다는 사실만으로 argv 복사, LSM 검사, handler 탐색, mm 교체 중 어디에서 실패했는지를 판단할 수 없다. 진입 trace나 사후 /proc/<pid>/maps도 같은 내부 단계 계측으로 취급하지 않는다.

실제로 실패 지점을 분리하려면 실행 중인 커널의 설정·사용 가능한 함수 추적 수단을 확인하고, 준비·호출 L1811–1867, handler 경계 L1730–1782의 반환/오류 지점을 대상으로 범위를 좁힌다. 이 글에는 검증하지 않은 tracefs 필터 명령을 넣지 않는다.

이어서 읽을 위치

이 페이지는 ELF를 택한 이후의 mapping, stack auxv, credential 최종 적용을 전부 설명하지 않는다. 특정 바이너리를 해석할 때는 실제 load_binary() 구현과 begin_new_exec()의 나머지 함수 본문을 고정 태그에서 함께 읽어야 한다. 개념 설명·함수 원문·그림·GIF도 별도로 볼 수 있다.