Home
System Hacking
🧦

msgget, msgsnd, msgrcv 분석

Type
운영체제
날짜
2025/11/14
종류
Kernel
msg
1 more property
앞에서 System VMessage Queue의 예제와 사용법에 대해 알아보았습니다. 이제 Kernel에서 어떤 방식으로 실행되는지 알아보겠습니다. kernel versionv6.10.12 버전으로 분석을 진행합니다.

msq_obtain_object 분석

아래의 글을 읽고 진행하면 이해가 더 쉽습니다.

msgget libc code

libc는 아래와 같이 syscallmsgget을 호출합니다.
int msgget (key_t key, int msgflg) { #ifdef __ASSUME_DIRECT_SYSVIPC_SYSCALLS return INLINE_SYSCALL_CALL (msgget, key, msgflg); #else return INLINE_SYSCALL_CALL (ipc, IPCOP_msgget, key, msgflg, 0, NULL); #endif }
C
복사

msgget kernel code

msgget가 호출되면, 실제로는 ksys_msgget가 호출됩니다.
SYSCALL_DEFINE2(msgget, key_t, key, int, msgflg) { return ksys_msgget(key, msgflg); }
C
복사

ksys_msget

일단 사용하는 구조체부터 하나씩 보겠습니다.
long ksys_msgget(key_t key, int msgflg) { struct ipc_namespace *ns; static const struct ipc_ops msg_ops = { .getnew = newque, .associate = security_msg_queue_associate, }; struct ipc_params msg_params; ns = current->nsproxy->ipc_ns; msg_params.key = key; msg_params.flg = msgflg; return ipcget(ns, &msg_ids(ns), &msg_ops, &msg_params); } SYSCALL_DEFINE2(msgget, key_t, key, int, msgflg) { return ksys_msgget(key, msgflg); }
C
복사
key_t key
부호있는 32bit 정수형입니다.
struct ipc_params
keyflg를 가지고 있는 ipc에 파라미터를 전달하는 구조체입니다.
struct ipc_params { key_t key; int flg; union { size_t size; /* for shared memories */ int nsems; /* for semaphores */ } u; /* holds the getnew() specific param */ };
C
복사

ipcget

params→key를 기준으로 새롭게 IPC를 생성할 지 기존 IPC를 사용할 지 선택합니다.
지금 분석에서는 ipcget_new 함수로 분석해보겠습니다.
/** * ipcget - Common sys_*get() code * @ns: namespace * @ids: ipc identifier set * @ops: operations to be called on ipc object creation, permission checks * and further checks * @params: the parameters needed by the previous operations. * * Common routine called by sys_msgget(), sys_semget() and sys_shmget(). */ int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids, const struct ipc_ops *ops, struct ipc_params *params) { if (params->key == IPC_PRIVATE) return ipcget_new(ns, ids, ops, params); else return ipcget_public(ns, ids, ops, params); }
C
복사

ipcget_new

/** * ipcget_new - create a new ipc object * @ns: ipc namespace * @ids: ipc identifier set * @ops: the actual creation routine to call * @params: its parameters * * This routine is called by sys_msgget, sys_semget() and sys_shmget() * when the key is IPC_PRIVATE. */ static int ipcget_new(struct ipc_namespace *ns, struct ipc_ids *ids, const struct ipc_ops *ops, struct ipc_params *params) { int err; down_write(&ids->rwsem); err = ops->getnew(ns, params); up_write(&ids->rwsem); return err; }
C
복사
해당 코드를 이해하기 전에 세마포어 관련해 알아야하는 함수가 존재합니다.
1.
down_read
read lock 획득
2.
up_read
read lock 해제
3.
down_write
write lock 획득
4.
up_write
write lock 해제
즉 해당 코드를 요약하면 write lock 획득을 한 뒤, 연산을 수행하고 write lock을 해제합니다.
ops→getnew를 분석하기 위해서, 먼저 ksys_msget 함수를 보면 아래와 같이 ops→getnewnewque로 설정한 것을 볼 수 있습니다.
long ksys_msgget(key_t key, int msgflg) { ... static const struct ipc_ops msg_ops = { .getnew = newque, .associate = security_msg_queue_associate, }; ... return ipcget(ns, &msg_ids(ns), &msg_ops, &msg_params); } int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids, const struct ipc_ops *ops, struct ipc_params *params) { ... return ipcget_new(ns, ids, ops, params); ... } static int ipcget_new(struct ipc_namespace *ns, struct ipc_ids *ids, const struct ipc_ops *ops, struct ipc_params *params) { ... err = ops->getnew(ns, params); ... }
C
복사

