Sign Kernel Module for Secure Boot

安装新的nvidia驱动遇到了内核模块签名的问题

在linux系统里安装了nvidia的CUDA,然后在支持UEFI的系统里启动会报错。
检查发现,系统的新安装的驱动,即新添加的一些nvidia相关的kernel module没有被sign,在secure boot启动过程中check failed。

这里检查时主要有两个:一个是fedora文档,一个是nvidia文档

1
2
3
# sh ./NVIDIA-Linux-x86-319.12.run -s \
--module-signing-secret-key=/path/to/signing.key \
--module-signing-public-key=/path/to/signing.x509

这里有说明用nvidia的runfile加参数,runfile会自动给你签名,但是安装CUDA的时候下载的runfile报错没有这些参数

顺便一说,CUDA的安装也有两种方式:rpm和runfile
http://developer.download.nvidia.com/compute/cuda/7.5/Prod/docs/sidebar/CUDA_Quick_Start_Guide.pdf

在rpm方式下已经安装成功,因为rpm基本都是下载文件,然后安装文件,最后一步直接yum install cuda完成所有依赖的东西的安装

runfile的话则是先要进行一些配置文件的修改,有些命令会执行失败


手动siging kernel module的流程

需要的tools

  • openssl openssl
  • sign-file kernel-devel
  • perl perl
  • mokutil mokutil
  • keyctl keyutils

其中,openssl、sign-file和perl都是在build system里操作。
openssl用来生成密钥对,perl命令用来执行sign-file这个脚本文件,来给kernel module进行签名

mokutil和keyctl是在Target system执行。
mokutil用来enroll the public key,就是把公钥加到MOK(Machine Owner Key)表里。
keyctl用来显示system key ring里的公钥

在build system里,不需要UEFI Secure Boot被enabled也不需要是一个支持UEFI的系统

kernel module authentication

fedora系统中,内核模块加载后,内核模块的签名signature会用存储在内核的system key ring里的公钥进行检查,包括检查白名单和黑名单。

在启动的时候,内核会从多处加载X.509 keys,加载到system key ring里,以便后面的对内核模块的check。
可以查看具体哪些source: Sources For System Key Rings

  • Embedded in kernel
  • UEFI Secure Boot “db”
  • UEFI Secure Boot “dbx”
  • Embedded in shim.efi boot loader
  • Machine Owner Key (MOK) list

kernel本身用户无法操作,UEFI的处于机器固件层面的,不好操作,就剩下shim.efi和MOK了。

在正常的系统中,shim.efi用户无法直接改动的,但在制作虚拟机镜像的时候,可以直接修改内核的所有文件,这时就可以替换或修改这个efi文件。
而通常情况下,比如在运行中的系统中,一般会用命令去操作MOK表,把前面步骤里生成的key注入到MOK中。

  • 如果uefi secure boot没有启动或系统本身就不支持uefi secure boot,那么内核只会加载keys Embedded in kernel。
  • 黑名单有更高优先级,如果存在于黑名单中了,那么就算内核模块以及签名也没用

下面是几个显示system key ring中密钥列表的命令例子:

UEFI Secure Boot is not enabled

1
2
3
~]# keyctl list %:.system_keyring
1 key in keyring:
61139991: ---lswrv 0 0 asymmetric: Fedora kernel signing key: 1fc9e68f7419556348fdee2fdeb7ff9da6337b

UEFI Secure Boot is enabled

1
2
3
4
5
6
7
8
~]# keyctl list %:.system_keyring
6 keys in keyring:
...asymmetric: Red Hat Enterprise Linux Driver Update Program (key 3): bf57f3e87...
...asymmetric: Red Hat Secure Boot (CA key 1): 4016841644ce3a810408050766e8f8a29...
...asymmetric: Microsoft Corporation UEFI CA 2011: 13adbf4309bd82709c8cd54f316ed...
...asymmetric: Microsoft Windows Production PCA 2011: a92902398e16c49778cd90f99e...
...asymmetric: Red Hat Enterprise Linux kernel signing key: 4249689eefc77e95880b...
...asymmetric: Red Hat Enterprise Linux kpatch signing key: 4d38fd864ebe18c5f0b7...

可以查看kernel console messages,查看密钥是从哪里加载进来的。示例中是搜索从efi文件加载的。

1
2
3
4
~]# dmesg | grep 'EFI: Loaded cert'
[5.160660] EFI: Loaded cert 'Microsoft Windows Production PCA 2011: a9290239...
[5.160674] EFI: Loaded cert 'Microsoft Corporation UEFI CA 2011: 13adbf4309b...
[5.165794] EFI: Loaded cert 'Red Hat Secure Boot (CA key 1): 4016841644ce3a8...

