Process / Syscall · Linux v6.6 · source + diagram note

execve

같은 task에서 인자를 준비하고 binary handler를 거쳐 실행 이미지를 교체하는 v6.6 경로.

전체 흐름

execve()는 새 task를 만들지 않는다. 같은 current가 기존 사용자 이미지에서 시스템 호출로 들어와 인자·환경 문자열을 준비하고, 성공 시 새 사용자 이미지로 돌아간다. 준비 중인 bprm->mm은 exec_mmap() 전에 현재 task의 주소 공간으로 공개되지 않는다.

그림 1. 같은 task 안의 인자 준비와 임시 주소 공간
exv-prep같은 current task가 syscall로 들어와 argv와 envp를 세고 bprm 스택 한계를 검사하여 문자열을 새 linux_binprm의 임시 인자 페이지에 복사한다. 이때 아직 새 mm이 current에 설치되지 않았다.같은 current task새 task 생성 없음user argv / envpuser pointer와 길이do_execveat_commonargc/envc · stack limitlinux_binprm임시 mm · p · argmincopy_strings()old user → 새 stack pagesbprm_execve()file · creds · handler새 task_struct를 만들지 않고 현재 실행 문맥의 이미지가 교체된다.새 bprm->mm은 exec_mmap() 전까지 current->mm으로 공개되지 않는다.

do_execveat_common() L1888–1970은 argc/envc를 세고 bprm_stack_limits()와 copy_strings()를 호출한다. 첫 대표 코드 상자는 이 두 helper의 경계를 발췌한다.

소스 코드 위치

Linux v6.6의 고정 원문

fs/exec.c가 syscall 준비, linux_binprm, binary handler 호출 및 begin_new_exec()의 경계를 보여 준다. ELF 형식의 상세 적재는 fs/binfmt_elf.c, 구조체 계약은 include/linux/binfmts.h와 함께 확인한다.

대표 코드

bprm_stack_limits() 끝과 copy_strings() 시작

원본 코드: fs/exec.c, L502–541 (두 helper의 경계)
	 *
	 * In the case of argc = 0, make sure there is space for adding a
	 * empty string (which will bump argc to 1), to ensure confused
	 * userspace programs don't start processing from argv[1], thinking
	 * argc can never be 0, to keep them from walking envp by accident.
	 * See do_execveat_common().
	 */
	ptr_size = (max(bprm->argc, 1) + bprm->envc) * sizeof(void *);
	if (limit <= ptr_size)
		return -E2BIG;
	limit -= ptr_size;

	bprm->argmin = bprm->p - limit;
	return 0;
}

/*
 * 'copy_strings()' copies argument/environment strings from the old
 * processes's memory to the new process's stack.  The call to get_user_pages()
 * ensures the destination page is created and not swapped out.
 */
static int copy_strings(int argc, struct user_arg_ptr argv,
			struct linux_binprm *bprm)
{
	struct page *kmapped_page = NULL;
	char *kaddr = NULL;
	unsigned long kpos = 0;
	int ret;

	while (argc-- > 0) {
		const char __user *str;
		int len;
		unsigned long pos;

		ret = -EFAULT;
		str = get_user_arg_ptr(argv, argc);
		if (IS_ERR(str))
			goto out;

		len = strnlen_user(str, MAX_ARG_STRLEN);

읽기 기준: L502–516은 bprm_stack_limits()의 끝이며 L518–541은 copy_strings()의 시작이다. do_execveat_common() 본문은 L1888에서 시작한다.

bprm_execve() 진입 (L1812)

원본 코드: fs/exec.c, L1807–1846 (앞 함수 끝 포함)
}

/*
 * sys_execve() executes a new program.
 */
static int bprm_execve(struct linux_binprm *bprm,
		       int fd, struct filename *filename, int flags)
{
	struct file *file;
	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);

	file = do_open_execat(fd, filename, flags);
	retval = PTR_ERR(file);
	if (IS_ERR(file))
		goto out_unmark;

	sched_exec();