newque

newque 함수를 보면 kmalloc 함수를 이용하여 msg_queue할당합니다.
struct msg_queue
struct msg_queue { struct kern_ipc_perm q_perm; time64_t q_stime; /* last msgsnd time */ time64_t q_rtime; /* last msgrcv time */ time64_t q_ctime; /* last change time */ unsigned long q_cbytes; /* current number of bytes on queue */ unsigned long q_qnum; /* number of messages in queue */ unsigned long q_qbytes; /* max number of bytes on queue */ struct pid *q_lspid; /* pid of last msgsnd */ struct pid *q_lrpid; /* last receive pid */ struct list_head q_messages; struct list_head q_receivers; struct list_head q_senders; } __randomize_layout; struct kern_ipc_perm { spinlock_t lock; bool deleted; int id; key_t key; kuid_t uid; kgid_t gid; kuid_t cuid; kgid_t cgid; umode_t mode; unsigned long seq; void *security; struct rhash_head khtnode; struct rcu_head rcu; refcount_t refcount; } ____cacheline_aligned_in_smp __randomize_layout;
C
복사
msq를 할당한 뒤 해당 큐에 대한 초기화를 진행하고, 해당 Message QueueIDreturn 합니다.
/** * newque - Create a new msg queue * @ns: namespace * @params: ptr to the structure that contains the key and msgflg * * Called with msg_ids.rwsem held (writer) */ static int newque(struct ipc_namespace *ns, struct ipc_params *params) { struct msg_queue *msq; int retval; key_t key = params->key; int msgflg = params->flg; msq = kmalloc(sizeof(*msq), GFP_KERNEL_ACCOUNT); if (unlikely(!msq)) return -ENOMEM; msq->q_perm.mode = msgflg & S_IRWXUGO; msq->q_perm.key = key; msq->q_perm.security = NULL; retval = security_msg_queue_alloc(&msq->q_perm); if (retval) { kfree(msq); return retval; } msq->q_stime = msq->q_rtime = 0; msq->q_ctime = ktime_get_real_seconds(); msq->q_cbytes = msq->q_qnum = 0; msq->q_qbytes = ns->msg_ctlmnb; msq->q_lspid = msq->q_lrpid = NULL; INIT_LIST_HEAD(&msq->q_messages); INIT_LIST_HEAD(&msq->q_receivers); INIT_LIST_HEAD(&msq->q_senders); /* ipc_addid() locks msq upon success. */ retval = ipc_addid(&msg_ids(ns), &msq->q_perm, ns->msg_ctlmni); if (retval < 0) { ipc_rcu_putref(&msq->q_perm, msg_rcu_free); return retval; } ipc_unlock_object(&msq->q_perm); rcu_read_unlock(); return msq->q_perm.id; }
C
복사
즉 여기까지 정리를 해보면 msgget 함수를 통해서 syscall을 호출하면, kernelkmalloc을 통해서 Message Queue를 할당하고 초기화를 진행합니다.
securify_msg_queue_alloc 함수를 통해 struct kern_ipc_perm msg→q_perm→security를 할당받고 ipc_addid함수를 통해 Message QueueIPC radix tree에 등록합니다.
이렇게 되면 Message Queue id를 통해서 Message Queue IPC에서 radix tree를 탐색하여 msg_queue의 객체를 찾아올 수 있기 때문입니다.
그 후 정상적으로 설정이 되었다면 해당 Message Queueid를 반환하거나 그렇지 않을 경우 오류를 반환합니다.

