首页 > 编程语言 >kernel-learn:msgrcv源码解读

kernel-learn:msgrcv源码解读

时间:2024-11-01 18:46:37浏览次数:6  
标签:kernel return struct msgrcv seg alen 源码 msg copy

笔者最近复现CVE中时常用到msgrcv,由于对某些细节不了解,卡了很久,所以解读一下源码。

msg.c - ipc/msg.c - Linux source code v5.11.22 - Bootlin

这是网址,有兴趣的可以自己看看。

这是msgrcv的flag

/* msgrcv options */
#define MSG_NOERROR     010000  /* no error if message is too big */
#define MSG_EXCEPT      020000  /* recv any msg except of specified type.*/
#define MSG_COPY        040000  /* copy (not remove) all queue messages */

本文主要聚焦msg_copy,这也是kernel pwn或复现CVE中经常用的。

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;

	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);
	}
	mode = convert_mode(&msgtyp, msgflg);

	rcu_read_lock();
	msq = msq_obtain_object_check(ns, msqid);
	if (IS_ERR(msq)) {
		rcu_read_unlock();
		free_copy(copy);
		return PTR_ERR(msq);
	}

	for (;;) {
		struct msg_receiver msr_d;

		msg = ERR_PTR(-EACCES);
		if (ipcperms(ns, &msq->q_perm, S_IRUGO))
			goto out_unlock1;

		ipc_lock_object(&msq->q_perm);

		/* raced with RMID? */
		if (!ipc_valid_object(&msq->q_perm)) {
			msg = ERR_PTR(-EIDRM);
			goto out_unlock0;
		}

		msg = find_msg(msq, &msgtyp, mode);
		if (!IS_ERR(msg)) {
			/*
			 * Found a suitable message.
			 * Unlink it from the queue.
			 */
			if ((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) {
				msg = ERR_PTR(-E2BIG);
				goto out_unlock0;
			}
			/*
			 * If we are copying, then do not unlink message and do
			 * not update queue parameters.
			 */
			if (msgflg & MSG_COPY) {
				msg = copy_msg(msg, copy);
				goto out_unlock0;
			}

			list_del(&msg->m_list);
			msq->q_qnum--;
			msq->q_rtime = ktime_get_real_seconds();
			ipc_update_pid(&msq->q_lrpid, task_tgid(current));
			msq->q_cbytes -= msg->m_ts;
			atomic_sub(msg->m_ts, &ns->msg_bytes);
			atomic_dec(&ns->msg_hdrs);
			ss_wakeup(msq, &wake_q, false);

			goto out_unlock0;
		}

		/* No message waiting. Wait for a message */
		if (msgflg & IPC_NOWAIT) {
			msg = ERR_PTR(-ENOMSG);
			goto out_unlock0;
		}

		list_add_tail(&msr_d.r_list, &msq->q_receivers);
		msr_d.r_tsk = current;
		msr_d.r_msgtype = msgtyp;
		msr_d.r_mode = mode;
		if (msgflg & MSG_NOERROR)
			msr_d.r_maxsize = INT_MAX;
		else
			msr_d.r_maxsize = bufsz;

		/* memory barrier not require due to ipc_lock_object() */
		WRITE_ONCE(msr_d.r_msg, ERR_PTR(-EAGAIN));

		/* memory barrier not required, we own ipc_lock_object() */
		__set_current_state(TASK_INTERRUPTIBLE);

		ipc_unlock_object(&msq->q_perm);
		rcu_read_unlock();
		schedule();

		/*
		 * Lockless receive, part 1:
		 * We don't hold a reference to the queue and getting a
		 * reference would defeat the idea of a lockless operation,
		 * thus the code relies on rcu to guarantee the existence of
		 * msq:
		 * Prior to destruction, expunge_all(-EIRDM) changes r_msg.
		 * Thus if r_msg is -EAGAIN, then the queue not yet destroyed.
		 */
		rcu_read_lock();

		/*
		 * Lockless receive, part 2:
		 * The work in pipelined_send() and expunge_all():
		 * - Set pointer to message
		 * - Queue the receiver task for later wakeup
		 * - Wake up the process after the lock is dropped.
		 *
		 * Should the process wake up before this wakeup (due to a
		 * signal) it will either see the message and continue ...
		 */
		msg = READ_ONCE(msr_d.r_msg);
		if (msg != ERR_PTR(-EAGAIN)) {
			/* see MSG_BARRIER for purpose/pairing */
			smp_acquire__after_ctrl_dep();

			goto out_unlock1;
		}

		 /*
		  * ... or see -EAGAIN, acquire the lock to check the message
		  * again.
		  */
		ipc_lock_object(&msq->q_perm);

		msg = READ_ONCE(msr_d.r_msg);
		if (msg != ERR_PTR(-EAGAIN))
			goto out_unlock0;

		list_del(&msr_d.r_list);
		if (signal_pending(current)) {
			msg = ERR_PTR(-ERESTARTNOHAND);
			goto out_unlock0;
		}

		ipc_unlock_object(&msq->q_perm);
	}

out_unlock0:
	ipc_unlock_object(&msq->q_perm);
	wake_up_q(&wake_q);
out_unlock1:
	rcu_read_unlock();
	if (IS_ERR(msg)) {
		free_copy(copy);
		return PTR_ERR(msg);
	}

	bufsz = msg_handler(buf, msg, bufsz);
	free_msg(msg);

	return bufsz;
}