	bprm->file = file;
	/*
	 * Record that a name derived from an O_CLOEXEC fd will be
	 * inaccessible after exec.  This allows the code in exec to
	 * choose to fail when the executable is not mmaped into the
	 * interpreter and an open file descriptor is not passed to
	 * the interpreter.  This makes for a better user experience
	 * than having the interpreter start and then immediately fail
	 * when it finds the executable is inaccessible.

읽기 기준: L1807은 앞 함수의 닫는 중괄호이고 L1812부터 bprm_execve()가 시작한다. 이 앞부분만으로 no-return 이후 오류를 판단할 수 없으므로 L1856–1885와 begin_new_exec()도 읽는다.

함수별 분석

do_execveat_common()은 linux_binprm을 할당해 argc/envc와 스택 한계를 확인하고 파일명·환경·인자를 임시 스택 페이지에 복사한다. bprm_execve()는 파일·자격 증명을 준비하고 exec_binprm()으로 형식 탐색을 넘긴다.

그림 2. handler 선택·재시도와 interpreter rewrite
exv-handlerbprm_execve가 exec_binprm을 부른다. search_binary_handler는 각 fmt load_binary를 시도한다. point_of_no_return 전 -ENOEXEC일 때 다음 포맷을 살피고 interpreter rewrite는 exec_binprm 루프를 돌 수 있다. handler 실행 중 begin_new_exec로 들어가며, 성공 반환 이후 호출되는 것이 아니다.bprm_execve()file·cred 준비exec_binprm()interpreter depth loopsearch_binary_handlerprepare + LSM checkfmt->load_binary()등록된 handler 시도begin_new_exec()handler 내부에서 호출다음 fmt / interpreter-ENOEXEC 또는 재해석오류 반환경계 전에는 errnohandler 내부 호출해석기 교체 또는 -ENOEXEC 재시도다른 오류는 handler 탐색 중단point_of_no_return 이후에는 -ENOEXEC라도 다른 fmt로 계속하지 않는다.

begin_new_exec()는 binary handler 내부에서 호출된다. ELF의 load_elf_binary() L1000–1003가 그 예이며, handler가 성공 반환한 뒤 호출되는 별도 단계가 아니다. search_binary_handler() L1717–1760은 -ENOEXEC일 때만 다른 형식을 탐색하고, point_of_no_return 뒤에는 재시도하지 않는다. exec_binprm() L1764–1807은 interpreter를 바꾸면 제한된 깊이로 다시 탐색한다.

함수실제 역할실패/다음 소비자
do_execveat_common L1888–1970bprm 할당, arg/env 수와 용량 확인, 문자열 복사준비 실패는 free_bprm() 뒤 errno 반환; 성공 시 bprm_execve()
bprm_execve L1812–1886cred 준비, 실행 파일 열기, exec_binprm() 호출경계 뒤 오류에는 필요 시 fatal signal 설정
exec_binprm / search_binary_handlerELF/script 등 handler와 interpreter를 탐색handler가 성공 경로에서 begin_new_exec() 호출

구조체와 필드

준비 객체와 현재 task에 설치된 객체를 분리해서 읽는다. linux_binprm은 경계 이전의 입력·새 mm 계획을 보유하며, current->mm의 교체는 exec_mmap()에서 일어난다.

객체/필드생산 지점읽는 기준
current / task_struct기존 호출 task새 task를 할당하는 경로가 아니다. thread-group 조정은 de_thread()에서 일어날 수 있다.
linux_binprm의 argc/envc/p/argmindo_execveat_common(), bprm_stack_limits()문자열 수·스택 하한과 역방향 복사 위치를 결정
bprm->mm와 argv/envp 페이지alloc_bprm() 및 문자열 복사새 이미지의 준비 공간. exec_mmap() 전에는 현재 task의 mm이 아님
bprm->point_of_no_returnbegin_new_exec() L1259이후 오류가 기존 userspace로 돌아갈 수 없는 경계
current->mm / credsexec_mmap() / 후속 commit_creds()실제 새 주소 공간과 자격 증명 반영을 각 시점별로 구분

실행 단계와 상태 변화

begin_new_exec() L1259가 exec_mmap() L1296보다 먼저 no-return 경계를 세운다. 따라서 "새 mm 설치 뒤에야 되돌릴 수 없다"는 순서는 v6.6 코드와 반대다.

그림 3. no-return 경계와 mm 설치의 실제 순서
exv-boundaryv6.6 begin_new_exec에서 bprm_creds_from_file 다음 point_of_no_return을 설정하고 de_thread와 기타 작업 뒤 exec_mmap으로 새 주소 공간을 설치한다. 경계 전 오류는 기존 image로 errno 반환, 뒤 오류는 fatal signal 경로다.bprm_creds_from_fileL1252 · 아직 경계 전point_of_no_returnL1259 · 먼저 truede_thread 등L1264–1285 · 실패 가능exec_mmap()L1296 · 새 mm 설치후속 setup / credbegin_new_exec 후반fatal signalL1877–1878 조건부errno 반환 가능기존 userspace image되돌릴 수 없는 경계는 mm 설치 이전이후 오류가 나면 C 함수는 음수 ret를 전파해도 기존 사용자 이미지로 돌아가지 않는다.trace_sched_process_exec() L1803은 handler 성공 후의 관찰 지점이다.

bprm_creds_from_file()은 경계 전이다. 경계 뒤 실패는 C 내부에서 음수 값을 전파할 수 있어도 기존 사용자 이미지로 errno를 돌려주지 않도록 fatal signal을 보장한다.

위치상태 변화오류의 의미
인자 준비 / 파일 열기 / L1252새 bprm·mm과 creds를 준비, 기존 current->mm 유지실패하면 정리 후 기존 userspace에 errno 반환 가능
L1259point_of_no_return = true이후 실패는 기존 이미지로 돌아갈 수 없음
L1264–1285de_thread(), 파일 테이블 분리, 새 mm의 실행 파일 지정mm 교체 전이라도 이미 fatal 경계 뒤
L1296 / L1013–1027exec_mmap()이 새 mm을 current에 설치이 단계 자체도 오류 가능; 경계는 이미 지나 있음
L1382 / L1803후속 자격 증명 commit / 성공 후 sched_process_exectracepoint는 성공 관찰 지점이지 모든 오류의 경계 표식이 아님

불변 조건과 실패 사례

검토할 조건v6.6 코드상 이유실패 해석
새 task 생성으로 설명하지 않기동일한 current가 새 사용자 이미지로 바뀐다.PID만 보고 새 process라고 추론하면 de_thread()와 mm 교체를 혼동한다.
인자 문자열과 pointer 공간 함께 계산bprm_stack_limits()는 argc=0에도 빈 argv[0] 자리와 pointer 배열을 고려한다.-E2BIG, -EFAULT 등은 handler 진입 전에도 발생할 수 있다.
handler 재시도 경계 준수search_binary_handler()는 point_of_no_return 또는 retval != -ENOEXEC면 반환한다.경계 뒤에 다른 handler로 정상 fallback한다고 그리면 안 된다.
경계 뒤 오류를 fatal로 처리bprm_execve() L1877–1878은 이미 fatal signal이 없을 때 SIGSEGV를 강제한다.오래된 사용자 이미지가 일반 errno를 받아 계속 실행하는 경로가 아니다.

계측과 검증

각 관찰 수단은 서로 다른 시점을 보여 준다. tracefs의 전역 tracer/filter나 trace_pipe는 공유 상태를 바꾸거나 다른 관찰자의 데이터를 소비할 수 있으므로 아래 예시는 읽기 전용 기능 확인만 한다.

관찰 수단보이는 범위보이지 않는 것
syscalls:sys_enter_execve / execveat호출 진입과 인자 pointer; 해당 event가 빌드에 있을 때문자열 복사·handler·mm 교체의 성공 여부
syscalls:sys_exit_execve제공·활성화된 경우 syscall 반환값내부 실패 단계의 위치; no-return 이후 기존 image 복귀 여부를 단독 판정할 수 없음
sched:sched_process_execexec_binprm() L1803의 성공 후 사건실패한 호출·준비 중 단계
function trace / do_execveat_common별도 격리 계측을 구성하면 함수 진입·소요 시간호출 관찰만으로 경계·handler 성공을 단정할 수 없음
/proc/<pid>/maps읽는 순간 해당 task의 주소 공간 snapshot과거 실패와 함수 내부 진행 단계
읽기 전용 확인:
# tracefs가 이미 제공된 환경에서만 조회한다.
test -r /sys/kernel/tracing/available_events &&   grep -E '^(sched:sched_process_exec|syscalls:sys_(enter|exit)_execve(at)?)$' /sys/kernel/tracing/available_events
test -r /sys/kernel/tracing/events/sched/sched_process_exec/format &&   cat /sys/kernel/tracing/events/sched/sched_process_exec/format

추가 확인 사항

  • 해당 보드의 v6.6 빌드에서 등록된 linux_binfmt 목록과 interpreter rewrite 조건은 무엇인가?
  • 실패가 L1259 앞인지 뒤인지 어떤 함수 반환·fatal signal 관찰로 구분할 수 있는가?
  • 같은 task가 exec에 성공한 뒤 새 mm과 credentials가 각각 언제 보이는지 어떻게 검증할 것인가?
  • tracefs 이벤트가 실제 빌드에 있는지 읽기 전용으로 확인한 뒤, 실험용 격리 trace instance가 필요한가?