PS:要转载请注明出处,本人版权所有。
PS: 这个只是基于《我自己》的理解,
如果和你的原则及想法相冲突,请谅解,勿喷。
环境说明
无
前言
只要装过各种系统的人都或多或少会接触到UEFI或者BIOS这样的概念。本文也不会对这些概念进行详解,本文主要把这些概念串起来,并引入MOK(Machine Owner Key)。
为什么需要MOK,是因为在使用现代linux系统时(如:PVE),如果我们需要自己安装一些自己构建的驱动(例如想实现gpu sr-iov),会用到此功能。
UEFI/BIOS
BIOS (Basic Input/Output System) 和UEFI (Unified Extensible Firmware Interface) 这两个名字或者功能我们非常的熟悉了,机器开机自检完成后,一般f2/del等进入的界面,就是这个系统在显示工作。如果我们不按f2/del等键,系统会默默运行BIOS或者UEFI,然后自动加载引导程序,然后加载OS来运行。
UEFI主要是为了取代BIOS系统的,因为其有:支持更多分区、启动速度快、支持更多硬件、更加安全、维护简单(统一标准)等等优点。其有个大的缺点就是,有些时候会因为安全的问题,需要更多的设置过程。
对于BIOS来说,机器开机自检完成后,自动读取MBR(Master boot record),一般在磁盘的开始的扇区,然后加载OS或者其他进行启动。注意,这里MBR分区是一种老旧的分区格式了。
对于UEFI来说,机器开机自检完成后,自动读取GPT分区(GUID Partition Table)中的EFI分区,然后加载OS或者其他进行启动。
总的来说,随着时间的推进,UEFI是一种标准,已经被各大厂商支持和实现了。BIOS其已经完成了其历史的作用,除了为了兼容老机器,否则我们不应该使用它。
Linux 在UEFI下,自构建驱动安装问题
这里有一个背景知识,那就是现代的linux内核,在加载内核驱动的时候,一般都会对内核驱动做一系列校验,其中一项就是做签名校验,如果校验失败,内核拒绝加载驱动。对于这部分内容,可以参考《Linux驱动加载源码分析(安全加载 、签名、校验)》 https://www.cnblogs.com/Iflyinsky/p/18301894 一文。
在《常用加密及其相关的概念、简介(对称、AES、非对称、RSA、散列、HASH、消息认证码、HMAC、签名、CA、数字证书、base64、填充)》 https://www.cnblogs.com/Iflyinsky/p/18076852 中我们介绍了签名的原理,这里简单提一下:首先有非对称加密算法生成公钥、私钥。然后对消息进行摘要,对摘要进行私钥加密得到签名,最后可以用公钥来验证(解密)此签名是否正确。
那么对应到内核驱动签名验证这里就是:首先对驱动模块使用私钥进行签名,并将签名文件写入驱动模块文件中,当我们加载驱动模块时,内核会使用其带的公钥来对驱动模块进行签名验证。
注意,这里有一个重要的问题是:内核带的公钥是哪里来的?一般来说,有两个渠道可以增加内核的公钥,一个是编译内核的时候,一个是通过运行时的一些方法动态写入一些公钥到内核。
Machine Owner Key
在实际我们自己测试自己的驱动模块的时候,一般都会自己生成一个私钥,公钥对来对自己的驱动模块进行签名。但是在启用的UEFI+ 支持安全启动的linux系统上,我们的驱动模块是无法正常加载的,因为我们的驱动无法过签名验证。
从上面的描述来看,如果要成功加载我们的内核模块,那么我们应该把我们的公钥传给内核。
在解决怎么把公钥传给内核前,我们第一步要简单了解一下linux secure boot的简单流程:
- 机器开机及硬件自检完成,然后进入uefi固件,uefi固件里面有微软公钥。
- uefi加载shim固件(独立与linux发行版,被微软私钥预先签名,例如这个包: https://packages.debian.org/sid/amd64/shim-signed )。此外shim有各大发行版公钥。
- shim固件加载grub固件(grub固件被各大发行版私钥签名)。
- grub加载linux签名内核。
其实从上面的流程来看,就是一环环签名校验,保证了信任链的传递。
回到我们之前的问题,我们怎么把我们私钥、公钥传给内核呢?必定是有一个工具能够将相关信息传进去,这个工具就是mokutil工具。
mokutil
简而言之,shim除了自带发行版的公钥外,还维护一个用户可以操作的密钥数据库,里面存储的是Machine Owner Key。通过mokutil工具,我们可以增加和删除这些密钥。这样我们就可以将我们自己的模块签名公钥嵌入到了UEFI启动流程中去,然后根据适当的方法即可交给内核使用,并能够加载我们自己密钥签名的驱动程序。
mokutil工具添加过程:
- 导入公钥
mokutil --import /var/lib/dkms/mok.pub
# 并输入一次性密码
-
重启系统,此时新一次的uefi的启动流程会启动mok管理器,让用户按照要求注册新的密钥,并输入之前的一次性密码。(弹个框,自己选择,输入密码即可)
-
这样启动系统后,我们的密钥成功加载。
-
测试系统是否成功注册密码
mokutil --test-key /var/lib/dkms/mok.pub
这样我们就可以使用mok.pub对应的私钥对我们的驱动进行签名,然后就可以正常使用公钥验证,然后加载驱动了。
此外,这里还要多提一下,其实android的安全加载也有类似的过程,也是两个要点:信任链传递、驱动签名。
后记
了解了越来越多计算机的知识,不得不感叹:知识总是不经意间出现在日常生活工作中。
参考文献
- https://wiki.debian.org/UEFI
- https://wiki.debian.org/SecureBoot
- https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux/7/html/kernel_administration_guide/sect-signing-kernel-modules-for-secure-boot
- https://www.cnblogs.com/Iflyinsky/p/18301894
- https://www.cnblogs.com/Iflyinsky/p/18076852
PS: 请尊重原创,不喜勿喷。
PS: 要转载请注明出处,本人版权所有。
PS: 有问题请留言,看到后我会第一时间回复。