首页 > 系统相关 >Linux驱动加载源码分析(安全加载 、签名、校验)

Linux驱动加载源码分析(安全加载 、签名、校验)

时间:2024-07-14 19:19:17浏览次数:14  
标签:info ... module init 源码 __ Linux 加载 mod

PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

环境说明

  无

前言


  很久很久以前,在android上面移植linux驱动的时候,由于一些条件限制,导致我们测试驱动非常的麻烦。其中有一个麻烦就是驱动校验失败,然后内核拒绝加载驱动。

  原则上来说,只要你对驱动进行签名或者配置,就能加载成功,但是当时赶时间验证,就想着直接把驱动校验部分的代码直接屏蔽了,达到了我们测试的目的。

  现在过了许久了,现在有经历来重温一下当初的问题,看看根源是什么,于是我们得了解驱动加载的通用流程,查看我们的驱动到底因为哪些原因加载失败。





linux驱动加载流程


  首先,我们知道linux驱动有两个关键入口函数,一般被module_init()/module_exit()宏进行处理。当我们想加载一个linux驱动的时候,一般我们使用insmod/modprobe来加载驱动,下面我们来看看执行insmod/modprobe时,到底发生了什么?

  经过简单的查询资料,驱动的处理涉及两个linux系统调用,他们是:

int syscall(SYS_init_module, void module_image[.len], unsigned long len,
            const char *param_values);
int syscall(SYS_finit_module, int fd,
            const char *param_values, int flags);

  根据man手册介绍,SYS_init_module三个参数分别是内核驱动文件内容、文件内容长度、内核驱动参数。

  下面我们深入内核看看,执行SYS_init_module时,到底发生了什么?

  根据linux v6.9.6 kernel/module/main.c文件

SYSCALL_DEFINE3(init_module, void __user *, umod,
		unsigned long, len, const char __user *, uargs)
{
	int err;
	struct load_info info = { };

    // ... ...

	err = copy_module_from_user(umod, len, &info);

    // ... ...

	return load_module(&info, uargs, 0);
}

  这里最重要的就是通过copy_module_from_user给struct load_info赋值。

  然后到了load_module函数(根据linux v6.9.6 kernel/module/main.c文件):

static int load_module(struct load_info *info, const char __user *uargs,
		       int flags)
{
	struct module *mod;
	bool module_allocated = false;
	long err = 0;
	char *after_dashes;

	/*
	 * Do the signature check (if any) first. All that
	 * the signature check needs is info->len, it does
	 * not need any of the section info. That can be
	 * set up later. This will minimize the chances
	 * of a corrupt module causing problems before
	 * we even get to the signature check.
	 *
	 * The check will also adjust info->len by stripping
	 * off the sig length at the end of the module, making
	 * checks against info->len more correct.
	 */
	err = module_sig_check(info, flags);
	if (err)
		goto free_copy;

	/*
	 * Do basic sanity checks against the ELF header and
	 * sections. Cache useful sections and set the
	 * info->mod to the userspace passed struct module.
	 */
	err = elf_validity_cache_copy(info, flags);
	if (err)
		goto free_copy;

	err = early_mod_check(info, flags);
	if (err)
		goto free_copy;
    
	/* Figure out module layout, and allocate all the memory. */
	mod = layout_and_allocate(info, flags);
	if (IS_ERR(mod)) {
		err = PTR_ERR(mod);
		goto free_copy;
	}


    // ... ...

    return do_init_module(mod);

    // ... ...
}

  在 load_module 中,我们找到了3个重要的验证接口,一个是签名验证、一个是elf文件验证、一个是模块本身的信息验证。其中签名验证、模块本身的信息验证就是本文要关注的地方。经过了一系列的验证和初始化后,调用了do_init_module接口。

static noinline int do_init_module(struct module *mod)
{
	int ret = 0;
	struct mod_initfree *freeinit;

    //... ...
	/* Start the module */
	if (mod->init != NULL)
		ret = do_one_initcall(mod->init);
	if (ret < 0) {
		goto fail_free_freeinit;
	}
	if (ret > 0) {
		pr_warn("%s: '%s'->init suspiciously returned %d, it should "
			"follow 0/-E convention\n"
			"%s: loading module anyway...\n",
			__func__, mod->name, ret, __func__);
		dump_stack();
	}    

    //... ...
}

  看这里的do_one_initcall(mod->init),就相当于调用了我们通过module_init()定义的接口了。

  但是这里有一个问题?那就是mod->init是module_init()定义的接口,那它是怎么赋值的呢?要回答这个问题,还要回到我们创建一个ko文件的时候,有两个地方我们需要关注,这里我们随便创建一个helloworld的驱动为例:

/* Each module must use one module_init(). */
#define module_init(initfn)					\
	static inline initcall_t __maybe_unused __inittest(void)		\
	{ return initfn; }					\
	int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, World!\n");
    return 0; 
}

module_init(hello_init);

  上面可以看到,我们通过module_init()这个宏,我们声明了一个叫做init_module函数,且此函数是hello_init的别名(alias是gcc的扩展用法),换句话说我们调用init_module就等于调用了hello_init。

  此外,在我们生成ko文件的时候,还会看到一个被创建的xxx.mod.c的文件,里面有一个地方定义很重要:

__visible struct module __this_module
__section(.gnu.linkonce.this_module) = {
	.name = KBUILD_MODNAME,
	.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
	.exit = cleanup_module,
#endif
	.arch = MODULE_ARCH_INIT,
};

  注意看这里的__this_module这个变量,这个变量其成员有init_module这个函数的地址信息,也就有了hello_init的地址信息,且这个__this_module变量被放到了.gnu.linkonce.this_module这个section里面。

  如果了解elf文件格式的,一定对section这个东西不陌生,其存放了很多elf相关内容,在这里,我们只需要关注.gnu.linkonce.this_module小节,就是__this_module的地址,这个会在驱动加载的时候用上。

  上面我们知道了init_module被放置到__this_module.init字段去了,那么执行do_one_initcall(mod->init)时,mod->init是怎么初始化的呢?下面我们接着分析mod->init的赋值,首先我们要回到SYS_init_module调用时,有一个load_module函数,在load_module函数中,有一个elf_validity_cache_copy()函数:

static int elf_validity_cache_copy(struct load_info *info, int flags)
{
	unsigned int i;
	Elf_Shdr *shdr, *strhdr;
	int err;
	unsigned int num_mod_secs = 0, mod_idx;
	unsigned int num_info_secs = 0, info_idx;
	unsigned int num_sym_secs = 0, sym_idx;

	//... ...
	for (i = 1; i < info->hdr->e_shnum; i++) {
		shdr = &info->sechdrs[i];
		switch (shdr->sh_type) {
			// ... ...
		default:
			// ... ...
			if (strcmp(info->secstrings + shdr->sh_name,
				   ".gnu.linkonce.this_module") == 0) {
				num_mod_secs++;
				mod_idx = i;
			} else if (strcmp(info->secstrings + shdr->sh_name,
				   ".modinfo") == 0) {
				num_info_secs++;
				info_idx = i;
			}
			// ... ...
		}
	}

	// ... ...
	info->index.mod = mod_idx;

	/* This is temporary: point mod into copy of data. */
	info->mod = (void *)info->hdr + shdr->sh_offset;

	/// ... ...	
}

  这里其实就是遍历section数组,然后得到.gnu.linkonce.this_module在section数组中的idx,并记录到info->index.mod中。(此处如果不明白,建议可以简单看看elf格式介绍,本文不分析这个)

  然后在load_module函数中的layout_and_allocate()中,会处理info->index.mod:

static struct module *layout_and_allocate(struct load_info *info, int flags)
{
	struct module *mod;
	unsigned int ndx;
	int err;

	// ... ...

	/* Module has been copied to its final place now: return it. */
	mod = (void *)info->sechdrs[info->index.mod].sh_addr;
	kmemleak_load_module(mod, info);
	return mod;
}

  在此函数对mod赋值的过程中,就把ko文件的__this_module变量的地址,绑定给了mod,然后mod往后面传,就可以执行mod->init函数了,也就是执行hello_init。





驱动校验加载


  对上文我们提到的load_module中有三个驱动校验相关的函数:

  • module_sig_check
  • elf_validity_cache_copy
  • early_mod_check

  其中elf_validity_cache_copy是对驱动二进制格式进行校验的,一般我们正常的驱动是满足条件的。因此,我们主要是去解决module_sig_check和early_mod_check的问题。

  对于module_sig_check来说,就是利用签名算法(可参考之前文章《常用加密及其相关的概念、简介(对称、AES、非对称、RSA、散列、HASH、消息认证码、HMAC、签名、CA、数字证书、base64、填充)》 https://www.cnblogs.com/Iflyinsky/p/18076852 ),保证内核驱动使用了内核认可的私钥进行签名,然后内核使用公钥进行验证。

  对于early_mod_check来说,就是校验内核版本信息、模块信息等等,这里就不详细介绍了。

  总的来说,如果我们要关闭内核的相关校验,可以通过以下的配置,或者直接处理module_sig_check、early_mod_check两个函数即可达到我们的目的。

CONFIG_MODULE_SIG=y
CONFIG_MODULE_SIG_FORCE=y
CONFIG_MODULE_SIG_ALL=y
CONFIG_MODULE_SIG_SHA256=y
CONFIG_MODVERSIONS=y

  特别注意,如果是在android系统里面,有些情况下(例如qcom的源码),你关闭了这些检测,会导致android系统编译失败,因为android kernel配置的安全检测无法通过。所以需要直接修改module_sig_check和early_mod_check函数,直接返回通过即可,这样即可测试。





后记


  通过阅读源码,感觉对内核各个模块的工作越来越熟悉了。

  但是越了解的多,越觉得未知越多。

参考文献




打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)
qrc_img

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。