전체적인 msgget call stadck

1. ksys_msgget() 2. ipcget() 3. ipcget_new() 4. newque()
C
복사

msgsnd libc code

Message Queue메시지 전송
int __libc_msgsnd (int msqid, const void *msgp, size_t msgsz, int msgflg) { #ifdef __ASSUME_DIRECT_SYSVIPC_SYSCALLS return SYSCALL_CANCEL (msgsnd, msqid, msgp, msgsz, msgflg); #else return SYSCALL_CANCEL (ipc, IPCOP_msgsnd, msqid, msgsz, msgflg, msgp); #endif } weak_alias (__libc_msgsnd, msgsnd)
C
복사

msgsnd kernel code

msgsnd가 호출되면, ksys_msgsnd가 호출됩니다.
SYSCALL_DEFINE4(msgsnd, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz, int, msgflg) { return ksys_msgsnd(msqid, msgp, msgsz, msgflg); }
C
복사

ksys_msgsnd

mtype의 값을 get_user 함수를 통해서 user → kernel 공간으로 복사하고 do_msgsnd 함수를 호출합니다.
long ksys_msgsnd(int msqid, struct msgbuf __user *msgp, size_t msgsz, int msgflg) { long mtype; if (get_user(mtype, &msgp->mtype)) return -EFAULT; return do_msgsnd(msqid, mtype, msgp->mtext, msgsz, msgflg); }
C
복사

struct msgbuf

실제 msgbufmtext의 들어올 데이터 크기를 알 수 없어 배열형태로 선언되어 있습니다.
struct msgbuf { __kernel_long_t mtype; /* type of message */ char mtext[1]; /* message text */ };
C
복사

do_msgsnd

아래의 함수 전체를 보기는 너무 길어 조금씩 끊어서 보겠습니다.
static long do_msgsnd(int msqid, long mtype, void __user *mtext, size_t msgsz, int msgflg) { struct msg_queue *msq; struct msg_msg *msg; int err; struct ipc_namespace *ns; DEFINE_WAKE_Q(wake_q); ns = current->nsproxy->ipc_ns; if (msgsz > ns->msg_ctlmax || (long) msgsz < 0 || msqid < 0) return -EINVAL; if (mtype < 1) return -EINVAL; msg = load_msg(mtext, msgsz); if (IS_ERR(msg)) return PTR_ERR(msg); msg->m_type = mtype; msg->m_ts = msgsz; rcu_read_lock(); msq = msq_obtain_object_check(ns, msqid); if (IS_ERR(msq)) { err = PTR_ERR(msq); goto out_unlock1; } ipc_lock_object(&msq->q_perm); for (;;) { struct msg_sender s; err = -EACCES; if (ipcperms(ns, &msq->q_perm, S_IWUGO)) goto out_unlock0; /* raced with RMID? */ if (!ipc_valid_object(&msq->q_perm)) { err = -EIDRM; goto out_unlock0; } err = security_msg_queue_msgsnd(&msq->q_perm, msg, msgflg); if (err) goto out_unlock0; if (msg_fits_inqueue(msq, msgsz)) break; /* queue full, wait: */ if (msgflg & IPC_NOWAIT) { err = -EAGAIN; goto out_unlock0; } /* enqueue the sender and prepare to block */ ss_add(msq, &s, msgsz); if (!ipc_rcu_getref(&msq->q_perm)) { err = -EIDRM; goto out_unlock0; } ipc_unlock_object(&msq->q_perm); rcu_read_unlock(); schedule(); rcu_read_lock(); ipc_lock_object(&msq->q_perm); ipc_rcu_putref(&msq->q_perm, msg_rcu_free); /* raced with RMID? */ if (!ipc_valid_object(&msq->q_perm)) { err = -EIDRM; goto out_unlock0; } ss_del(&s); if (signal_pending(current)) { err = -ERESTARTNOHAND; goto out_unlock0; } } ipc_update_pid(&msq->q_lspid, task_tgid(current)); msq->q_stime = ktime_get_real_seconds(); if (!pipelined_send(msq, msg, &wake_q)) { /* no one is waiting for this message, enqueue it */ list_add_tail(&msg->m_list, &msq->q_messages); msq->q_cbytes += msgsz; msq->q_qnum++; percpu_counter_add_local(&ns->percpu_msg_bytes, msgsz); percpu_counter_add_local(&ns->percpu_msg_hdrs, 1); } err = 0; msg = NULL; out_unlock0: ipc_unlock_object(&msq->q_perm); wake_up_q(&wake_q); out_unlock1: rcu_read_unlock(); if (msg != NULL) free_msg(msg); return err; }
C
복사
struct msg_msgload_msg 함수를 통해서 받아옵니다.
static long do_msgsnd(int msqid, long mtype, void __user *mtext, size_t msgsz, int msgflg) { ... struct msg_msg *msg; msg = load_msg(mtext, msgsz); ... }
C
복사

struct msg_msg

먼저 msg_msg 구조체를 보면 아래와 같습니다.
struct msg_msg { struct list_head m_list; long m_type; size_t m_ts; /* message text size */ struct msg_msgseg *next; void *security; /* the actual message follows immediately */ }; struct list_head { struct list_head *next, *prev; }; struct msg_msgseg { struct msg_msgseg *next; /* the next part of the message follows immediately */ };
C
복사

load_msg 일부

일단 먼저 alloc_msg를 먼저 보겠습니다.
struct msg_msg *load_msg(const void __user *src, size_t len) { struct msg_msg *msg; struct msg_msgseg *seg; int err = -EFAULT; size_t alen; msg = alloc_msg(len); }
C
복사

alloc_msg

코드를 먼저 한번 보겠습니다.
#define DATALEN_MSG ((size_t)PAGE_SIZE-sizeof(struct msg_msg)) #define DATALEN_SEG ((size_t)PAGE_SIZE-sizeof(struct msg_msgseg)) static struct msg_msg *alloc_msg(size_t len) { struct msg_msg *msg; struct msg_msgseg **pseg; size_t alen; alen = min(len, DATALEN_MSG); msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT); if (msg == NULL) return NULL; msg->next = NULL; msg->security = NULL; len -= alen; pseg = &msg->next; while (len > 0) { struct msg_msgseg *seg; cond_resched(); alen = min(len, DATALEN_SEG); seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT); if (seg == NULL) goto out_err; *pseg = seg; seg->next = NULL; pseg = &seg->next; len -= alen; } return msg; out_err: free_msg(msg); return NULL; }
C
복사
프로세스를 정리하면 아래와 같습니다.
1.
alenlenDATALEN_MSG 중 더 작은 값을 반환합니다
버퍼 크기(DATALEN_MSG)보다 큰 데이터가 올 수 있으니, min 함수를 통해서 처리할 수 있는 최소 단위로 쪼갭니다.
2.
sizeof(*msg) + alen의 크기를 kmalloc으로 할당받습니다.
최소 sizeof(*msg) = 0x30, 최대 PAGE_SIZE - 0x30의 크기를 할당받을 수 있습니다.
3.
alen으로 자른 뒤 남은 데이터가 있다면, msg→next를 설정합니다.
최소 sizeof(*seg) = 0x8, 최대 PAGE_SIZE - 0x8의 크기를 할당받을 수 있습니다.
4.
최종적으로 kmalloc으로 할당한 msgreturn 합니다.
Exploit 관점에서 볼 경우 Heap 위치를 가지고 있어, heap leak을 하는데 사용할 수 있습니다.

load_msg

alloc_msg로 부터 할당받은 msg 객체에 user space에서 kernel space로 데이터를 복사합니다.
DATALEN_MSG를 넘어가는 경우 남은 데이터는 seg를 참조해서 데이터를 복사합니다.
struct msg_msg *load_msg(const void __user *src, size_t len) { struct msg_msg *msg; struct msg_msgseg *seg; int err = -EFAULT; size_t alen; msg = alloc_msg(len); if (msg == NULL) return ERR_PTR(-ENOMEM); alen = min(len, DATALEN_MSG); if (copy_from_user(msg + 1, src, alen)) goto out_err; for (seg = msg->next; seg != NULL; seg = seg->next) { len -= alen; src = (char __user *)src + alen; alen = min(len, DATALEN_SEG); if (copy_from_user(seg + 1, src, alen)) goto out_err; } err = security_msg_msg_alloc(msg); if (err) goto out_err; return msg; out_err: free_msg(msg); return ERR_PTR(err); }
C
복사
여기서 copy_from_user에서 msg + 1, seg + 1 부분에 데이터를 복사합니다.
실제 위에서 본 msg_msg 구조체의 경우 아래와 같습니다.
struct msg_msg { struct list_head m_list; long m_type; size_t m_ts; /* message text size */ struct msg_msgseg *next; void *security; /* the actual message follows immediately */ };
C
복사
하지만 kernel에서 msg_msg 구조체를 사용할 때는 아래와 같이 사용합니다.
datapage최대 크기만큼 들어갈 수 있고 위에서 보았듯 page최대 크기를 넘어가는 경우 msg_msgseg *nextdata가 추가적으로 들어갑니다.
struct msg_msg { struct list_head m_list; long m_type; size_t m_ts; /* message text size */ struct msg_msgseg *next; void *security; char data[] };
C
복사
마지막으로 정리하면 load_msg 함수는 user space로 부터 데이터를 받아 msg_msg 구조체에 데이터를 chain 해서 넣어준 뒤 heap에 할당된 msgreturn 합니다.

do_msgsnd

다시 do_msgsnd 함수로 돌아와 load_msg 이후 return되는 값을 정리하면, kmalloc으로 user space로 부터 받아온 데이터가 msg 구조체에 맞게 들어가 있습니다.
static long do_msgsnd(int msqid, long mtype, void __user *mtext, size_t msgsz, int msgflg) { ... msg = load_msg(mtext, msgsz); if (IS_ERR(msg)) return PTR_ERR(msg); msg->m_type = mtype; msg->m_ts = msgsz; ... }
C
복사
이후 idMessage Queue를 찾고 권한을 확인 한 뒤 Queue Lock을 설정합니다.
static long do_msgsnd(int msqid, long mtype, void __user *mtext, size_t msgsz, int msgflg) { ... rcu_read_lock(); msq = msq_obtain_object_check(ns, msqid); if (IS_ERR(msq)) { err = PTR_ERR(msq); goto out_unlock1; } ipc_lock_object(&msq->q_perm); ... }
C
복사

struct msg_sender

ipc 권한유효성 점검을 진행한 뒤 최종적으로 MessageQueue에 삽입합니다. ipcradix treeinsert 하는 부분은 다음에 시간이 되면 글로 다시 한번 정리해보겠습니다.
struct msg_sender { struct list_head list; struct task_struct *tsk; size_t msgsz; }; static long do_msgsnd(int msqid, long mtype, void __user *mtext, size_t msgsz, int msgflg) { ... for (;;) { struct msg_sender s; err = -EACCES; if (ipcperms(ns, &msq->q_perm, S_IWUGO)) goto out_unlock0; /* raced with RMID? */ if (!ipc_valid_object(&msq->q_perm)) { err = -EIDRM; goto out_unlock0; } ... }
C
복사

do_msgsnd 전체 코드

static long do_msgsnd(int msqid, long mtype, void __user *mtext, size_t msgsz, int msgflg) { struct msg_queue *msq; struct msg_msg *msg; int err; struct ipc_namespace *ns; DEFINE_WAKE_Q(wake_q); ns = current->nsproxy->ipc_ns; if (msgsz > ns->msg_ctlmax || (long) msgsz < 0 || msqid < 0) return -EINVAL; if (mtype < 1) return -EINVAL; msg = load_msg(mtext, msgsz); if (IS_ERR(msg)) return PTR_ERR(msg); msg->m_type = mtype; msg->m_ts = msgsz; rcu_read_lock(); msq = msq_obtain_object_check(ns, msqid); if (IS_ERR(msq)) { err = PTR_ERR(msq); goto out_unlock1; } ipc_lock_object(&msq->q_perm); for (;;) { struct msg_sender s; err = -EACCES; if (ipcperms(ns, &msq->q_perm, S_IWUGO)) goto out_unlock0; /* raced with RMID? */ if (!ipc_valid_object(&msq->q_perm)) { err = -EIDRM; goto out_unlock0; } err = security_msg_queue_msgsnd(&msq->q_perm, msg, msgflg); if (err) goto out_unlock0; if (msg_fits_inqueue(msq, msgsz)) break; /* queue full, wait: */ if (msgflg & IPC_NOWAIT) { err = -EAGAIN; goto out_unlock0; } /* enqueue the sender and prepare to block */ ss_add(msq, &s, msgsz); if (!ipc_rcu_getref(&msq->q_perm)) { err = -EIDRM; goto out_unlock0; } ipc_unlock_object(&msq->q_perm); rcu_read_unlock(); schedule(); rcu_read_lock(); ipc_lock_object(&msq->q_perm); ipc_rcu_putref(&msq->q_perm, msg_rcu_free); /* raced with RMID? */ if (!ipc_valid_object(&msq->q_perm)) { err = -EIDRM; goto out_unlock0; } ss_del(&s); if (signal_pending(current)) { err = -ERESTARTNOHAND; goto out_unlock0; } } ipc_update_pid(&msq->q_lspid, task_tgid(current)); msq->q_stime = ktime_get_real_seconds(); if (!pipelined_send(msq, msg, &wake_q)) { /* no one is waiting for this message, enqueue it */ list_add_tail(&msg->m_list, &msq->q_messages); msq->q_cbytes += msgsz; msq->q_qnum++; percpu_counter_add_local(&ns->percpu_msg_bytes, msgsz); percpu_counter_add_local(&ns->percpu_msg_hdrs, 1); } err = 0; msg = NULL; out_unlock0: ipc_unlock_object(&msq->q_perm); wake_up_q(&wake_q); out_unlock1: rcu_read_unlock(); if (msg != NULL) free_msg(msg); return err; }
C
복사

전체적인 msgsnd call stack

1. ksys_msgsnd() 2. do_msgsnd() 3. load_msg() 4. alloc_msg()
C
복사
msgsnd를 호출하면 user space의 데이터를 복사해서 kmalloc으로 할당받은 heap에 복사합니다.
여기서 원하는 크기의 heap을 할당받을 수 있으며, heap 주소도 구조체에 포함된다는 것을 알 수 있습니다.

msgrcv libc code

Message Queue에 존재하는 메시지 수신
ssize_t __libc_msgrcv (int msqid, void *msgp, size_t msgsz, long int msgtyp, int msgflg) { #ifdef __ASSUME_DIRECT_SYSVIPC_SYSCALLS return SYSCALL_CANCEL (msgrcv, msqid, msgp, msgsz, msgtyp, msgflg); #else return SYSCALL_CANCEL (ipc, IPCOP_msgrcv, msqid, msgsz, msgflg, MSGRCV_ARGS (msgp, msgtyp)); #endif } weak_alias (__libc_msgrcv, msgrcv)
C
복사

msgrcv kernel code

msgrcvksys_msgrcv를 호출합니다.
SYSCALL_DEFINE5(msgrcv, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz, long, msgtyp, int, msgflg) { return ksys_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg); }
C
복사

ksys_msgrcv

do_msgrcv로 전달됩니다. do_msgrcv 함수를 보겠습니다.
long ksys_msgrcv(int msqid, struct msgbuf __user *msgp, size_t msgsz, long msgtyp, int msgflg) { return do_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg, do_msg_fill); }
C
복사

do_msgrcv

함수가 길지만 하나하나 분석해보겠습니다.
Code

do_msgrcv preview

ns에 해당 프로세스에서 사용하는 ipc_namespace를 받아 온 뒤, msqidbufsz에 대한 valid check를 수행합니다.
static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg, long (*msg_handler)(void __user *, struct msg_msg *, size_t)) { int mode; struct msg_queue *msq; struct ipc_namespace *ns; struct msg_msg *msg, *copy = NULL; DEFINE_WAKE_Q(wake_q); ns = current->nsproxy->ipc_ns; if (msqid < 0 || (long) bufsz < 0) return -EINVAL; ... }
C
복사
다음 msgflgMSG_COPY flag가 설정되어 있다면, prepare_copystruct msg_msg copymsg의 복사본을 반환합니다.
if (msgflg & MSG_COPY) { if ((msgflg & MSG_EXCEPT) || !(msgflg & IPC_NOWAIT)) return -EINVAL; copy = prepare_copy(buf, min_t(size_t, bufsz, ns->msg_ctlmax)); if (IS_ERR(copy)) return PTR_ERR(copy); }
C
복사

