본문 바로가기
Kernel/理解

ELF 실행 & execve

by 暻煥 2024. 2. 2.

ELF 실행되는 과정 요약

 

Kernel v4.1 기준

 


 

Source Code 분석

 

먼저 실행가능한 파일(ELF)에 대한 정보를 담게될 linux_binprm에 대해서 살펴보자.

 

/*
 * This structure is used to hold the arguments that are used when loading binaries.
 */
struct linux_binprm {
	char buf[BINPRM_BUF_SIZE]; // ELF Header의 128개 문자르 저장한다
#ifdef CONFIG_MMU
	struct vm_area_struct *vma;
	unsigned long vma_pages;
#else
# define MAX_ARG_PAGES	32
	struct page *page[MAX_ARG_PAGES];
#endif
	struct mm_struct *mm;
	unsigned long p; /* current top of mem, 메모리 페이지의 제일 높은 주소 */
	unsigned int
		cred_prepared:1,/* true if creds already prepared (multiple
				 * preps happen for interpreters) */
		cap_effective:1;/* true if has elevated effective capabilities,
				 * false if not; except for init which inherits
				 * its parent's caps anyway */
#ifdef __alpha__
	unsigned int taso:1;
#endif
	unsigned int recursion_depth; /* only for search_binary_handler() */
	struct file * file; // 실행할 ELF File
	struct cred *cred;	/* new credentials */
	int unsafe;		/* how unsafe this exec is (mask of LSM_UNSAFE_*) */
	unsigned int per_clear;	/* bits to clear in current->personality */
	int argc, envc; // 인자와 환경변수
	const char * filename;	/* Name of binary as seen by procps */
	const char * interp;	/* Name of the binary really executed. Most
				   of the time same as filename, but could be
				   different for binfmt_{misc,script} */ // 실행할 파일의 진짜 이름, 보통은 filename과 같다
	unsigned interp_flags;
	unsigned interp_data;
	unsigned long loader, exec;
};

 

Linux는 여러가지 실행가능한 파일 type을 제공한다. 실행 가능한 file들에 대한 정보는 linux_binfmt 구조체에 등록되어 있고, 리스트 형태로 관리된다.

register_binfmt( ) 함수를 통하여 시스템 초기화 시, 각 파일 포맷에 맞는 handler가 등록된다.

아래 글에서 세부적인 내용을 다루고 여기서는 구조체의 형태만 한번 보도록 하자.

 

/*
  * This structure defines the functions that are used to load the binary formats that
  * linux accepts.
  */
struct linux_binfmt {
    struct list_head lh;
    struct module *module;
    int (*load_binary)(struct linux_binprm *);
    int (*load_shlib)(struct file *);
    int (*core_dump)(struct coredump_params *cprm);
    unsigned long min_coredump;     /* minimal dump size */
 };

 

ELF 실행의 함수 Call Tree는 위 그림과 같다.

아래 부터는 위 그림의 내용을 하나씩 따라가 본다.

 

SYSCALL_DEFINE3(execve,
		const char __user *, filename,
		const char __user *const __user *, argv,
		const char __user *const __user *, envp)
{
	return do_execve(getname(filename), argv, envp);
}

ELF 파일이 처음으로 실행되면 SYSCALL_DEFINE3 macro define을 이용하여 do_execve가 실행된다.

getname( ) 함수는 다시 getname_flags( ) 함수를 호출하고, memory 공간을 할당(kzalloc)받아 user space에서 부터 전달된 filename을 저장한다.

 

int do_execve(struct filename *filename,
	const char __user *const __user *__argv,
	const char __user *const __user *__envp)
{
	struct user_arg_ptr argv = { .ptr.native = __argv };
	struct user_arg_ptr envp = { .ptr.native = __envp };
	return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}

user_arg_ptr 구조체는 user space의 data를 encapsulation 하는데 사용되어지는 구조체이다. process가 사용하게 될 인자인 __argv와 __envp를 encapsulation 한다.
AT_FDCWD는 file open이 root directory가 아닌 현재 directory에서 진행되도록 하는 flag 이다.

 

static int do_execveat_common(int fd, struct filename *filename,
			      struct user_arg_ptr argv,
			      struct user_arg_ptr envp,
			      int flags)
{
	char *pathbuf = NULL;
	struct linux_binprm *bprm;
	struct file *file;
	struct files_struct *displaced;
	int retval;
// IS_ERR macro를 이용하여 filename의 유효성을 검사한다. 
// 만약 filename이 가르키고 있는 포인터가 
// kernel space의 제일 마지막 page를 가르키고 있다면 
// ERR로 판단하고 함수를 종료한다.
	if (IS_ERR(filename))
		return PTR_ERR(filename);

	/*
	 * We move the actual failure in case of RLIMIT_NPROC excess from
	 * set*uid() to execve() because too many poorly written programs
	 * don't check setuid() return code.  Here we additionally recheck
	 * whether NPROC limit is still exceeded.
	 */
// PF_NPROC_EXCCEEDED는 process의 갯수가 최대 갯수인 RLIMIT_NPROC를 초과하는 경우 set 된다. 
// 현재 상태가 더이상 process를 새롭게 만들 수 없는 상황이라면 함수를 종료한다.
	if ((current->flags & PF_NPROC_EXCEEDED) &&
	    atomic_read(&current_user()->processes) > rlimit(RLIMIT_NPROC)) {
		retval = -EAGAIN;
		goto out_ret;
	}

	/* We're below the limit (still or again), so we don't want to make
	 * further execve() calls fail. */
	current->flags &= ~PF_NPROC_EXCEEDED;
// unshare_files( ) 함수를 이용하여 이전 process의 file list pointer를 displaced에 저장한다. 
// 만약 이전 process의 file_struct 구조체에서 error를 발견시에는 
// 이전 file list를 복구하는데 사용된다.
	retval = unshare_files(&displaced);
	if (retval)
		goto out_ret;
// linux_binprm 구조체를 할당받는다.
	retval = -ENOMEM;
	bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
	if (!bprm)
		goto out_files;
// linux_binprm 구조체에 대하여 초기화 진행.
// 이전 process로 부터 cred 구조체를 복사하고, 인증정보를 가져온다.
	retval = prepare_bprm_creds(bprm);
	if (retval)
		goto out_free;
// process가 인증받은 안전한 상태인지 확인하고, 
// 만약 unsafe 상태라면 linux_binprm->unsafe에 값을 설정한다.
	check_unsafe_exec(bprm);
	current->in_execve = 1;
// 내부적으로 do_flip_open( ) 함수를 호출하여 file을 열고, file 구조체를 반환한다.
	file = do_open_execat(fd, filename, flags);
	retval = PTR_ERR(file);
	if (IS_ERR(file))
		goto out_unmark;
// 함수는 multi core 환경에서, 현재 가장 부하가 적은 core를 찾아 binary를 수행한다.
	sched_exec();

	bprm->file = file;
	if (fd == AT_FDCWD || filename->name[0] == '/') {
		bprm->filename = filename->name;
	} else {
		if (filename->name[0] == '\0')
			pathbuf = kasprintf(GFP_TEMPORARY, "/dev/fd/%d", fd);
		else
			pathbuf = kasprintf(GFP_TEMPORARY, "/dev/fd/%d/%s",
					    fd, filename->name);
		if (!pathbuf) {
			retval = -ENOMEM;
			goto out_unmark;
		}
		/*
		 * Record that a name derived from an O_CLOEXEC fd will be
		 * inaccessible after exec. Relies on having exclusive access to
		 * current->files (due to unshare_files above).
		 */
		if (close_on_exec(fd, rcu_dereference_raw(current->files->fdt)))
			bprm->interp_flags |= BINPRM_FLAGS_PATH_INACCESSIBLE;
		bprm->filename = pathbuf;
	}
	bprm->interp = bprm->filename;
// 새로운 process 생성에 쓰일 mm_struct 를 할당한다.
	retval = bprm_mm_init(bprm);
	if (retval)
		goto out_unmark;
// 인자와 환경변수의 길이를 계산하여 bprm->argc, bprm->envc에 대입한다. 
// count( ) 함수 내부에서는 get_user( ) 함수를 이용하여 user space pointer의 유효셩을 검사한다. 
// (thread_info->addr_limit을 통해서 확인한다. 
// get_user( )는 kernel에서 user space의 값을 읽어오는 API이다.)
	bprm->argc = count(argv, MAX_ARG_STRINGS);
	if ((retval = bprm->argc) < 0)
		goto out;

	bprm->envc = count(envp, MAX_ARG_STRINGS);
	if ((retval = bprm->envc) < 0)
		goto out;
// process의 권한을 설정하고, ELF 파일의 내용을 읽어서 bprm->buf의 cache에 저장한다.
	retval = prepare_binprm(bprm);
	if (retval < 0)
		goto out;
// copy_strings_kernel( )과 copy_strings( ) 함수를 이용하여 
// user space의 data를 kernel space로 복사한다. 
// 또한 내부적으로는 kmap( )을 이용하여 kernel space의 page를 할당받는다.
	retval = copy_strings_kernel(1, &bprm->filename, bprm);
	if (retval < 0)
		goto out;

	bprm->exec = bprm->p;
	retval = copy_strings(bprm->envc, envp, bprm);
	if (retval < 0)
		goto out;

	retval = copy_strings(bprm->argc, argv, bprm);
	if (retval < 0)
		goto out;
// 새로운 process를 시작한다.
	retval = exec_binprm(bprm);
	if (retval < 0)
		goto out;

	/* execve succeeded */
	current->fs->in_exec = 0;
	current->in_execve = 0;
	acct_update_integrals(current);
	task_numa_free(current);
	free_bprm(bprm);
	kfree(pathbuf);
	putname(filename);
	if (displaced)
		put_files_struct(displaced);
	return retval;

out:
	if (bprm->mm) {
		acct_arg_size(bprm, 0);
		mmput(bprm->mm);
	}

out_unmark:
	current->fs->in_exec = 0;
	current->in_execve = 0;

out_free:
	free_bprm(bprm);
	kfree(pathbuf);

out_files:
	if (displaced)
		reset_files_struct(displaced);
out_ret:
	putname(filename);
	return retval;
}