这是源码,接下来我们一步一步分析。

if (msqid < 0 || (long) bufsz < 0)
		return -EINVAL;

首先是这一步判断,基本不会错,-EINVAL反映到用户态就是invalid argument。

copy = prepare_copy(buf, min_t(size_t, bufsz, ns->msg_ctlmax));
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;
}
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);
}
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;
}

这一步实际上就是创建一个大小为bufsz的msg_msg,中间把用户的buf给copy上去,其实没意义,毕竟我们是接受信息,我也不知道为什么copy上去。

mode = convert_mode(&msgtyp, msgflg);
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;
}

很明显,我们的mode就是SEARCH_NUMBER

msg = find_msg(msq, &msgtyp, mode);

接下来就到这了

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);
}

因为我们的mode是SEARCH_NUMBER,所以msgtyp是与count比对,而不是msg_msg->m_type,count每轮加一,所以我们传入的mtype实际就是序号,从0开始。

if (msgflg & MSG_COPY) {
				msg = copy_msg(msg, copy);
				goto out_unlock0;
			}

接下来就到这了。

struct msg_msg *copy_msg(struct msg_msg *src, struct msg_msg *dst)
{
	struct msg_msgseg *dst_pseg, *src_pseg;
	size_t len = src->m_ts;
	size_t alen;

	if (src->m_ts > dst->m_ts)
		return ERR_PTR(-EINVAL);

	alen = min(len, DATALEN_MSG);
	memcpy(dst + 1, src + 1, alen);

	for (dst_pseg = dst->next, src_pseg = src->next;
	     src_pseg != NULL;
	     dst_pseg = dst_pseg->next, src_pseg = src_pseg->next) {

		len -= alen;
		alen = min(len, DATALEN_SEG);
		memcpy(dst_pseg + 1, src_pseg + 1, alen);
	}

	dst->m_type = src->m_type;
	dst->m_ts = src->m_ts;

	return dst;
}
if (src->m_ts > dst->m_ts)
		return ERR_PTR(-EINVAL);

所以在msg_copy时,我们传入的长度要大于或等于msg_msg->m_ts。

这一步其实就是将目标msg_msg传给我们之前创建的copy。

bufsz = msg_handler(buf, msg, bufsz);
free_msg(msg);

 最后就是这2步了。

我们msg_cpy时,

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);
}

是通过这个函数进来的。

所以msg_handler就是do_msg_fill

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;
}

 

msgsz = (bufsz > msg->m_ts) ? msg->m_ts : bufsz;

其实就是取bufsz(就是我们传入的size)和我们copy的msg_msg->m_ts中的最小值。

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;
}