prepare_copy

load_msg 함수를 이용하여 msg복사한 뒤 반환합니다.
static inline struct msg_msg *prepare_copy(void __user *buf, size_t bufsz) { struct msg_msg *copy; /* * Create dummy message to copy real message to. */ copy = load_msg(buf, bufsz); if (!IS_ERR(copy)) copy->m_ts = bufsz; return copy; }
C
복사
여기서 왜 굳이 load_msgbuf에 있는 user space 데이터kernel space 데이터에 복사해서 넣어주나 했는데 kernel에 있는 데이터를 유출할 수 있기 때문에 load_msgbuf에 있는 데이터를 kernel로 다 덮어서 쓴 뒤 msg_msg 객체를 return 한다고 한다.

convert_mode

다음으로 do_msgrcv 함수에서 convert_mode 함수를 호출합니다.
static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg, long (*msg_handler)(void __user *, struct msg_msg *, size_t)) { ... mode = convert_mode(&msgtyp, msgflg); ... }
C
복사
해당 함수는 msgtypmsgflag를 해석해, 어떻게 msg를 찾을지를 설정합니다.
static inline int convert_mode(long *msgtyp, int msgflg) { if (msgflg & MSG_COPY) return SEARCH_NUMBER; /* * find message of correct type. * msgtyp = 0 => get first. * msgtyp > 0 => get first message of matching type. * msgtyp < 0 => get message with least type must be < abs(msgtype). */ if (*msgtyp == 0) return SEARCH_ANY; if (*msgtyp < 0) { if (*msgtyp == LONG_MIN) /* -LONG_MIN is undefined */ *msgtyp = LONG_MAX; else *msgtyp = -*msgtyp; return SEARCH_LESSEQUAL; } if (msgflg & MSG_EXCEPT) return SEARCH_NOTEQUAL; return SEARCH_EQUAL; }
C
복사
msq_obtain_object_check 함수를 통해 msg_queue를 가져옵니다.
static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg, long (*msg_handler)(void __user *, struct msg_msg *, size_t)) { ... msq = msq_obtain_object_check(ns, msqid); if (IS_ERR(msq)) { rcu_read_unlock(); free_copy(copy); return PTR_ERR(msq); ... }
C
복사
해당 for문의 길이가 길어 일부분만 분석을 진행하겠습니다.
for (;;) { struct msg_receiver msr_d; ... msg = find_msg(msq, &msgtyp, mode); } ...
C
복사
find_msg 함수를 통해 msg를 가져옵니다.

find_msg

static struct msg_msg *find_msg(struct msg_queue *msq, long *msgtyp, int mode) { struct msg_msg *msg, *found = NULL; long count = 0; list_for_each_entry(msg, &msq->q_messages, m_list) { if (testmsg(msg, *msgtyp, mode) && !security_msg_queue_msgrcv(&msq->q_perm, msg, current, *msgtyp, mode)) { if (mode == SEARCH_LESSEQUAL && msg->m_type != 1) { *msgtyp = msg->m_type - 1; found = msg; } else if (mode == SEARCH_NUMBER) { if (*msgtyp == count) return msg; } else return msg; count++; } } return found ?: ERR_PTR(-EAGAIN); }
C
복사
1.
list_for_each_entry Message Queue에 저장된 모든 msg를 순회합니다.
2.
testmsgmsg→m_typemode를 기반으로 조건에 맞는지 검사합니다.
3.
조건에 맞는 msg를 찾았다면 return 합니다
다음으로 찾은 msg를 인자로 msg_handler 함수를 호출합니다.
for (;;) { struct msg_receiver msr_d; ... msg = find_msg(msq, &msgtyp, mode); } ... bufsz = msg_handler(buf, msg, bufsz); free_msg(msg); return bufsz;
C
복사
msg_handlerdo_msgrcv 함수가 인자로 받는 함수 포인터입니다.
long ksys_msgrcv(int msqid, struct msgbuf __user *msgp, size_t msgsz, long msgtyp, int msgflg) { return do_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg, do_msg_fill); } static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg, long (*msg_handler)(void __user *, struct msg_msg *, size_t)) { ... }
C
복사