IS_ERR macro를 이용하여 filename의 유효성을 검사한다. 만약 filename이 가르키고 있는 포인터가 kernel space의 제일 마지막 page를 가르키고 있다면 ERR로 판단하고 함수를 종료한다.

PF_NPROC_EXCCEEDED는 process의 갯수가 최대 갯수인 RLIMIT_NPROC를 초과하는 경우 set 된다. 현재 상태가 더이상 process를 새롭게 만들 수 없는 상황이라면 함수를 종료한다.

unshare_files( ) 함수를 이용하여 이전 process의 file list pointer를 displaced에 저장한다. 만약 이전 process의 file_struct 구조체에서 error를 발견시에는 이전 file list를 복구하는데 사용된다.

linux_binprm 구조체를 할당받고, 해당 구조체에 대한 초기화를 진행한다.

prepare_bprm_creds( ) 함수는 이전 process로 부터 cred 구조체를 복사하고, 인증정보를 가져온다.

check_unsafe_exec( )를 통하여 process가 인증받은 안전한 상태인지 확인하고, 만약 unsafe 상태라면 linux_binprm->unsafe에 값을 설정한다.

do_open_execat( ) 함수는 do_flip_open( ) 함수를 호출하여 file을 열고, file 구조체를 반환한다.

sched_exec( ) 함수는 multi core 환경에서, 현재 가장 부하가 적은 core를 찾아 binary를 수행한다.

bprm_mm_init( ) 함수를 이용하여 새로운 process 생성에 쓰일 mm_struct 를 할당한다.

인자와 환경변수의 길이를 계산하여 bprm->argc, bprm->envc에 대입한다. count( ) 함수 내부에서는 get_user( ) 함수를 이용하여 user space pointer의 유효셩을 검사한다. (thread_info->addr_limit을 통해서 확인한다. get_user( )는 kernel에서 user space의 값을 읽어오는 API이다.)

prepare_binprm( )에서는 process의 권한을 설정하고, ELF 파일의 내용을 읽어서 bprm->buf의 cache에 저장한다.

copy_strings_kernel( )과 copy_strings( ) 함수를 이용하여 user space의 data를 kernel space로 복사한다. 또한 내부적으로는 kmap( )을 이용하여 kernel space의 page를 할당받는다.

마지막으로, exec_binprm( )을 호출하여 새로운 process를 시작한다.

 

/*
 *	Helper to unshare the files of the current task.
 *	We don't want to expose copy_files internals to
 *	the exec layer of the kernel.
 */

int unshare_files(struct files_struct **displaced)
{
	struct task_struct *task = current;
	struct files_struct *copy = NULL;
	int error;

	error = unshare_fd(CLONE_FILES, &copy);
	if (error || !copy) {
		*displaced = NULL;
		return error;
	}
	*displaced = task->files;
	task_lock(task);
	task->files = copy;
	task_unlock(task);
	return 0;
}
/*
 * Unshare file descriptor table if it is being shared
 */
static int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp)
{
	struct files_struct *fd = current->files;
	int error = 0;

	if ((unshare_flags & CLONE_FILES) &&
	    (fd && atomic_read(&fd->count) > 1)) {
		*new_fdp = dup_fd(fd, &error);
		if (!*new_fdp)
			return error;
	}

	return 0;
}

unshare_files( )은 unshare_fd( )함수를 통하여 직전 process인 current의 file list를 displaced에 복사해 온다.

해당 조작은 dup_fd( )함수를 통하여 이루어 진다.

/*
 * Prepare credentials and lock ->cred_guard_mutex.
 * install_exec_creds() commits the new creds and drops the lock.
 * Or, if exec fails before, free_bprm() should release ->cred and
 * and unlock.
 */
int prepare_bprm_creds(struct linux_binprm *bprm)
{
	if (mutex_lock_interruptible(&current->signal->cred_guard_mutex))
		return -ERESTARTNOINTR;

	bprm->cred = prepare_exec_creds();
	if (likely(bprm->cred))
		return 0;

	mutex_unlock(&current->signal->cred_guard_mutex);
	return -ENOMEM;
}
struct cred *prepare_exec_creds(void)
{
	struct cred *new;

	new = prepare_creds();
	if (!new)
		return new;

#ifdef CONFIG_KEYS
	/* newly exec'd tasks don't get a thread keyring */
	key_put(new->thread_keyring);
	new->thread_keyring = NULL;

	/* inherit the session keyring; new process keyring */
	key_put(new->process_keyring);
	new->process_keyring = NULL;
#endif

	return new;
}
/**
 * prepare_creds - Prepare a new set of credentials for modification
 *
 * Prepare a new set of task credentials for modification.  A task's creds
 * shouldn't generally be modified directly, therefore this function is used to
 * prepare a new copy, which the caller then modifies and then commits by
 * calling commit_creds().
 *
 * Preparation involves making a copy of the objective creds for modification.
 *
 * Returns a pointer to the new creds-to-be if successful, NULL otherwise.
 *
 * Call commit_creds() or abort_creds() to clean up.
 */
struct cred *prepare_creds(void)
{
	struct task_struct *task = current;
	const struct cred *old;
	struct cred *new;

	validate_process_creds();

	new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
	if (!new)
		return NULL;

	kdebug("prepare_creds() alloc %p", new);

	old = task->cred;
	memcpy(new, old, sizeof(struct cred));

	atomic_set(&new->usage, 1);
	set_cred_subscribers(new, 0);
	get_group_info(new->group_info);
	get_uid(new->user);
	get_user_ns(new->user_ns);

#ifdef CONFIG_KEYS
	key_get(new->session_keyring);
	key_get(new->process_keyring);
	key_get(new->thread_keyring);
	key_get(new->request_key_auth);
#endif

#ifdef CONFIG_SECURITY
	new->security = NULL;
#endif

	if (security_prepare_creds(new, old, GFP_KERNEL) < 0)
		goto error;
	validate_creds(new);
	return new;

error:
	abort_creds(new);
	return NULL;
}
EXPORT_SYMBOL(prepare_creds);

이전 process의 cred 구조체를 복사해 오고 reference counter를 증가시킨다.

 

/*
 * determine how safe it is to execute the proposed program
 * - the caller must hold ->cred_guard_mutex to protect against
 *   PTRACE_ATTACH or seccomp thread-sync
 */
static void check_unsafe_exec(struct linux_binprm *bprm)
{
	struct task_struct *p = current, *t;
	unsigned n_fs;

	if (p->ptrace) {
		if (p->ptrace & PT_PTRACE_CAP)
			bprm->unsafe |= LSM_UNSAFE_PTRACE_CAP;
		else
			bprm->unsafe |= LSM_UNSAFE_PTRACE;
	}

	/*
	 * This isn't strictly necessary, but it makes it harder for LSMs to
	 * mess up.
	 */
	if (task_no_new_privs(current))
		bprm->unsafe |= LSM_UNSAFE_NO_NEW_PRIVS;

	t = p;
	n_fs = 1;
	spin_lock(&p->fs->lock);
	rcu_read_lock();
	while_each_thread(p, t) {
		if (t->fs == p->fs)
			n_fs++;
	}
	rcu_read_unlock();

	if (p->fs->users > n_fs)
		bprm->unsafe |= LSM_UNSAFE_SHARE;
	else
		p->fs->in_exec = 1;
	spin_unlock(&p->fs->lock);
}

ptrace는 debugging 정보가 포함된 ELF의 경우 설정된다.

task_no_new_privs( ) macro는 include/linux/sched.h에 정의되어 있으며, task_struct->atomic_flags가 PFA_NO_NEW_PRIVS가 설정되어 있는지 검사한다. (새로운 권한을 획득하지 않음을 나타낸다.)

그 다음 process내의 thread 中 fs_struct가 동일한 thread의 갯수를 새어, 현재 process내의 fs_struct user 갯수와 비교한다. 만약 fs_struct user 갯수가 더 많다면, bprm->unsafe에 LSM_UNSAFE_SHARE를 설정한다. 만약에 작다면 current->fs->in_exec에 1을 설정한다.

 

static struct file *do_open_execat(int fd, struct filename *name, int flags)
{
	struct file *file;
	int err;
	struct open_flags open_exec_flags = {
		.open_flag = O_LARGEFILE | O_RDONLY | __FMODE_EXEC,
		.acc_mode = MAY_EXEC | MAY_OPEN,
		.intent = LOOKUP_OPEN,
		.lookup_flags = LOOKUP_FOLLOW,
	};