就是循环把msg中的东西copy到我们传入的buf中,也是我们想要的。

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;
}

 就是先将msg->security给free掉,然后循环free掉msg_msg,通过伪造security指针可以构造任意释放,当然,也可以看到,如果msg_msg有security指针的话,我们不能随意覆盖,必须要覆盖合法的堆地址,不然可能kernel panic。

return bufsz;

最后得到的是复制的长度,对于msgcpy而言,就是msg_msg->m_ts。 

标签:kernel,return,struct,msgrcv,seg,alen,源码,msg,copy
From: https://blog.csdn.net/A13837377363/article/details/143432917

相关文章

  • UcOs-III 源码阅读: os_flag.c
    /***********************************************************************************************************uC/OS-III*TheReal-TimeKernel**Copy......
  • 基于SpringBoot的考试安排管理系统的设计和实现(源码+LW+调试文档)
     目录:完整视频演示:系统架构:程序运行截图:核心代码参考:   数据库sql:项目技术介绍:java介绍:Mysql数据库介绍:为什么选择我:获取源码:......
  • 基于SpringBoot的高考择校推荐系统的设计和实现(源码+LW+调试文档)
     目录:完整视频演示:系统架构:程序运行截图:核心代码参考:   数据库sql:项目技术介绍:java介绍:Mysql数据库介绍:为什么选择我:获取源码:......
  • NodeJS动漫论坛-计算机毕业设计源码09947
    基于微信小程序的动漫论坛摘 要随着移动互联网的飞速发展,智能手机和移动互联网已经成为人们日常生活中不可或缺的一部分。在这样的背景下,微信小程序应运而生,凭借其无需下载安装、即用即走的特点,迅速成为连接用户与服务的桥梁。动漫作为一种深受年轻人喜爱的文化形式,拥有庞......
  • 基于nodejs+vue基于的农贸市场网站的设计与实现[开题+源码+程序+论文]计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景关于农贸市场网站的设计与实现问题的研究,现有研究主要以农贸市场的实体运营、传统管理模式为主,专门针对基于特定功能的农贸市场网站设计与实现的研究较......
  • 基于nodejs+vue基于的汽车租赁系统[开题+源码+程序+论文]计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景关于汽车租赁系统的研究,现有研究多集中在系统的基本功能构建与运营模式方面,如迪蒙汽车租赁系统,其主要关注如何通过多网融合来拓展业务模式,提高运营效率......
  • 基于nodejs+vue基于的社区问答网站与设计[开题+源码+程序+论文]计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景关于社区问答网站的研究,现有研究多集中在其社交性和信息传播方面1。在国外,社区问答网站发展较早,技术和运营模式相对成熟,注重用户体验和信息的个性化推荐......
  • 给网站添加春节灯笼效果:引入即用,附源码!
    记得之前在别的网站上看到这个喜庆的春节灯笼效果,觉得非常吸引人。虽然网上有一些现成的API可以直接实现,比如这个春节灯笼API,但使用后我发现两个问题:一是手机端访问时灯笼没有自适应,二是灯笼上的“春节快乐”四个字不能自定义。为了解决这些问题,我找到了这篇文章,并“借鉴”了其......
  • RTX5/FreeRTOS全家桶源码工程综合实战模板集成CANopen组件(2024-10-30)
    【前言】之前的视频教程分享了两期CANopen的专题,配套的例子都是基于裸机的,为了方便大家在OS下使用,本期视频带OS下的支持。CANopen协议栈专题,实战方式系统了解NMT,PDO,SDO,时间戳,同步报文,紧急报文等(2023-10-17)https://www.armbbs.cn/forum.php?mod=viewthread&tid=121438CANopen......
  • (附源码)基于WEB的家乡特色农商品仓库管理系统的设计与实现-计算机毕设 26145
    基于WEB的家乡特色农商品仓库管理系统的设计与实现摘 要本论文旨在设计和实现基于WEB和SSM(Spring+SpringMVC+MyBatis)的家乡特色农商品仓库管理系统,以提高农产品仓储管理效率和服务水平。论文首先分析了传统农仓库信息管理存在的问题和需求,包括信息化程度低、管理效率不......