do_msg_fill preview

일단 간단하게 보고 넘어가면 kernel에서 수신한 msguser space으로 복사하는 함수입니다.

store_msg

함수를 이해하기 위해서 store_msg 함수 먼저 보겠습니다.
user space의 dest으로 msgdata를 복사합니다.
int store_msg(void __user *dest, struct msg_msg *msg, size_t len) { size_t alen; struct msg_msgseg *seg; alen = min(len, DATALEN_MSG); if (copy_to_user(dest, msg + 1, alen)) return -1; for (seg = msg->next; seg != NULL; seg = seg->next) { len -= alen; dest = (char __user *)dest + alen; alen = min(len, DATALEN_SEG); if (copy_to_user(dest, seg + 1, alen)) return -1; } return 0; }
C
복사
static long do_msg_fill(void __user *dest, struct msg_msg *msg, size_t bufsz) { struct msgbuf __user *msgp = dest; size_t msgsz; if (put_user(msg->m_type, &msgp->mtype)) return -EFAULT; msgsz = (bufsz > msg->m_ts) ? msg->m_ts : bufsz; if (store_msg(msgp->mtext, msg, msgsz)) return -EFAULT; return msgsz; }
C
복사
1.
put_user 함수로 msg→m_typedest→mtype에 복사합니다.
2.
store_msgkernel space에서 user space로 데이터를 복사합니다.
3.
msgsz에 최종적으로 전달한 msg의 길이를 담아서 return합니다.
이제 해당 코드를 이해해보면 msg를 찾고 user spacemsg를 복사해서 넣은 뒤 bufsz에 최종적으로 복사한 msg size가 들어가게 됩니다.
for (;;) { struct msg_receiver msr_d; ... msg = find_msg(msq, &msgtyp, mode); } ... bufsz = msg_handler(buf, msg, bufsz); free_msg(msg); return bufsz;
C
복사
최종적으로 free_msg를 보면 kernel에서 할당한 msguser space로 전달했기 때문에 kernel에서 전송(할당)받은 msg는 삭제(free)합니다.
void free_msg(struct msg_msg *msg) { struct msg_msgseg *seg; security_msg_msg_free(msg); seg = msg->next; kfree(msg); while (seg != NULL) { struct msg_msgseg *tmp = seg->next; cond_resched(); kfree(seg); seg = tmp; } } void security_msg_msg_free(struct msg_msg *msg) { call_void_hook(msg_msg_free_security, msg); kfree(msg->security); msg->security = NULL; }
C
복사

전체적인 msgrcv call stack

1. ksys_msgrcv() 2. do_msgrcv() 3. find_msg() 4. do_msg_fill() 5. free_msg()
C
복사
msgrcv 함수 호출 시 user spacekernel에 존재하는 msg를 복사한 뒤 msgfree합니다.
호출 시 전달하는 인자에 따라서 free하지 않을 수 도 있습니다.

후일담

처음하는 kernel 코드 분석이라 틀린부분이 있다면 이해해주시고 payload.jang@gmail.com으로 말씀해주시면 감사하겠습니다.