	if ((flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) != 0)
		return ERR_PTR(-EINVAL);
	if (flags & AT_SYMLINK_NOFOLLOW)
		open_exec_flags.lookup_flags &= ~LOOKUP_FOLLOW;
	if (flags & AT_EMPTY_PATH)
		open_exec_flags.lookup_flags |= LOOKUP_EMPTY;

	file = do_filp_open(fd, name, &open_exec_flags);
	if (IS_ERR(file))
		goto out;

	err = -EACCES;
	if (!S_ISREG(file_inode(file)->i_mode))
		goto exit;

	if (file->f_path.mnt->mnt_flags & MNT_NOEXEC)
		goto exit;

	err = deny_write_access(file);
	if (err)
		goto exit;

	if (name->name[0] != '\0')
		fsnotify_open(file);

out:
	return file;

exit:
	fput(file);
	return ERR_PTR(err);
}

ELF 파일을 열기 위하여 필요한 작업들을 수행한다.

 

/*
 * sched_exec - execve() is a valuable balancing opportunity, because at
 * this point the task has the smallest effective memory and cache footprint.
 */
void sched_exec(void)
{
	struct task_struct *p = current;
	unsigned long flags;
	int dest_cpu;

	raw_spin_lock_irqsave(&p->pi_lock, flags);
	dest_cpu = p->sched_class->select_task_rq(p, task_cpu(p), SD_BALANCE_EXEC, 0);
	if (dest_cpu == smp_processor_id())
		goto unlock;

	if (likely(cpu_active(dest_cpu))) {
		struct migration_arg arg = { p, dest_cpu };

		raw_spin_unlock_irqrestore(&p->pi_lock, flags);
		stop_one_cpu(task_cpu(p), migration_cpu_stop, &arg);
		return;
	}
unlock:
	raw_spin_unlock_irqrestore(&p->pi_lock, flags);
}

select_task_rq( )함수를 통하여 load가 가정 적은 cpu를 선택한다.

stop_one_cpu( )를 이용하여 선택된 cpu를 멈추고 문맥교환을 진행한다.

 

/*
 * Create a new mm_struct and populate it with a temporary stack
 * vm_area_struct.  We don't have enough context at this point to set the stack
 * flags, permissions, and offset, so we use temporary values.  We'll update
 * them later in setup_arg_pages().
 */
static int bprm_mm_init(struct linux_binprm *bprm)
{
	int err;
	struct mm_struct *mm = NULL;

	bprm->mm = mm = mm_alloc();
	err = -ENOMEM;
	if (!mm)
		goto err;

	err = __bprm_mm_init(bprm);
	if (err)
		goto err;

	return 0;

err:
	if (mm) {
		bprm->mm = NULL;
		mmdrop(mm);
	}

	return err;
}
static int __bprm_mm_init(struct linux_binprm *bprm)
{
	int err;
	struct vm_area_struct *vma = NULL;
	struct mm_struct *mm = bprm->mm;

	bprm->vma = vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
	if (!vma)
		return -ENOMEM;

	down_write(&mm->mmap_sem);
	vma->vm_mm = mm;

	/*
	 * Place the stack at the largest stack address the architecture
	 * supports. Later, we'll move this to an appropriate place. We don't
	 * use STACK_TOP because that can depend on attributes which aren't
	 * configured yet.
	 */
	BUILD_BUG_ON(VM_STACK_FLAGS & VM_STACK_INCOMPLETE_SETUP);
	vma->vm_end = STACK_TOP_MAX;
	vma->vm_start = vma->vm_end - PAGE_SIZE;
	vma->vm_flags = VM_SOFTDIRTY | VM_STACK_FLAGS | VM_STACK_INCOMPLETE_SETUP;
	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
	INIT_LIST_HEAD(&vma->anon_vma_chain);

	err = insert_vm_struct(mm, vma);
	if (err)
		goto err;

	mm->stack_vm = mm->total_vm = 1;
	arch_bprm_mm_init(mm, vma);
	up_write(&mm->mmap_sem);
	bprm->p = vma->vm_end - sizeof(void *);
	return 0;
err:
	up_write(&mm->mmap_sem);
	bprm->vma = NULL;
	kmem_cache_free(vm_area_cachep, vma);
	return err;
}

bprm_mm_init( ) 함수는 먼저 mm_alloc( )을 통하여 새로운 process의 mm_struct를 할당받는다. 다음에는 __bprm_mm_init( )을 통하여 초기화를 진행한다.

__bprm_mm_init( )에서는 virtual address space를 관리하는 구조체안 vm_area_struct를 할당받는다. 그 다음에는 각각의 member 변수에 대한 초기화를 진행한다.

이 때, virtual address의 마지막을 나타내는 vm_end에 STACK_TOP_MAX 값을 대입한다.

(MMU가 활성화 되어 있는 32bit system의 경우 :

STACK_TOP_MAX = TASK_SIZE = CONFIG_PAGEOFFSET - SZ_16M = 0xC0000000 - 0x01000000 = 0xBF000000 ==> process가 사용 가능한 최대 크기의 가상주소 공간을 말한다)

insert_vm_struct( ) 함수는 할당받은 virtual address space인 vm_area_struct를 memory 관리 system인 mm_struct에 입력한다.

arch_bprm_mm_init( ) 함수를 통하여 해당 프로세서에 맞는 초기화를 진행한다.

마지막으로, stack pointer를 설정하는데 STACK_TOP_MAX에서 pointer 크기 만큼을 뺀 값이 stack pointer가 된다.

 

/*
 * Fill the binprm structure from the inode.
 * Check permissions, then read the first 128 (BINPRM_BUF_SIZE) bytes
 *
 * This may be called multiple times for binary chains (scripts for example).
 */
int prepare_binprm(struct linux_binprm *bprm)
{
	int retval;

	bprm_fill_uid(bprm);

	/* fill in binprm security blob */
	retval = security_bprm_set_creds(bprm);
	if (retval)
		return retval;
	bprm->cred_prepared = 1;

	memset(bprm->buf, 0, BINPRM_BUF_SIZE);
	return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE);
}

EXPORT_SYMBOL(prepare_binprm);

bprm_fill_uid( )는 process의 uid와 gid를 설정한다. 그 다음, security_bprm_set_creds( ) 함수를 통하여 권한을 설정한다. 마지막으로, kernel_read( ) 함수를 통하여 ELF file의 내용을 읽어서 bprm의 cache buf에 기록한다.

 

static void bprm_fill_uid(struct linux_binprm *bprm)
{
	struct inode *inode;
	unsigned int mode;
	kuid_t uid;
	kgid_t gid;

	/* clear any previous set[ug]id data from a previous binary */
	bprm->cred->euid = current_euid();
	bprm->cred->egid = current_egid();

	if (bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)
		return;

	if (task_no_new_privs(current))
		return;

	inode = file_inode(bprm->file);
	mode = READ_ONCE(inode->i_mode);
	if (!(mode & (S_ISUID|S_ISGID)))
		return;

	/* Be careful if suid/sgid is set */
	mutex_lock(&inode->i_mutex);

	/* reload atomically mode/uid/gid now that lock held */
	mode = inode->i_mode;
	uid = inode->i_uid;
	gid = inode->i_gid;
	mutex_unlock(&inode->i_mutex);

	/* We ignore suid/sgid if there are no mappings for them in the ns */
	if (!kuid_has_mapping(bprm->cred->user_ns, uid) ||
		 !kgid_has_mapping(bprm->cred->user_ns, gid))
		return;

	if (mode & S_ISUID) {
		bprm->per_clear |= PER_CLEAR_ON_SETID;
		bprm->cred->euid = uid;
	}

	if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
		bprm->per_clear |= PER_CLEAR_ON_SETID;
		bprm->cred->egid = gid;
	}
}

먼저 euid와 egid를 직전 process의 euid와 egid로 설정한다.
그 다음, file의 mode 中, S_ISGID와 S_ISUID에 해당하는 bit를 검사하여 실행가능한 파일인지 검사한다. 만약 실행가능한 파일이면 해당하는 bit가 1로 set되어 있다.
파일이 실행 중에는 process의 effective group ID(egid)와 effective user ID(euid)가 file에 기록되어 있는 i_uid와 i_gid로 설정된다.

 

static int exec_binprm(struct linux_binprm *bprm)
{
	pid_t old_pid, old_vpid;
	int ret;

	/* Need to fetch pid before load_binary changes it */
	old_pid = current->pid;
	rcu_read_lock();
	old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));
	rcu_read_unlock();

	ret = search_binary_handler(bprm);
	if (ret >= 0) {
		audit_bprm(bprm);
		trace_sched_process_exec(current, old_pid, bprm);
		ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
		proc_exec_connector(current);
	}

	return ret;
}
int search_binary_handler(struct linux_binprm *bprm)
{
	bool need_retry = IS_ENABLED(CONFIG_MODULES);
	struct linux_binfmt *fmt;
	int retval;

	/* This allows 4 levels of binfmt rewrites before failing hard. */
	if (bprm->recursion_depth > 5)
		return -ELOOP;

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

	retval = -ENOENT;
 retry:
	read_lock(&binfmt_lock);
	list_for_each_entry(fmt, &formats, lh) {
		if (!try_module_get(fmt->module))
			continue;
		read_unlock(&binfmt_lock);
		bprm->recursion_depth++;
		retval = fmt->load_binary(bprm);
		read_lock(&binfmt_lock);
		put_binfmt(fmt);
		bprm->recursion_depth--;
		if (retval < 0 && !bprm->mm) {
			/* we got to flush_old_exec() and failed after it */
			read_unlock(&binfmt_lock);
			force_sigsegv(SIGSEGV, current);
			return retval;
		}
		if (retval != -ENOEXEC || !bprm->file) {
			read_unlock(&binfmt_lock);
			return retval;
		}
	}
	read_unlock(&binfmt_lock);

	if (need_retry) {
		if (printable(bprm->buf[0]) && printable(bprm->buf[1]) &&
		    printable(bprm->buf[2]) && printable(bprm->buf[3]))
			return retval;
		if (request_module("binfmt-%04x", *(ushort *)(bprm->buf + 2)) < 0)
			return retval;
		need_retry = false;
		goto retry;
	}

	return retval;
}
EXPORT_SYMBOL(search_binary_handler);