kernel module在启动过程中的规则

kernel module在启动过程中可能被检测,因为还要依托于一个参数module.sig_enforce

kernel module在启动过程中的整体流程

key是否强制加载,是否找到,是否验证通过,是否支持uefi secure boot,内核模块是否加载成功,内核是否被污染

Kernel Module Authentication Requirements for Loading

生成密钥

  • 用openssl生成X.509密钥对
    执行命令时依赖一个配置文件,要先创建一个配置文件(尖括号括起来的需要手动修改)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
~]# cat << EOF > configuration_file.config
[ req ]
default_bits = 4096
distinguished_name = req_distinguished_name
prompt = no
string_mask = utf8only
x509_extensions = myexts

[ req_distinguished_name ]
O = <Organization>
CN = <Organization signing key>
emailAddress = <E-mail address>

[ myexts ]
basicConstraints=critical,CA:FALSE
keyUsage=digitalSignature
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid
EOF
  • 执行openssl命令
1
2
3
4
~]# openssl req -x509 -new -nodes -utf8 -sha256 -days 36500 \
> -batch -config configuration_file.config -outform DER \
> -out public_key.der \
> -keyout private_key.priv

注意保护私钥

把生成的密钥加入到target system的多种途径

因为内核从多处加载密钥,所以也有多种方式放置密钥

  • 让服务器硬件厂商把你自己生成的密钥加入到他们自己的固件镜像的UEFI Secure Boot key database

    有db和dbx两种database,内核加载是会过滤掉dbx中的失效key(revoked key)

  • Executable Key Enrollment Image Adding Public Key
    没太理解,应该是是编辑一个已有的image

  • 手动把public key加入到MOK list
    MOK是fedora里的一个功能,The MOK facility is supported by shim.efi, MokManager.efi, grubx64.efi, and the Fedora mokutil utility.

1
~]# mokutil --import my_signing_key_pub.der

reboot the machine

MOK key enrollment的请求会被shim.efi注意到,然后它会调用MokManager.efi,从UEFI console完成密钥的登记。这时候你需要输入你刚才mokutil命令输入的密码

对于ironic的user image怎么办??,无法启动,是否会在image加载后,启动时完成enrollment??

Signing Kernel Module with the Private Key

  • 编译出来module(或者其他方式生成的module)
1
~]# make -C /usr/src/kernels/$(uname -r) M=$PWD modules
  • 执行perl脚本,签名
1
2
3
4
5
~]# perl /usr/src/kernels/$(uname -r)/scripts/sign-file \
> sha256 \
> my_signing_key.priv \
> my_signing_key_pub.der \
> my_module.ko
  • 可以用modinfo来查看module的信息,链接

加载signed kernel module

  • 首先确认系统本次启动中内核没有加载密钥,用命令来查看system keyring
1
keyctl list %:.system_keyring
  • 像之前的步骤一样,把公钥登记到MOK中,准确点叫做请求登记
1
~]# mokutil --import my_signing_key_pub.der
  • Reboot, and complete the enrollment at the UEFI console.
  • 重启后,再次检查keyring
1
keyctl list %:.system_keyring
  • 现在则可以正常的加载你的kernel module了
1
2
3
4
~]# modprobe -v my_module
insmod /lib/modules/3.17.4-302.fc21.x86_64/extra/my_module.ko
~]# lsmod | grep my_module
my_module 12425 0

有个疑问,这里内核模块没有签名,则该内核模块加载失败,

kernel modules and their utilities

一些内核相关的命令

1
2
3
4
5
6
lsmod(8) — The manual page for the lsmod command.
modinfo(8) — The manual page for the modinfo command.
modprobe(8) — The manual page for the modprobe command.
rmmod(8) — The manual page for the rmmod command.
ethtool(8) — The manual page for the ethtool command.
mii-tool(8) — The manual page for the mii-tool command.

回到对ironic user image的签名

回到实际的场景,对添加了nvidia kernel module的image进行签名,有以下几种可操作的方法:

  • 使用自签名,那么使用命令操作MOK后,系统文件里就包含密钥了,但是需要把这个密钥最终写入到uefi主板上,这一步需要在系统启动时手动操作,所以无法自动化。
  • 使用已有签名,就是uefi主板的数据库里已经带了默认密钥,是在出厂时就写入的官方公钥。所以如果想想一个内核模块加载成功,就需要这个内核模块被官方的私钥签名,但是这个操作个人是无法做的,只能由fedora官方来做。