标签:info,...,module,init,源码,__,Linux,加载,mod
From: https://www.cnblogs.com/Iflyinsky/p/18301894

相关文章

  • 在 Linux 中的 Nginx 上部署 Django 项目
    要在Linux中的Nginx上部署Django项目,一般需要以下步骤:安装必要的软件安装Python和相关依赖。安装Django项目所需的库。配置Django项目完成Django项目的开发和测试。配置项目的 settings.py 文件,例如设置数据库连接、静态文件路径等。安装和配置uWSGI......
  • 网站源码SEO公司pbootcms模板网页设计主题
    SEO公司的网站设计分享我很高兴向大家介绍我刚刚制作的SEO公司的网站设计。友好的站点界面,是打动访客的第一步。SEO公司网站主题网站设计旨在展示公司的专业能力、服务范围以及优化效果,吸引潜在客户的关注,提升公司在搜索引擎中的排名和可见性。以下是对SEO公司网站主题设计的......
  • 02 源码编译安装LAMP
          目录2.1Apache网站服务基础2.1Apache简介1.Apache的起源起源背景Apache的诞生Apache软件基金会的成立Apache的流行和影响2.1.1安装httpd服务器1.准备工作2.源码编译及安装(1)解包(2)配置(3)编译及安装3.确认安装结果4优化执行路径5.添加httpd系统......
  • linux命令中arping的使用
    linux命令在线查询工具https://wheart.cn/onlinetools/linux_command/index.htmlarping通过发送ARP协议报文测试网络补充说明arping命令是用于发送arp请求到一个相邻主机的工具,arping使用arp数据包,通过ping命令检查设备上的硬件地址。能够测试一个ip地址是否是在网络......
  • Linux ---gcc
    c语言的链接类型:动态链接:ll/lib64/libc-2.17.so静态链接:ll/lib64/libc.aglibc-static安装-CSDN博客https://blog.csdn.net/itas109/article/details/104226783在Linux中下载c语言的静态库。gcctest.c-otest.s-static以静态链接的形式对程序进行编译。g++mytest.......
  • linux内核下并发时同步机制
    1并发场景Linux系统并发产生的原因很复杂,总结一下有下面几个主要原因:多线程并发访问,Linux是多任务(线程)的系统,所以多线程访问是最基本的原因。抢占式并发访问,从2.6版本内核开始,Linux内核支持抢占,也就是说调度程序可以在任意时刻抢占正在运行的线程,从而运行其他的线程......
  • Java计算机毕业设计个性化旅游景点推荐网站(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在旅游业蓬勃发展的今天,随着人们生活水平的提高和休闲时间的增加,个性化旅游需求日益凸显。传统旅游推荐方式往往基于热门景点或固定线路,难以满足游客......
  • Java计算机毕业设计多媒体素材管理库(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展,多媒体素材在教育教学、广告宣传、影视制作等多个领域的应用日益广泛。然而,多媒体素材的种类繁多、数量庞大,如何高效地存储、......
  • 003java jsp SSM在线医院医疗服务系统医院预约挂号医生坐诊健康资讯(源码+文档+开题+运
     项目技术:SSM+Maven+Vue等等组成,B/S模式+Maven管理等等。环境需要1.运行环境:最好是javajdk1.8,我们在这个平台上运行的。其他版本理论上也可以。2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA;3.tomcat环境:Tomcat7.x,8.x,9.x版本均可4.硬件环境:windows7/8/1......
  • 1117java jsp SSM Springboot在线答疑系统学生考试问题发布教师疑难解答(源码+文档+PPT
     项目技术:Springboot+Maven+Vue等等组成,B/S模式+Maven管理等等。环境需要1.运行环境:最好是javajdk1.8,我们在这个平台上运行的。其他版本理论上也可以。2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA;3.tomcat环境:Tomcat7.x,8.x,9.x版本均可4.硬件环境:window......