exec_binprm( )은 search_binary_handler( )을 통하여 실행 가능한 파일을 조작한다.

미리 등록되어 있는 format handler들을 순회하면서 인식 가능한 파일인지 검사하고, load_binary( )를 실행한다.

static struct linux_binfmt elf_format = {
	.module		= THIS_MODULE,
	.load_binary	= load_elf_binary,
	.load_shlib	= load_elf_library,
	.core_dump	= elf_core_dump,
	.min_coredump	= ELF_EXEC_PAGESIZE,
};
static int __init init_elf_binfmt(void)
{
	register_binfmt(&elf_format);
	return 0;
}

ELF 파일의 경우 format handler에 기술되어 있는 대로, load_elf_binary를 실행한다.

 

static int load_elf_binary(struct linux_binprm *bprm)
{
	struct file *interpreter = NULL; /* to shut gcc up */
 	unsigned long load_addr = 0, load_bias = 0;
	int load_addr_set = 0;
	char * elf_interpreter = NULL;
	unsigned long error;
	struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
	unsigned long elf_bss, elf_brk;
	int retval, i;
	unsigned long elf_entry;
	unsigned long interp_load_addr = 0;
	unsigned long start_code, end_code, start_data, end_data;
	unsigned long reloc_func_desc __maybe_unused = 0;
	int executable_stack = EXSTACK_DEFAULT;
	struct pt_regs *regs = current_pt_regs();
	struct {
		struct elfhdr elf_ex;
		struct elfhdr interp_elf_ex;
	} *loc;
	struct arch_elf_state arch_state = INIT_ARCH_ELF_STATE;

	loc = kmalloc(sizeof(*loc), GFP_KERNEL);
	if (!loc) {
		retval = -ENOMEM;
		goto out_ret;
	}
	
	/* Get the exec-header */
	loc->elf_ex = *((struct elfhdr *)bprm->buf);

	retval = -ENOEXEC;
	/* First of all, some simple consistency checks */
	if (memcmp(loc->elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
		goto out;

	if (loc->elf_ex.e_type != ET_EXEC && loc->elf_ex.e_type != ET_DYN)
		goto out;
	if (!elf_check_arch(&loc->elf_ex))
		goto out;
	if (!bprm->file->f_op->mmap)
		goto out;

	elf_phdata = load_elf_phdrs(&loc->elf_ex, bprm->file);
	if (!elf_phdata)
		goto out;

......

 

load_elf_binary( )의 첫번째 부분은 ELF header의 주요 내용들을 검사하는 것이다.

먼저 e_indent라고 부르는 magic number를 확인한다.

그 다음에는 e_type이 ET_EXEC, EX_DYN이 아니면 실행을 계속한다.

 

int elf_check_arch(const struct elf32_hdr *x)
{
	unsigned int eflags;

	/* Make sure it's an ARM executable */
	if (x->e_machine != EM_ARM)
		return 0;

	/* Make sure the entry address is reasonable */
	if (x->e_entry & 1) {
		if (!(elf_hwcap & HWCAP_THUMB))
			return 0;
	} else if (x->e_entry & 3)
		return 0;

	eflags = x->e_flags;
	if ((eflags & EF_ARM_EABI_MASK) == EF_ARM_EABI_UNKNOWN) {
		unsigned int flt_fmt;

		/* APCS26 is only allowed if the CPU supports it */
		if ((eflags & EF_ARM_APCS_26) && !(elf_hwcap & HWCAP_26BIT))
			return 0;

		flt_fmt = eflags & (EF_ARM_VFP_FLOAT | EF_ARM_SOFT_FLOAT);

		/* VFP requires the supporting code */
		if (flt_fmt == EF_ARM_VFP_FLOAT && !(elf_hwcap & HWCAP_VFP))
			return 0;
	}
	return 1;
}
EXPORT_SYMBOL(elf_check_arch);

 

elf_check_arch( )는 e_machine, e_entry, e_flags 값을 확인한다. 또한, entry address 유효성, Vector Floating Point 지원 여부등을 검사한다.

elf_check_arch( )의 구현은 architecture 별로 상이하다.

load_elf_phdr( ) 함수는 ELF File내의 Segment에 관한 정보를 가지고 있는 Program Header를 읽어서 elf_phdata에 저장한다.

Elf32_Ehdr 및 Elf32_Phdr에 대한 구조체는 아래와 같다.

 

typedef struct elf32_hdr{
  unsigned char	e_ident[EI_NIDENT];
  Elf32_Half	e_type;
  Elf32_Half	e_machine;
  Elf32_Word	e_version;
  Elf32_Addr	e_entry;  /* Entry point */
  Elf32_Off	e_phoff;
  Elf32_Off	e_shoff;
  Elf32_Word	e_flags;
  Elf32_Half	e_ehsize;
  Elf32_Half	e_phentsize;
  Elf32_Half	e_phnum;
  Elf32_Half	e_shentsize;
  Elf32_Half	e_shnum;
  Elf32_Half	e_shstrndx;
} Elf32_Ehdr;

typedef struct elf32_phdr{
  Elf32_Word	p_type;
  Elf32_Off	p_offset;
  Elf32_Addr	p_vaddr;
  Elf32_Addr	p_paddr;
  Elf32_Word	p_filesz;
  Elf32_Word	p_memsz;
  Elf32_Word	p_flags;
  Elf32_Word	p_align;
} Elf32_Phdr;

해당 구조체에 대한 자세한 설명은 본 글 아래의 링크를 참조하기 바란다.

 

/**
 * load_elf_phdrs() - load ELF program headers
 * @elf_ex:   ELF header of the binary whose program headers should be loaded
 * @elf_file: the opened ELF binary file
 *
 * Loads ELF program headers from the binary file elf_file, which has the ELF
 * header pointed to by elf_ex, into a newly allocated array. The caller is
 * responsible for freeing the allocated data. Returns an ERR_PTR upon failure.
 */
static struct elf_phdr *load_elf_phdrs(struct elfhdr *elf_ex,
				       struct file *elf_file)
{
	struct elf_phdr *elf_phdata = NULL;
	int retval, size, err = -1;

	/*
	 * If the size of this structure has changed, then punt, since
	 * we will be doing the wrong thing.
	 */
	if (elf_ex->e_phentsize != sizeof(struct elf_phdr))
		goto out;

	/* Sanity check the number of program headers... */
	if (elf_ex->e_phnum < 1 ||
		elf_ex->e_phnum > 65536U / sizeof(struct elf_phdr))
		goto out;

	/* ...and their total size. */
	size = sizeof(struct elf_phdr) * elf_ex->e_phnum;
	if (size > ELF_MIN_ALIGN)
		goto out;

	elf_phdata = kmalloc(size, GFP_KERNEL);
	if (!elf_phdata)
		goto out;

	/* Read in the program headers */
	retval = kernel_read(elf_file, elf_ex->e_phoff,
			     (char *)elf_phdata, size);
	if (retval != size) {
		err = (retval < 0) ? retval : -EIO;
		goto out;
	}

	/* Success! */
	err = 0;
out:
	if (err) {
		kfree(elf_phdata);
		elf_phdata = NULL;
	}
	return elf_phdata;
}

 

ELF File의 Header에서 e_phnum은 segment의 갯수를 나타내고, e_phentsize는 program header의 크기를 나타낸다.

e_phoff는 program header가 file 내에서 어디에 위치하는지를 나타난다.

kernel_read( ) 함수를 이용하여 file내의 program header를 읽어서 elf_phdata 구조체에 저장한다.

 

static int load_elf_binary(struct linux_binprm *bprm)
{
......
    elf_ppnt = elf_phdata;
	elf_bss = 0;
	elf_brk = 0;

	start_code = ~0UL;
	end_code = 0;
	start_data = 0;
	end_data = 0;

	for (i = 0; i < loc->elf_ex.e_phnum; i++) {
		if (elf_ppnt->p_type == PT_INTERP) {
			/* This is the program interpreter used for
			 * shared libraries - for now assume that this
			 * is an a.out format binary
			 */
			retval = -ENOEXEC;
			if (elf_ppnt->p_filesz > PATH_MAX || 
			    elf_ppnt->p_filesz < 2)
				goto out_free_ph;

			retval = -ENOMEM;
			elf_interpreter = kmalloc(elf_ppnt->p_filesz,
						  GFP_KERNEL);
			if (!elf_interpreter)
				goto out_free_ph;

			retval = kernel_read(bprm->file, elf_ppnt->p_offset,
					     elf_interpreter,
					     elf_ppnt->p_filesz);
			if (retval != elf_ppnt->p_filesz) {
				if (retval >= 0)
					retval = -EIO;
				goto out_free_interp;
			}
			/* make sure path is NULL terminated */
			retval = -ENOEXEC;
			if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0')
				goto out_free_interp;

			interpreter = open_exec(elf_interpreter);
			retval = PTR_ERR(interpreter);
			if (IS_ERR(interpreter))
				goto out_free_interp;

			/*
			 * If the binary is not readable then enforce
			 * mm->dumpable = 0 regardless of the interpreter's
			 * permissions.
			 */
			would_dump(bprm, interpreter);

			retval = kernel_read(interpreter, 0, bprm->buf,
					     BINPRM_BUF_SIZE);
			if (retval != BINPRM_BUF_SIZE) {
				if (retval >= 0)
					retval = -EIO;
				goto out_free_dentry;
			}

			/* Get the exec headers */
			loc->interp_elf_ex = *((struct elfhdr *)bprm->buf);
			break;
		}
		elf_ppnt++;
	}
......

 

load_elf_binary( )의 두번째 부분은 ELF File 내에서 interpreter의 정보를 읽어온다.

먼저, program header의 모든 부분은 탐색하면서 p_type값이 PT_INTERP인지 확인한다.

interpreter에 대해서는, p_filesz는 해당 library 까지의 path를 말한다. (ex. /usr/lib/gcc/x64-64-linux-gnu/7/libgcc_s.so)

p_offset은 ELF File 내부에서 path가 위치해 있는 공간을 가리킨다.

p_filesz와 p_offset 값의 유효성 여부에 대해서 검사하는 부분이 들어가 있다.

p_filesz 만큼의 공간을 할당받아서, kernel_read( )를 이용하여 interpreter의 path를 elf_interpreter에 저장한다.

다음으로, open_exec( )함수를 통해서 interpreter를 열어서 file 구조체를 반환한다. openc_exec( ) 함수 또한 내부적으로는 do_open_execat( ) 함수를 사용한다.

kernel_read( ) 함수를 사용하여 interpreter의 첫번째 128개의 문자를 bprm->buf에 저장한다. 이전에 ELF File header의 내용이 buffer에 쓰여져 있었으므로 overwrite 된다.

마지막으로, bprm->buf 저장되어 있는 interpreter 시작주소를 loc->interp_elf_ex에 저장한다.

 

static int load_elf_binary(struct linux_binprm *bprm)
{
......
    elf_ppnt = elf_phdata;
	for (i = 0; i < loc->elf_ex.e_phnum; i++, elf_ppnt++)
		switch (elf_ppnt->p_type) {
		case PT_GNU_STACK:
			if (elf_ppnt->p_flags & PF_X)
				executable_stack = EXSTACK_ENABLE_X;
			else
				executable_stack = EXSTACK_DISABLE_X;
			break;

		case PT_LOPROC ... PT_HIPROC:
			retval = arch_elf_pt_proc(&loc->elf_ex, elf_ppnt,
						  bprm->file, false,
						  &arch_state);
			if (retval)
				goto out_free_dentry;
			break;
		}
......

PT_GNU_STACK segment를 찾아서 stack의 실행가능 여부를 판단한다.

stack의 실행가능 여부에 따라서 executable_stack 값을 설정한다.

(executable stack : stack 영역에 code를 적재하여 실행하는 것을 말한다)

PT_LOPROC와 PT_HOPROC는 개별 processor를 위하여 reserve 되어 있는 segment이다.

 

/* Some simple consistency checks for the interpreter */
	if (elf_interpreter) {
		retval = -ELIBBAD;
		/* Not an ELF interpreter */
		if (memcmp(loc->interp_elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
			goto out_free_dentry;
		/* Verify the interpreter has a valid arch */
		if (!elf_check_arch(&loc->interp_elf_ex))
			goto out_free_dentry;

		/* Load the interpreter program headers */
		interp_elf_phdata = load_elf_phdrs(&loc->interp_elf_ex,
						   interpreter);
		if (!interp_elf_phdata)
			goto out_free_dentry;

		/* Pass PT_LOPROC..PT_HIPROC headers to arch code */
		elf_ppnt = interp_elf_phdata;
		for (i = 0; i < loc->interp_elf_ex.e_phnum; i++, elf_ppnt++)
			switch (elf_ppnt->p_type) {
			case PT_LOPROC ... PT_HIPROC:
				retval = arch_elf_pt_proc(&loc->interp_elf_ex,
							  elf_ppnt, interpreter,
							  true, &arch_state);
				if (retval)
					goto out_free_dentry;
				break;
			}
	}

path가 제공된 interpreter 또한 ELF 이다.

그러므로 마찬가지로 load_elf_phdrs( ) 함수를 통하여 interpreter의 program header를 읽어온다.

 

static int load_elf_binary(struct linux_binprm *bprm)
{
......
    /*
	 * Allow arch code to reject the ELF at this point, whilst it's
	 * still possible to return an error to the code that invoked
	 * the exec syscall.
	 */
	retval = arch_check_elf(&loc->elf_ex, !!interpreter, &arch_state);
	if (retval)
		goto out_free_dentry;

	/* Flush all traces of the currently running executable */
	retval = flush_old_exec(bprm);
	if (retval)
		goto out_free_dentry;

	/* Do this immediately, since STACK_TOP as used in setup_arg_pages
	   may depend on the personality.  */
	SET_PERSONALITY2(loc->elf_ex, &arch_state);
	if (elf_read_implies_exec(loc->elf_ex, executable_stack))
		current->personality |= READ_IMPLIES_EXEC;

	if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
		current->flags |= PF_RANDOMIZE;

	setup_new_exec(bprm);

	/* Do this so that we can load the interpreter, if need be.  We will
	   change some of these later */
	retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
				 executable_stack);
	if (retval < 0)
		goto out_free_dentry;
	
	current->mm->start_stack = bprm->p;
......

 

flush_old_exec( )를 통하여 process의 address space 변환이 이루어지고, thread group에 남아있는 thread들이 제거된다.

다음으로, task_struct의 personality를 판단한다.

그리고, setup_new_exec( ) 함수를 이용하여 방금 변환된 address space에 대한 초기화가 진행된다.

setup_arg_page( )와 randomize_stack_top( ) 함수를 통해서 stack 위치를 radom화 한다.

마지막으로 stack의 시작 위치를 mm_struct 구조체에 저장한다.

 

int flush_old_exec(struct linux_binprm * bprm)
{
	int retval;

	/*
	 * Make sure we have a private signal table and that
	 * we are unassociated from the previous thread group.
	 */
	retval = de_thread(current);
	if (retval)
		goto out;

	/*
	 * Must be called _before_ exec_mmap() as bprm->mm is
	 * not visibile until then. This also enables the update
	 * to be lockless.
	 */
	set_mm_exe_file(bprm->mm, bprm->file);

	/*
	 * Release all of the old mmap stuff
	 */
	acct_arg_size(bprm, 0);
	retval = exec_mmap(bprm->mm);
	if (retval)
		goto out;

	bprm->mm = NULL;		/* We're using it now */

	set_fs(USER_DS);
	current->flags &= ~(PF_RANDOMIZE | PF_FORKNOEXEC | PF_KTHREAD |
					PF_NOFREEZE | PF_NO_SETAFFINITY);
	flush_thread();
	current->personality &= ~bprm->per_clear;

	return 0;

out:
	return retval;
}
EXPORT_SYMBOL(flush_old_exec);

flush_old_exec( ) 함수를 진입했다면, 새로운 process를 위한 address space 교환을 하기 전에 de_thread( )함수를 호출하여 현재 thread group 내의 모든 thread를 제거한다.

set_mm_exe_file( )함수를 통하여 process의 file path를 설정한다. 즉, mm_struct->exe_file을 설정한다.

다음으로, exec_mmap( ) 함수를 이용하여 새로운 process의 주소 공간을 bprm->mm에 저장되어 있는 주소공간으로 설정한다.

flush_thread( ) 함수를 호출하여 thread_struct의 Thread Local Storage의 data를 초기화 한다.

마지막으로, flags와 personality를 설정한다. personality는 구버전의 linux혹은 다른 OS와의 호환성을 위한 정보이다.

 

/*
 * This function makes sure the current process has its own signal table,
 * so that flush_signal_handlers can later reset the handlers without
 * disturbing other processes.  (Other processes might share the signal
 * table via the CLONE_SIGHAND option to clone().)
 */
static int de_thread(struct task_struct *tsk)
{
	struct signal_struct *sig = tsk->signal;
	struct sighand_struct *oldsighand = tsk->sighand;
	spinlock_t *lock = &oldsighand->siglock;

	if (thread_group_empty(tsk))
		goto no_thread_group;

	/*
	 * Kill all other threads in the thread group.
	 */
	spin_lock_irq(lock);
	if (signal_group_exit(sig)) {
		/*
		 * Another group action in progress, just
		 * return so that the signal is processed.
		 */
		spin_unlock_irq(lock);
		return -EAGAIN;
	}

	sig->group_exit_task = tsk;
	sig->notify_count = zap_other_threads(tsk);
	if (!thread_group_leader(tsk))
		sig->notify_count--;

	while (sig->notify_count) {
		__set_current_state(TASK_KILLABLE);
		spin_unlock_irq(lock);
		schedule();
		if (unlikely(__fatal_signal_pending(tsk)))
			goto killed;
		spin_lock_irq(lock);
	}
	spin_unlock_irq(lock);

	/*
	 * At this point all other threads have exited, all we have to
	 * do is to wait for the thread group leader to become inactive,
	 * and to assume its PID:
	 */
	if (!thread_group_leader(tsk)) {
		struct task_struct *leader = tsk->group_leader;

		for (;;) {
			threadgroup_change_begin(tsk);
			write_lock_irq(&tasklist_lock);
			/*
			 * Do this under tasklist_lock to ensure that
			 * exit_notify() can't miss ->group_exit_task
			 */
			sig->notify_count = -1;
			if (likely(leader->exit_state))
				break;
			__set_current_state(TASK_KILLABLE);
			write_unlock_irq(&tasklist_lock);
			threadgroup_change_end(tsk);
			schedule();
			if (unlikely(__fatal_signal_pending(tsk)))
				goto killed;
		}

		/*
		 * The only record we have of the real-time age of a
		 * process, regardless of execs it's done, is start_time.
		 * All the past CPU time is accumulated in signal_struct
		 * from sister threads now dead.  But in this non-leader
		 * exec, nothing survives from the original leader thread,
		 * whose birth marks the true age of this process now.
		 * When we take on its identity by switching to its PID, we
		 * also take its birthdate (always earlier than our own).
		 */
		tsk->start_time = leader->start_time;
		tsk->real_start_time = leader->real_start_time;

		BUG_ON(!same_thread_group(leader, tsk));
		BUG_ON(has_group_leader_pid(tsk));
		/*
		 * An exec() starts a new thread group with the
		 * TGID of the previous thread group. Rehash the
		 * two threads with a switched PID, and release
		 * the former thread group leader:
		 */

		/* Become a process group leader with the old leader's pid.
		 * The old leader becomes a thread of the this thread group.
		 * Note: The old leader also uses this pid until release_task
		 *       is called.  Odd but simple and correct.
		 */
		tsk->pid = leader->pid;
		change_pid(tsk, PIDTYPE_PID, task_pid(leader));
		transfer_pid(leader, tsk, PIDTYPE_PGID);
		transfer_pid(leader, tsk, PIDTYPE_SID);

		list_replace_rcu(&leader->tasks, &tsk->tasks);
		list_replace_init(&leader->sibling, &tsk->sibling);

		tsk->group_leader = tsk;
		leader->group_leader = tsk;

		tsk->exit_signal = SIGCHLD;
		leader->exit_signal = -1;

		BUG_ON(leader->exit_state != EXIT_ZOMBIE);
		leader->exit_state = EXIT_DEAD;

		/*
		 * We are going to release_task()->ptrace_unlink() silently,
		 * the tracer can sleep in do_wait(). EXIT_DEAD guarantees
		 * the tracer wont't block again waiting for this thread.
		 */
		if (unlikely(leader->ptrace))
			__wake_up_parent(leader, leader->parent);
		write_unlock_irq(&tasklist_lock);
		threadgroup_change_end(tsk);

		release_task(leader);
	}

	sig->group_exit_task = NULL;
	sig->notify_count = 0;

no_thread_group:
	/* we have changed execution domain */
	tsk->exit_signal = SIGCHLD;

	exit_itimers(sig);
	flush_itimer_signals();

	if (atomic_read(&oldsighand->count) != 1) {
		struct sighand_struct *newsighand;
		/*
		 * This ->sighand is shared with the CLONE_SIGHAND
		 * but not CLONE_THREAD task, switch to the new one.
		 */
		newsighand = kmem_cache_alloc(sighand_cachep, GFP_KERNEL);
		if (!newsighand)
			return -ENOMEM;

		atomic_set(&newsighand->count, 1);
		memcpy(newsighand->action, oldsighand->action,
		       sizeof(newsighand->action));

		write_lock_irq(&tasklist_lock);
		spin_lock(&oldsighand->siglock);
		rcu_assign_pointer(tsk->sighand, newsighand);
		spin_unlock(&oldsighand->siglock);
		write_unlock_irq(&tasklist_lock);

		__cleanup_sighand(oldsighand);
	}

	BUG_ON(!thread_group_leader(tsk));
	return 0;

killed:
	/* protects against exit_notify() and __exit_signal() */
	read_lock(&tasklist_lock);
	sig->group_exit_task = NULL;
	sig->notify_count = 0;
	read_unlock(&tasklist_lock);
	return -EAGAIN;
}

가장 먼저, thread_group_empty( ) 함수를 이용하여 thread group이 비어있는지 확인한다. 만약, 비어있다면 더이상 해당 함수를 실행할 필요가 없다.

다음으로, signal_group_exit( ) 함수를 통하여 thread group 삭제가 현재 진행중인지 확인한다. 만약, 이미 진행중 이라면 더이상 해당 함수를 실행할 필요가 없다.

zap_other_threads( ) 함수를 호출하여, 현재 thread를 제와한 group 내의 다른 thread에게 SIGKILL 시그널을 전송한다.

그리고, while문을 통하여 다른 thread들이 종료되기를 대기한다.

만약, 현재 thread가 group의 leader 라면, leader가 종료될때까지 대기한다.

본 함수의 나머지 부분은 현재 task가 leader가 아닌 경우에 현재 task를 leader로 만들기 위해 구현된 부분이다.

de_thread( )가 종료되면 다시 load_elf_binary( )로 돌아와서 새로운 process에 대한 sighand_struct의 초기화를 진행한다.

 

void setup_new_exec(struct linux_binprm * bprm)
{
	arch_pick_mmap_layout(current->mm);

	/* This is the point of no return */
	current->sas_ss_sp = current->sas_ss_size = 0;

	if (uid_eq(current_euid(), current_uid()) && gid_eq(current_egid(), current_gid()))
		set_dumpable(current->mm, SUID_DUMP_USER);
	else
		set_dumpable(current->mm, suid_dumpable);

	perf_event_exec();
	__set_task_comm(current, kbasename(bprm->filename), true);

	/* Set the new mm task size. We have to do that late because it may
	 * depend on TIF_32BIT which is only updated in flush_thread() on
	 * some architectures like powerpc
	 */
	current->mm->task_size = TASK_SIZE;

	/* install the new credentials */
	if (!uid_eq(bprm->cred->uid, current_euid()) ||
	    !gid_eq(bprm->cred->gid, current_egid())) {
		current->pdeath_signal = 0;
	} else {
		would_dump(bprm, bprm->file);
		if (bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP)
			set_dumpable(current->mm, suid_dumpable);
	}

	/* An exec changes our domain. We are no longer part of the thread
	   group */
	current->self_exec_id++;
	flush_signal_handlers(current, 0);
	do_close_on_exec(current->files);
}
EXPORT_SYMBOL(setup_new_exec);

setup_new_exec( ) 함수에 진입하기 전, 이미 새로운 프로세스를 위한 mm_struct로 교환했기 때문에, 이제 mm_struct에 대한 설정 및 초기화를 진행해야 한다.

arch_pick_mmap_layout( )함수를 통해서 mmap의 시작 지점을 설정한다.

다음으로, mm의 flag bit를 설정하고, kbasename( ) 함수를 이용해서 bprm->filename path의 가장 마지막 파일 이름을 얻어온다.

그리고, __set_task_comm( ) 함수를 통하여 process의 file path 설정 및 task_struct->comm 설정을 진행한다.

flush_signal_handler( ) 함수를 호출해서 signal handler를 모두 clear한다.

마지막으로, do_close_on_exec( ) 함수를 이용해서 file을 닫는다.

 

void arch_pick_mmap_layout(struct mm_struct *mm)
{
	unsigned long random_factor = 0UL;

	if (current->flags & PF_RANDOMIZE)
		random_factor = arch_mmap_rnd();

	if (mmap_is_legacy()) {
		mm->mmap_base = TASK_UNMAPPED_BASE + random_factor;
		mm->get_unmapped_area = arch_get_unmapped_area;
	} else {
		mm->mmap_base = mmap_base(random_factor);
		mm->get_unmapped_area = arch_get_unmapped_area_topdown;
	}
}

arch_mmap_rnd( )을 통하여 linearization range의 randomize 시작 주소를 얻는다. 해당 값은 "get_random_int( ) % (1<<28)" 이다.

mm->mmap_base에 TASK_UNMAPPED_BASE 값을 더하여 시작 주소를 randomize 한다.

마지막으로, 함수 포인터 mm->get_unmapped_area에 arch_ger_unmapped_area를 저장하여, virtual address를 할당하는데 사용한다.

 

/*
 * Finalizes the stack vm_area_struct. The flags and permissions are updated,
 * the stack is optionally relocated, and some extra space is added.
 */
int setup_arg_pages(struct linux_binprm *bprm,
		    unsigned long stack_top,
		    int executable_stack)
{
	unsigned long ret;
	unsigned long stack_shift;
	struct mm_struct *mm = current->mm;
	struct vm_area_struct *vma = bprm->vma;
	struct vm_area_struct *prev = NULL;
	unsigned long vm_flags;
	unsigned long stack_base;
	unsigned long stack_size;
	unsigned long stack_expand;
	unsigned long rlim_stack;

#ifdef CONFIG_STACK_GROWSUP
	/* Limit stack size */
	stack_base = rlimit_max(RLIMIT_STACK);
	if (stack_base > STACK_SIZE_MAX)
		stack_base = STACK_SIZE_MAX;

	/* Add space for stack randomization. */
	stack_base += (STACK_RND_MASK << PAGE_SHIFT);

	/* Make sure we didn't let the argument array grow too large. */
	if (vma->vm_end - vma->vm_start > stack_base)
		return -ENOMEM;

	stack_base = PAGE_ALIGN(stack_top - stack_base);

	stack_shift = vma->vm_start - stack_base;
	mm->arg_start = bprm->p - stack_shift;
	bprm->p = vma->vm_end - stack_shift;
#else
	stack_top = arch_align_stack(stack_top);
	stack_top = PAGE_ALIGN(stack_top);

	if (unlikely(stack_top < mmap_min_addr) ||
	    unlikely(vma->vm_end - vma->vm_start >= stack_top - mmap_min_addr))
		return -ENOMEM;

	stack_shift = vma->vm_end - stack_top;

	bprm->p -= stack_shift;
	mm->arg_start = bprm->p;
#endif

	if (bprm->loader)
		bprm->loader -= stack_shift;
	bprm->exec -= stack_shift;

	down_write(&mm->mmap_sem);
	vm_flags = VM_STACK_FLAGS;

	/*
	 * Adjust stack execute permissions; explicitly enable for
	 * EXSTACK_ENABLE_X, disable for EXSTACK_DISABLE_X and leave alone
	 * (arch default) otherwise.
	 */
	if (unlikely(executable_stack == EXSTACK_ENABLE_X))
		vm_flags |= VM_EXEC;
	else if (executable_stack == EXSTACK_DISABLE_X)
		vm_flags &= ~VM_EXEC;
	vm_flags |= mm->def_flags;
	vm_flags |= VM_STACK_INCOMPLETE_SETUP;

	ret = mprotect_fixup(vma, &prev, vma->vm_start, vma->vm_end,
			vm_flags);
	if (ret)
		goto out_unlock;
	BUG_ON(prev != vma);

	/* Move stack pages down in memory. */
	if (stack_shift) {
		ret = shift_arg_pages(vma, stack_shift);
		if (ret)
			goto out_unlock;
	}

	/* mprotect_fixup is overkill to remove the temporary stack flags */
	vma->vm_flags &= ~VM_STACK_INCOMPLETE_SETUP;

	stack_expand = 131072UL; /* randomly 32*4k (or 2*64k) pages */
	stack_size = vma->vm_end - vma->vm_start;
	/*
	 * Align this down to a page boundary as expand_stack
	 * will align it up.
	 */
	rlim_stack = rlimit(RLIMIT_STACK) & PAGE_MASK;
#ifdef CONFIG_STACK_GROWSUP
	if (stack_size + stack_expand > rlim_stack)
		stack_base = vma->vm_start + rlim_stack;
	else
		stack_base = vma->vm_end + stack_expand;
#else
	if (stack_size + stack_expand > rlim_stack)
		stack_base = vma->vm_end - rlim_stack;
	else
		stack_base = vma->vm_start - stack_expand;
#endif
	current->mm->start_stack = bprm->p;
	ret = expand_stack(vma, stack_base);
	if (ret)
		ret = -EFAULT;

out_unlock:
	up_write(&mm->mmap_sem);
	return ret;
}
EXPORT_SYMBOL(setup_arg_pages);

setup_arg_pages( )의 인자 stack_top은 randomize되어 있다. 그러므로, stack_top에 대해서 page align을 진행한다.

그 다음, mm->arg_start = bprm->p - (vma_end - stack_top) 을 수행하여서 program의 시작위치를 잡아준다.

이제 남은 부분은 stack top에 맞추어서 flag bit를 설정하고, stack pointer를 설정한다. 즉, setup_arg_pages( ) 함수를 통해서 stack 맞추어 virtual address space를 수정한다.

마지막으로 ,expand_stack( ) 함수를 통해서 stack을 확장하는데 default stack size는 4개 page이다.

 

앞 부분에서 ELF Header를 이용한 초기화를 진행했고, Interpreter의 Segment Header를 로드하였다.

이제는 ELF의 PT_LOAD Segment에 헤앙하는 부분을 loading 한다.

static int load_elf_binary(struct linux_binprm *bprm)
{
......
       /* Now we do a little grungy work by mmapping the ELF image into
	   the correct location in memory. */
	for(i = 0, elf_ppnt = elf_phdata;
	    i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {
		int elf_prot = 0, elf_flags;
		unsigned long k, vaddr;
		unsigned long total_size = 0;

		if (elf_ppnt->p_type != PT_LOAD)
			continue;

		if (unlikely (elf_brk > elf_bss)) {
			unsigned long nbyte;
	            
			/* There was a PT_LOAD segment with p_memsz > p_filesz
			   before this one. Map anonymous pages, if needed,
			   and clear the area.  */
			retval = set_brk(elf_bss + load_bias,
					 elf_brk + load_bias);
			if (retval)
				goto out_free_dentry;
			nbyte = ELF_PAGEOFFSET(elf_bss);
			if (nbyte) {
				nbyte = ELF_MIN_ALIGN - nbyte;
				if (nbyte > elf_brk - elf_bss)
					nbyte = elf_brk - elf_bss;
				if (clear_user((void __user *)elf_bss +
							load_bias, nbyte)) {
					/*
					 * This bss-zeroing can fail if the ELF
					 * file specifies odd protections. So
					 * we don't check the return value
					 */
				}
			}
		}

		if (elf_ppnt->p_flags & PF_R)
			elf_prot |= PROT_READ;
		if (elf_ppnt->p_flags & PF_W)
			elf_prot |= PROT_WRITE;
		if (elf_ppnt->p_flags & PF_X)
			elf_prot |= PROT_EXEC;

		elf_flags = MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE;

		vaddr = elf_ppnt->p_vaddr;
		if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
			elf_flags |= MAP_FIXED;
		} else if (loc->elf_ex.e_type == ET_DYN) {
			/* Try and get dynamic programs out of the way of the
			 * default mmap base, as well as whatever program they
			 * might try to exec.  This is because the brk will
			 * follow the loader, and is not movable.  */
			load_bias = ELF_ET_DYN_BASE - vaddr;
			if (current->flags & PF_RANDOMIZE)
				load_bias += arch_mmap_rnd();
			load_bias = ELF_PAGESTART(load_bias);
			total_size = total_mapping_size(elf_phdata,
							loc->elf_ex.e_phnum);
			if (!total_size) {
				retval = -EINVAL;
				goto out_free_dentry;
			}
		}

		error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
				elf_prot, elf_flags, total_size);
		if (BAD_ADDR(error)) {
			retval = IS_ERR((void *)error) ?
				PTR_ERR((void*)error) : -EINVAL;
			goto out_free_dentry;
		}

		if (!load_addr_set) {
			load_addr_set = 1;
			load_addr = (elf_ppnt->p_vaddr - elf_ppnt->p_offset);
			if (loc->elf_ex.e_type == ET_DYN) {
				load_bias += error -
				             ELF_PAGESTART(load_bias + vaddr);
				load_addr += load_bias;
				reloc_func_desc = load_bias;
			}
		}
		k = elf_ppnt->p_vaddr;
		if (k < start_code)
			start_code = k;
		if (start_data < k)
			start_data = k;

		/*
		 * Check to see if the section's size will overflow the
		 * allowed task size. Note that p_filesz must always be
		 * <= p_memsz so it is only necessary to check p_memsz.
		 */
		if (BAD_ADDR(k) || elf_ppnt->p_filesz > elf_ppnt->p_memsz ||
		    elf_ppnt->p_memsz > TASK_SIZE ||
		    TASK_SIZE - elf_ppnt->p_memsz < k) {
			/* set_brk can never work. Avoid overflows. */
			retval = -EINVAL;
			goto out_free_dentry;
		}

		k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz;

		if (k > elf_bss)
			elf_bss = k;
		if ((elf_ppnt->p_flags & PF_X) && end_code < k)
			end_code = k;
		if (end_data < k)
			end_data = k;
		k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz;
		if (k > elf_brk)
			elf_brk = k;
	}

	loc->elf_ex.e_entry += load_bias;
	elf_bss += load_bias;
	elf_brk += load_bias;
	start_code += load_bias;
	end_code += load_bias;
	start_data += load_bias;
	end_data += load_bias;

	/* Calling set_brk effectively mmaps the pages that we need
	 * for the bss and break sections.  We must do this before
	 * mapping in the interpreter, to make sure it doesn't wind
	 * up getting placed where the bss needs to go.
	 */
	retval = set_brk(elf_bss, elf_brk);
	if (retval)
		goto out_free_dentry;
	if (likely(elf_bss != elf_brk) && unlikely(padzero(elf_bss))) {
		retval = -EFAULT; /* Nobody gets to see this, but.. */
		goto out_free_dentry;
	}
......

먼저 program header를 순환하면서 PT_LOAD Header를 찾는다.

segment header의 flag값을 확인하여 elf_prot flag bit를 설정한다.

만약 File 종류가 ET_EXEC라면, 고정된 장소에 메모리를 할당하기 때문에 MAP_FIXED 플래그를 설정한다.

만약 File 종류가 ET_DYN이면은 ELF_ET_DYN_BASE로 부터 random 값을 더하여 메모리를 할당하여 주고, PF_RAMDOMIZE 플래그를 설정한다.

다음으로는, elf_map( ) 함수를 통하여 virtual address 공간에 mapping을 진행한다. 만약에 첫번째 mapping이라면 ELF file 내의 load_addr 정보가 필요하고, 만약 ET_DYN 유형일 경우에는 load_bias 정보가 필요한다.

 

주소가 낮음 곳에서 부터 text, data, bss, heap 영역이 차례로 위치하고, start_code는 위로, start_data는 아래로, elf_bss는 위로, elf_code는 위로, elf_data는 위로, elf_brk는 위로 증가한다.

그러므로 적절한 시작위치를 잡아준 후에, 시작위치에 load_bias를 더해준다.

마지막으로, set_brk( ) 함수를 통하여 elf_bss부터 elf_brk의 공간까지 매모리를 할당하여 준다.

 

static unsigned long elf_map(struct file *filep, unsigned long addr,
		struct elf_phdr *eppnt, int prot, int type,
		unsigned long total_size)
{
	unsigned long map_addr;
	unsigned long size = eppnt->p_filesz + ELF_PAGEOFFSET(eppnt->p_vaddr);
	unsigned long off = eppnt->p_offset - ELF_PAGEOFFSET(eppnt->p_vaddr);
	addr = ELF_PAGESTART(addr);
	size = ELF_PAGEALIGN(size);

	/* mmap() will return -EINVAL if given a zero size, but a
	 * segment with zero filesize is perfectly valid */
	if (!size)
		return addr;

	/*
	* total_size is the size of the ELF (interpreter) image.
	* The _first_ mmap needs to know the full size, otherwise
	* randomization might put this image into an overlapping
	* position with the ELF binary image. (since size < total_size)
	* So we first map the 'big' image - and unmap the remainder at
	* the end. (which unmap is needed for ELF images with holes.)
	*/
	if (total_size) {
		total_size = ELF_PAGEALIGN(total_size);
		map_addr = vm_mmap(filep, addr, total_size, prot, type, off);
		if (!BAD_ADDR(map_addr))
			vm_munmap(map_addr+size, total_size-size);
	} else
		map_addr = vm_mmap(filep, addr, size, prot, type, off);

	return(map_addr);
}

인자로 전달되는 값들은,

filep : file 포인터

addr : mapping을 진행할 virtual address

size : file의 크기

off : file내에서의 offset

elf_map( ) 함수는 vm_mmap를 통해서 file에 표시되어 있는 만큼의 memory공간을 할당하고, 시작 주소를 반환한다.

바로 직전에는 ELF File내의 PT_LOAD 부분에 해당하는 segment에 대해서 memory mapping을 진행했다.

마찬가지로 interpreter에 대해서 elf_map( )을 통하여 mapping을 진행하고, process의 시작지점을 elf_entry에 저장한다.

 

static int load_elf_binary(struct linux_binprm *bprm)
{
......    
    if (elf_interpreter) {
		unsigned long interp_map_addr = 0;

		elf_entry = load_elf_interp(&loc->interp_elf_ex,
					    interpreter,
					    &interp_map_addr,
					    load_bias, interp_elf_phdata);
		if (!IS_ERR((void *)elf_entry)) {
			/*
			 * load_elf_interp() returns relocation
			 * adjustment
			 */
			interp_load_addr = elf_entry;
			elf_entry += loc->interp_elf_ex.e_entry;
		}
		if (BAD_ADDR(elf_entry)) {
			retval = IS_ERR((void *)elf_entry) ?
					(int)elf_entry : -EINVAL;
			goto out_free_dentry;
		}
		reloc_func_desc = interp_load_addr;

		allow_write_access(interpreter);
		fput(interpreter);
		kfree(elf_interpreter);
	} else {
		elf_entry = loc->elf_ex.e_entry;
		if (BAD_ADDR(elf_entry)) {
			retval = -EINVAL;
			goto out_free_dentry;
		}
	}

	kfree(interp_elf_phdata);
	kfree(elf_phdata);
......

 

static int load_elf_binary(struct linux_binprm *bprm)
{
......
    set_binfmt(&elf_format);

#ifdef ARCH_HAS_SETUP_ADDITIONAL_PAGES
	retval = arch_setup_additional_pages(bprm, !!elf_interpreter);
	if (retval < 0)
		goto out;
#endif /* ARCH_HAS_SETUP_ADDITIONAL_PAGES */

	install_exec_creds(bprm);
	retval = create_elf_tables(bprm, &loc->elf_ex,
			  load_addr, interp_load_addr);
	if (retval < 0)
		goto out;
	/* N.B. passed_fileno might not be initialized? */
	current->mm->end_code = end_code;
	current->mm->start_code = start_code;
	current->mm->start_data = start_data;
	current->mm->end_data = end_data;
	current->mm->start_stack = bprm->p;

	if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {
		current->mm->brk = current->mm->start_brk =
			arch_randomize_brk(current->mm);
#ifdef compat_brk_randomized
		current->brk_randomized = 1;
#endif
	}

	if (current->personality & MMAP_PAGE_ZERO) {
		/* Why this, you ask???  Well SVr4 maps page 0 as read-only,
		   and some applications "depend" upon this behavior.
		   Since we do not have the power to recompile these, we
		   emulate the SVr4 behavior. Sigh. */
		error = vm_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_EXEC,
				MAP_FIXED | MAP_PRIVATE, 0);
	}

#ifdef ELF_PLAT_INIT
	/*
	 * The ABI may specify that certain registers be set up in special
	 * ways (on i386 %edx is the address of a DT_FINI function, for
	 * example.  In addition, it may also specify (eg, PowerPC64 ELF)
	 * that the e_entry field is the address of the function descriptor
	 * for the startup routine, rather than the address of the startup
	 * routine itself.  This macro performs whatever initialization to
	 * the regs structure is required as well as any relocations to the
	 * function descriptor entries when executing dynamically links apps.
	 */
	ELF_PLAT_INIT(regs, reloc_func_desc);
#endif

	start_thread(regs, elf_entry, bprm->p);
......

set_binfmt( ) 함수를 통해서 elf_format의 내용을 mm_struct->binfmt 변수에 저장한다.

install_exec_creds( )는 새로운 process의 cred구조체를 설정한다.

create_elf_tables( )는 interpreter와 process를 실행하기 전, user space의 stack과 heap에 추가적인 정보(시작 주소 등..)를 설정한다.

다음으로는 mm_struct에 code, data, bss, heap 위치를 저장하고 PF_RANDOMIZE 플래그에 따라서 위치를 다시한번 random화 한다.

마지막으로 start_thrad( ) 함수를 통하여 ELF를 실행한다.

 

#define start_thread(regs,pc,sp)					\
({									\
	memset(regs->uregs, 0, sizeof(regs->uregs));			\
	if (current->personality & ADDR_LIMIT_32BIT)			\
		regs->ARM_cpsr = USR_MODE;				\
	else								\
		regs->ARM_cpsr = USR26_MODE;				\
	if (elf_hwcap & HWCAP_THUMB && pc & 1)				\
		regs->ARM_cpsr |= PSR_T_BIT;				\
	regs->ARM_cpsr |= PSR_ENDSTATE;					\
	regs->ARM_pc = pc & ~1;		/* pc */			\
	regs->ARM_sp = sp;		/* sp */			\
	nommu_start_thread(regs);					\
})
#define nommu_start_thread(regs) regs->ARM_r10 = current->mm->start_data

pc는 interpreter 혹은 응용 프로그램의 code부분 시작주소를 말한다. sp는 user space에서 stack pointer를 말한다.

pc register를 새로운 시작주소로 jump 한다.

 


 

참고 및 출처

 

 

[ELF] ELF Header

ELF 란 용어를 많이 들어보셨을텐데요, ELF는 Executable and Linking Format의 약어입니다. UNIX / LINUX 기반에서 사용되는 실행 및 링킹 파일 포맷입니다. 이번 글에서는 ELF 파일 포맷에 대해 알아보겠습니

sonseungha.tistory.com

 

 

sys_execv源码分析_task_set_no_new_privs函数在哪里定义_二侠的博客-CSDN博客

因为最近工作涉及到linux的反汇编,做了一阵子Java搜索引擎的工作,再回到C很不适应,因此借着看源码回忆一点linux的知识,上一篇博文分析了_dl_runtime_resolve的源码,本章从头开始研究在linux下

blog.csdn.net

 

 

5.execve()到底干了啥?_chengonghao的博客-CSDN博客

导语 很多童鞋有分析阅读Linux源代码的强烈愿望,可是Linux内核代码量庞大,大部分人不知道如何下手,以下是我分析Linux源代码的一些经验,仅供参考,有不实之处请大神指正! 1.要想阅读内核

blog.csdn.net

 

 

Credentials in Linux — The Linux Kernel documentation

Credentials in Linux By: David Howells <dhowells@redhat.com> There are several parts to the security check performed by Linux when one object acts upon another: Objects. Objects are things in the system that may be acted upon directly by userspace programs

www.kernel.org

 

 

.

'Kernel > 理解' 카테고리의 다른 글

Device Tree 문법  (0) 2024.02.02
ZRAM 분석  (0) 2024.02.02
Bootloader 개요  (0) 2024.02.02
[sched] Pressure Stall Information (PSI)  (0) 2024.02.02
[mm][fs] File/Anon + VMA/Page  (0) 2024.02.02