prepare_kernel_cred
와 commit_creds
함수는 보통 kernel exploit을 작성할 때 root 권한을 얻어오는 작업을 수행할 때 사용하는 함수이다. 이 함수들의 소스코드에 대한 분석을 진행해보았다.
prepare_kernel_cred
...
static struct kmem_cache *cred_jar;
...
/*
* initialise the credentials stuff
*/
void __init cred_init(void)
{
/* allocate a slab in which we can store credentials */
cred_jar = kmem_cache_create("cred_jar", sizeof(struct cred),
0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
}
/**
* prepare_kernel_cred - Prepare a set of credentials for a kernel service
* @daemon: A userspace daemon to be used as a reference
*
* Prepare a set of credentials for a kernel service. This can then be used to
* override a task's own credentials so that work can be done on behalf of that
* task that requires a different subjective context.
*
* @daemon is used to provide a base for the security record, but can be NULL.
* If @daemon is supplied, then the security data will be derived from that;
* otherwise they'll be set to 0 and no groups, full capabilities and no keys.
*
* The caller may change these controls afterwards if desired.
*
* Returns the new credentials or NULL if out of memory.
*
* Does not take, and does not return holding current->cred_replace_mutex.
*/
struct cred *prepare_kernel_cred(struct task_struct *daemon)
{
const struct cred *old;
struct cred *new;
new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
if (!new)
return NULL;
kdebug("prepare_kernel_cred() alloc %p", new);
if (daemon)
old = get_task_cred(daemon);
else
old = get_cred(&init_cred);
validate_creds(old);
*new = *old;
atomic_set(&new->usage, 1);
set_cred_subscribers(new, 0);
get_uid(new->user);
get_user_ns(new->user_ns);
get_group_info(new->group_info);
#ifdef CONFIG_KEYS
new->session_keyring = NULL;
new->process_keyring = NULL;
new->thread_keyring = NULL;
new->request_key_auth = NULL;
new->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
#endif
#ifdef CONFIG_SECURITY
new->security = NULL;
#endif
if (security_prepare_creds(new, old, GFP_KERNEL) < 0)
goto error;
put_cred(old);
validate_creds(new);
return new;
error:
put_cred(new);
put_cred(old);
return NULL;
}
EXPORT_SYMBOL(prepare_kernel_cred);
kernel/cred.c, line 586
에 있는 prepare_kernel_cred
함수의 코드이다.
설명 주석을 보면, 해당 함수는 커널 서비스에 대한 자격증명을 준비하는 것을 알 수 있다. 자격증명이란 커널이 발급하는 권한의 집합을 말한다. 자격증명은 커널이 직접 발급 및 관리하고, 커널 외부에서 변경되지 않을 것을 보장한다.
static struct kmem_cache *cred_jar;
/*
* initialise the credentials stuff
*/
void __init cred_init(void)
{
/* allocate a slab in which we can store credentials */
cred_jar = kmem_cache_create("cred_jar", sizeof(struct cred),
0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
}
이 부분을 설명하기 앞서, slab
는 커널이 사용하는 자료구조로, /proc/meminfo
에서 리눅스 커널이 사용하는 cache 크기를 말한다. 이러한 slab
메모리 타입중 가장 유명한 애들이 kmalloc-32
, kmalloc-64
, kmalloc-128
, kmalloc-256
... , kmalloc-2048
이다. 이렇게 kmalloc
함수를 이용해 만들어지는 kmalloc slab 캐시
를 관리하는 자료구조가 kmalloc_caches
배열 타입 전역변수이다. 그리고 이 kmalloc_caches
배열의 타입은 struct kmem_cache
구조체이다. 간단히 설명하면 슬랩 캐시를 관리하는 자료 구조가 struct kmem_cache
이다. 이 구조체에 대한 설명은 더 길어질 것 같아서 다른 자료로 빼도록 하겠다.
아무튼 이러한 kmem_cache
구조체 포인터 변수 cred_jar
가 전역변수로 선언이 되어 있고, cred_init
함수에서 이 변수에다가 자격증명을 저장할 수 있도록 slab
을 할당한다. 할당하는 함수가 kmem_cache_create
이다. 해당 함수를 통해 특정 사이즈 단위로 첫번째 인자의 이름을 가지는 slab cache를 생성할 수 있다. 만약 주어진 사이즈와 호환이 가능한 플래그를 사용한 slab cache가 이미 존재하는 경우 별도로 생성하지 않는다. 즉 cred_init
함수를 통해 slab cache를 생성하여, 후에 자격증명을 새로 생성할 때 slab cache에서 자격증명을 저장할 수 있는 객체를 얻어올 수 있다.
const struct cred *old;
struct cred *new;
new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
if (!new)
return NULL;
kdebug("prepare_kernel_cred() alloc %p", new);
먼저 원래의 자격증명을 저장하는 old
변수와 새롭게 준비될 자격증명을 저장하는 new
변수가 있다. 여기서 두 변수는 cred
구조체 포인터 타입이다. 이 cred
구조체에 대한 설명은 후에 첨부하겠다. 위에서 cred_jar
slab cache를 할당했고 플래그로 GFP_KERNEL
를 주었으므로, 커널용 메모리를 할당받게 된다.
if (daemon)
old = get_task_cred(daemon);
else
old = get_cred(&init_cred);
validate_creds(old);
그리고 prepare_kernel_cred
함수의 호출 인자가 0이 아닐 때는 해당 인자를 가지고 get_task_cred(daemon)
함수를 호출하여 리턴값을 old
에 저장하고, 호출 인자가 0인 경우에는 get_cred
함수의 인자로 init_cred
를 인자로 get_cred
함수를 호출하고 리턴값을 old
에 저장한다.
즉 if(daemon)
의 참, 거짓 여부에 따라
- true:
old
에는 전달된deamon
프로세스의 자격 증명을 저장함 - false:
old
에는init_cred
의 자격증명을 저장함
그리고 저장된 old
에 있는 자격증명이 올바른지 validate_cred
함수를 통해 확인한다.
*new = *old;
atomic_set(&new->usage, 1);
set_cred_subscribers(new, 0);
get_uid(new->user);
get_user_ns(new->user_ns);
get_group_info(new->group_info)
이제 new
변수가 가리키는 구조체에 old
의 내용을 저장한다.
그리고 atomic_set
함수를 이용해서 new->usage
값을 1로 설정한다.
set_cred_subscribers
함수를 통해 new
자격증명의 subscribers
멤버를 0으로 설정해준다.
다음 get_uid()
, get_user_ns()
, get_group_info()
함수를 통해 new
자격증명의 uid, user namespace, group info를 가져온다.
if (security_prepare_creds(new, old, GFP_KERNEL) < 0)
goto error;
put_cred(old);
validate_creds(new);
return new;
security_prepare_creds
함수를 통해 새로 만들어진 자격증명 new
로 현재 프로세스의 자격증명을 변경한다.
그리고 기존의 자격증명인 old
를 해제하고, new
자격증명의 유효성을 검사한다. 여기까지가 preapre_kernel_cred
함수이다.
해당 함수를 호출 할 때 인자로 NULL(0)
을 주면 init_cred
의 자격증명을 얻게 된다고 하였다. 그리고 0
을 주었을 때 root 권한을 얻을 수 있음을 이용하여 kernel exploit 코드를 작성할 때 사용한다.
init_cred struct
/*
* The initial credentials for the initial task
*/
struct cred init_cred = {
.usage = ATOMIC_INIT(4),
#ifdef CONFIG_DEBUG_CREDENTIALS
.subscribers = ATOMIC_INIT(2),
.magic = CRED_MAGIC,
#endif
.uid = GLOBAL_ROOT_UID,
.gid = GLOBAL_ROOT_GID,
.suid = GLOBAL_ROOT_UID,
.sgid = GLOBAL_ROOT_GID,
.euid = GLOBAL_ROOT_UID,
.egid = GLOBAL_ROOT_GID,
.fsuid = GLOBAL_ROOT_UID,
.fsgid = GLOBAL_ROOT_GID,
.securebits = SECUREBITS_DEFAULT,
.cap_inheritable = CAP_EMPTY_SET,
.cap_permitted = CAP_FULL_SET,
.cap_effective = CAP_FULL_SET,
.cap_bset = CAP_FULL_SET,
.user = INIT_USER,
.user_ns = &init_user_ns,
.group_info = &init_groups,
};
init_cred
구조체를 보면 uid, gid, suid 등 모든 권한이 다 root 권한을 가지고 있다. 따라서 prepare_kernel_cred
함수에서 인자를 0으로 주어, init_cred
자격 증명을 부여하게 되었을 때 위와 같이 root 권한을 얻을 수 있게 되는 것이다.
commit_creds
/**
* commit_creds - Install new credentials upon the current task
* @new: The credentials to be assigned
*
* Install a new set of credentials to the current task, using RCU to replace
* the old set. Both the objective and the subjective credentials pointers are
* updated. This function may not be called if the subjective credentials are
* in an overridden state.
*
* This function eats the caller's reference to the new credentials.
*
* Always returns 0 thus allowing this function to be tail-called at the end
* of, say, sys_setgid().
*/
int commit_creds(struct cred *new)
{
struct task_struct *task = current;
const struct cred *old = task->real_cred;
kdebug("commit_creds(%p{%d,%d})", new,
atomic_read(&new->usage),
read_cred_subscribers(new));
BUG_ON(task->cred != old);
#ifdef CONFIG_DEBUG_CREDENTIALS
BUG_ON(read_cred_subscribers(old) < 2);
validate_creds(old);
validate_creds(new);
#endif
BUG_ON(atomic_read(&new->usage) < 1);
get_cred(new); /* we will require a ref for the subj creds too */
/* dumpability changes */
if (!uid_eq(old->euid, new->euid) ||
!gid_eq(old->egid, new->egid) ||
!uid_eq(old->fsuid, new->fsuid) ||
!gid_eq(old->fsgid, new->fsgid) ||
!cred_cap_issubset(old, new)) {
if (task->mm)
set_dumpable(task->mm, suid_dumpable);
task->pdeath_signal = 0;
/*
* If a task drops privileges and becomes nondumpable,
* the dumpability change must become visible before
* the credential change; otherwise, a __ptrace_may_access()
* racing with this change may be able to attach to a task it
* shouldn't be able to attach to (as if the task had dropped
* privileges without becoming nondumpable).
* Pairs with a read barrier in __ptrace_may_access().
*/
smp_wmb();
}
/* alter the thread keyring */
if (!uid_eq(new->fsuid, old->fsuid))
key_fsuid_changed(new);
if (!gid_eq(new->fsgid, old->fsgid))
key_fsgid_changed(new);
/* do it
* RLIMIT_NPROC limits on user->processes have already been checked
* in set_user().
*/
alter_cred_subscribers(new, 2);
if (new->user != old->user)
atomic_inc(&new->user->processes);
rcu_assign_pointer(task->real_cred, new);
rcu_assign_pointer(task->cred, new);
if (new->user != old->user)
atomic_dec(&old->user->processes);
alter_cred_subscribers(old, -2);
/* send notifications */
if (!uid_eq(new->uid, old->uid) ||
!uid_eq(new->euid, old->euid) ||
!uid_eq(new->suid, old->suid) ||
!uid_eq(new->fsuid, old->fsuid))
proc_id_connector(task, PROC_EVENT_UID);
if (!gid_eq(new->gid, old->gid) ||
!gid_eq(new->egid, old->egid) ||
!gid_eq(new->sgid, old->sgid) ||
!gid_eq(new->fsgid, old->fsgid))
proc_id_connector(task, PROC_EVENT_GID);
/* release the old obj and subj refs both */
put_cred(old);
put_cred(old);
return 0;
}
EXPORT_SYMBOL(commit_creds);
맨 위의 주석을 보면 commit_creds
함수는 새로운 자격증명을 현재의 프로세스에 설치하는 기능을 하는 것을 알 수 있다.
struct task_struct *task = current;
const struct cred *old = task->real_cred;
kdebug("commit_creds(%p{%d,%d})", new,
atomic_read(&new->usage),
read_cred_subscribers(new));
BUG_ON(task->cred != old);
#ifdef CONFIG_DEBUG_CREDENTIALS
BUG_ON(read_cred_subscribers(old) < 2);
validate_creds(old);
validate_creds(new);
#endif
BUG_ON(atomic_read(&new->usage) < 1);
get_cred(new); /* we will require a ref for the subj creds too */
current
는 현재 프로세스를 의미한다.
old
에 현재 프로세스의 자격증명을 저장해둔다. 일반적으로 cred
와 real_cred
는 같다고 보면 된다.
그리고 BUG_ON(testk->cred != old)
를 통해 현재 프로세스의 자격증명과 직전에 old
에 저장해둔 자격증명이 같은지 확인한다. BUG_ON
는 매크로 내의 조건이 1이면 심각한 오류 상태 시 실행하는 BUG
매크로를 실행한다.
다음은 new->usage
값이 1보다 작은지 검사한다. 하지만 이 부분은 이전의 prepare_kernel_cred
함수에서 1로 설정을 했으므로 BUG
가 실행 될 일음 없다.
그리고 get_cred(new)
함수를 통해 new
의 자격증명 정보를 가져온다.
/* dumpability changes */
if (!uid_eq(old->euid, new->euid) ||
!gid_eq(old->egid, new->egid) ||
!uid_eq(old->fsuid, new->fsuid) ||
!gid_eq(old->fsgid, new->fsgid) ||
!cred_cap_issubset(old, new)) {
if (task->mm)
set_dumpable(task->mm, suid_dumpable);
task->pdeath_signal = 0;
/*
* If a task drops privileges and becomes nondumpable,
* the dumpability change must become visible before
* the credential change; otherwise, a __ptrace_may_access()
* racing with this change may be able to attach to a task it
* shouldn't be able to attach to (as if the task had dropped
* privileges without becoming nondumpable).
* Pairs with a read barrier in __ptrace_may_access().
*/
smp_wmb();
}
uid_eq, gid_eq
는 각각 인자로 주어진 두개의 euid, egid
값을 비교하는 함수이다.
static inline bool uid_eq(kuid_t left, kuid_t right)
{
return __kuid_val(left) == __kuid_val(right);
}
static inline bool gid_eq(kgid_t left, kgid_t right)
{
return __kgid_val(left) == __kgid_val(right);
}
같으면 1, 다르면 0을 리턴한다. euid, egid
는 effective가 붙어서, 유효 사용자 식별자라는 의미로 프로세스가 파일에 대해 가지는 권한을 말한다. fsuid
는 파일 시스템 사용자 아이디를 의미하며, 파일 시스템 접근 제어 용도로 사용된다.
그리고 cred_cap_issubset
함수를 통해 두개의 자격증명이 동일한 사용자 공간에 존재하는지 확인한다.
이 if 문에서 new
와 old
가 같다면, 내부로 들어가지 않을 것이다. 익스플로잇을 작성할 때는 둘이 항상 다른 경우이기 때문에 내부는 자세히 보지 않겠다.
/* alter the thread keyring */
if (!uid_eq(new->fsuid, old->fsuid))
key_fsuid_changed(new);
if (!gid_eq(new->fsgid, old->fsgid))
key_fsgid_changed(new);
또 uid_eq, gid_eq
로 new->fsuid, old->fsuid, new->fsgid, old->fsgid
를 검사한다. 비교한 두개의 값이 다르다면 fsuid, fsgid
값을 변경해주는 key_fsuid_changed
함수를 통해 new->fsuid, new->fsgid
로 바꾸어준다.
/* do it
* RLIMIT_NPROC limits on user->processes have already been checked
* in set_user().
*/
alter_cred_subscribers(new, 2);
if (new->user != old->user)
atomic_inc(&new->user->processes);
rcu_assign_pointer(task->real_cred, new);
rcu_assign_pointer(task->cred, new);
if (new->user != old->user)
atomic_dec(&old->user->processes);
alter_cred_subscribers(old, -2);
/* send notifications */
if (!uid_eq(new->uid, old->uid) ||
!uid_eq(new->euid, old->euid) ||
!uid_eq(new->suid, old->suid) ||
!uid_eq(new->fsuid, old->fsuid))
proc_id_connector(task, PROC_EVENT_UID);
if (!gid_eq(new->gid, old->gid) ||
!gid_eq(new->egid, old->egid) ||
!gid_eq(new->sgid, old->sgid) ||
!gid_eq(new->fsgid, old->fsgid))
proc_id_connector(task, PROC_EVENT_GID);
/* release the old obj and subj refs both */
put_cred(old);
put_cred(old);
return 0;
alter_cred_subscribers
함수는 첫번째 인자의 subscribers
변수에 두번째 인자만큼을 더하는 함수이다. 따라서 여기서는 new->subscribers
변수에 2를 더하게 된다.
rcu_assign_pointer
함수를 이용해서 task->rear_cred, task->cred
에 new
자격증명을 저장한다. 그리고 다시 alter_cred_subscribers
함수를 이용해서 new->subscribers
변수에 -2를 더한다.
그 다음 두개의 if 문에서는 또 uid_eq
와 gid_eq
를 이용해서 확인하고, 주석을 보면 proc_id_connector
함수를 이용해서 노티를 보내는 것 같다.
마지막으로 old
자격증명을 해제하고 해당 함수는 끝난다.
cred struct
/*
* The security context of a task
*
* The parts of the context break down into two categories:
*
* (1) The objective context of a task. These parts are used when some other
* task is attempting to affect this one.
*
* (2) The subjective context. These details are used when the task is acting
* upon another object, be that a file, a task, a key or whatever.
*
* Note that some members of this structure belong to both categories - the
* LSM security pointer for instance.
*
* A task has two security pointers. task->real_cred points to the objective
* context that defines that task's actual details. The objective part of this
* context is used whenever that task is acted upon.
*
* task->cred points to the subjective context that defines the details of how
* that task is going to act upon another object. This may be overridden
* temporarily to point to another security context, but normally points to the
* same context as task->real_cred.
*/
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
/* RCU deletion */
union {
int non_rcu; /* Can we skip RCU deletion? */
struct rcu_head rcu; /* RCU deletion hook */
};
} __randomize_layout;
/include/linux/cred.h
헤더에 정의되어 있는 cred
구조체이다.