Ubuntu 22.04 通过编译降级内核版本

虚拟化平台与虚拟机安装

虚拟化平台使用 Windows 自带的 Hyper-V 虚拟机,安装 Ubuntu Server 22.04.1 LTS 系统。在创建虚拟机时选择第二代虚拟机,配置虚拟机的安全设置中安全启动的模板选择“Microsoft UEFI 证书颁发机构”,或直接禁用安全启动,否则会因为 UEFI 安全启动的原因无法进行 Ubuntu 系统的安装与启动。

安装时未启用 LVM。

安装完成之后可以配置 SSH server 后在 host 直接通过 SSH 连接上虚拟机,以避免在虚拟机窗口中输入命令的麻烦。

内核编译准备

安装必要的软件包

1
sudo apt install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison

复制配置信息

从当前系统的启动目录复制配置信息到源代码目录,使编译内核的配置与用当前环境一致。

1
cp /boot/config-$(uname -r) .config

配置内核的编译参数

使用 make oldconfig 比对当前系统配置信息和新内核选项的差别,并对新增项与修改项做配置。这里也可以直接使用 make olddefconfig 配置所有差异项为默认推荐设置。

使用 make menuconfig 启动 TUI 界面的内核配置工具,可以对编译选项做更详细的设置。

或者可以直接编辑源码目录下 .config 文件进行配置。

配置 Hyper-V 第二代虚拟机驱动

对于 Hyper-V 第二代虚拟机,需要按照此顺序启用(以 built-in 方式)内核中的某些选项,在启用前面的选项之后才可以启用后面的选项:

  • CONFIG_HYPERVISOR_GUEST: Processor type and featueres > Linux Guest Support
  • CONFIG_PARAVIRT: Processor type and features > Linux Guest Support > Enable paravirtualization code
  • CONFIG_PARAVIRT_SPINLOCKS: Processor type and features > Linux Guest Support > Paravirtualization layer for spinlocks
  • CONFIG_CONNECTOR: Device Drivers > Connector - unified userspace <-> kernelspase linker
  • CONFIG_SCSI_FC_ATTRS: Device Drivers > SCSI device support > SCSI Transports > FiberChannel Transport Attributes
  • CONFIG_HYPERV: Device Drivers > Microsoft Hyper-V guest support > Microsoft Hyper-V client drivers
  • CONFIG_HYPERV_UTILS: Device Drivers > Microsoft Hyper-V guest support > Microsoft Hyper-V Utilities driver
  • CONFIG_HYPERV_BALLOON: Device Drivers > Microsoft Hyper-V guest support > Microsoft Hyper-V Balloon driver
  • CONFIG_HYPERV_STORAGE: Device Drivers > SCSI device support > SCSI low-level drivers > Microsoft Hyper-V virtual storage driver
  • CONFIG_HYPERV_NET: Device Drivers > Network device support > Microsoft Hyper-V virtual network driver
  • CONFIG_HYPERV_KEYBOARD: Device Drivers > Input device support > Hardware I/O ports > Microsoft Synthetic Keyboard driver
  • CONFIG_FB_HYPERV: Device Drivers > Graphics support > Frame buffer Devices > Microsoft Hyper-V Synthetic Video support
  • CONFIG_HID: Device Drivers > HID support > HID bus support
  • CONFIG_HID_HYPERV_MOUSE: Device Drivers > HID support > Special HID drivers > Microsoft Hyper-V mouse driver
  • CONFIG_PCI_HYPERV: Bus options (PCI etc.) > Hyper-V PCI Frontend
  • CONFIG_VSOCKETS: Networking support > Networking options > Virtual Socket protocol
  • CONFIG_HYPERV_VSOCKETS: Networking support > Networking options > Hyper-V transport for Virtual Sockets

system trusted key 文件配置

使用编辑器打开源码目录下 .config 文件,找到如下行:

1
CONFIG_SYSTEM_TRUSTED_KEYS="debian/canonical-certs.pem"

将引号中内容删去。

内核编译安装

为提升编译速度,可以临时提升虚拟机的 vCPU 核心数。为防止 SSH 连接掉线导致编译过程中断,可以使用 screen 创建虚拟终端会话。

使用 make -j$(nproc) 开始编译。

编译完成后使用 sudo make modules_install 安装模块。

随后使用 sudo make install 安装内核。

内核签名

若在开启了安全启动的情况下不对自行编译的内核进行签名,在 GRUB 加载内核后会出现 error: bad shim signature 错误。可以对内核进行签名解决。

内核的签名证书需要使用 openssl 生成。

创建配置文件 openssl.cnf 保存密钥信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# This definition stops the following lines choking if HOME isn't
# defined.
HOME = .
RANDFILE = $ENV::HOME/.rnd
[ req ]
distinguished_name = req_distinguished_name
x509_extensions = v3
string_mask = utf8only
prompt = no

[ req_distinguished_name ]
countryName = CN
stateOrProvinceName = Beijing
localityName = Beijing
0.organizationName = Neko.ooo
commonName = Secure Boot Signing
emailAddress = i@neko.ooo

[ v3 ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical,CA:FALSE
extendedKeyUsage = codeSigning
nsComment = "OpenSSL Generated Certificate"

在 home 目录使用 openssl rand -writerand .rnd 创建随机数文件。

使用如下命令创建密钥对:

1
2
3
4
5
openssl req -config ./openssl.cnf \
-new -x509 -newkey rsa:2048 \
-nodes -days 36500 -outform DER \
-keyout "MOK.priv" \
-out "MOK.der"

将创建的证书转换为 PEM:

1
openssl x509 -in MOK.der -inform DER -outform PEM -out MOK.pem

用 PEM 文件签署内核文件:

1
sudo sbsign --key MOK.priv --cert MOK.pem /boot/vmlinuz-4.19.260 --output /boot/vmlinuz-4.19.260

随后使用 mokutil 命令注册密钥:

1
sudo mokutil --import MOK.der

按照提示输入密码。随后重新启动系统。在加载 GRUB 之前 shim 的蓝屏界面选择“注册 MOK”,然后跟随菜单完成注册过程。

选择 Enroll MOK

可以选择 View key 0 查看密钥属性,也可以直接选择 Continue 继续。

选择 Yes 导入,随后输入使用 mokutil 命令注册密钥时设置的密码。

选择 Reboot 重新启动。

修改 GRUB 默认配置

因为是降级安装,在更新引导项时编译的新内核并不会被设为默认启动项,这里可以编辑 /etc/default/grub,将 GRUB_TIMEOUT_STYLE=hidden 注释以显示 GRUB 引导菜单,并修改 GRUB_TIMEOUT=0 的数值大于 0 以延长选择时间。在新内核测试成功后还可以修改 GRUB_DEFAULT=0 以默认直接启动至新内核。

若新内核无法通过 UUID 启动,可以取消文件中 GRUB_DISABLE_LINUX_UUID=true 的注释。

修改完成后使用 sudo update-grub2 更新引导配置。

安装完成

配置完成后重新启动选择新内核即可。

重启前:

1
2
neko@ubuntu:~$ uname -a
Linux ubuntu 5.15.0-48-generic #54-Ubuntu SMP Fri Aug 26 13:26:29 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

重启后:

1
2
neko@ubuntu:~$ uname -a
Linux ubuntu 4.19.260 #2 SMP Tue Oct 4 08:08:01 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

可能出现的问题

缺少 system trusted key 文件

Ubuntu 系统中直接使用 /boot/config-$(uname -r) 配置编译内核可能会出现如下问题:

1
2
3
make[1]: *** 没有规则可制作目标“debian/canonical-certs.pem”,由“certs/x509_certificate_list” 需求。 停止。
make: *** [Makefile:1073:certs] 错误 2
make: *** 正在等待未完成的任务....

Ubuntu 的默认配置会使用其自身的证书来签名内核模块,这个证书在从 kernel.org 中下载的内核源代码包中不存在。

可选方案:

  • 不使用指定的签名证书。编辑 .config 文件,找到 CONFIG_SYSTEM_TRUSTED_KEYS="debian/canonical-certs.pem",将双引号中内容删除,这样内核编译时会生成一次性的证书来签名内核模块。
  • 自己生成签名证书。
  • 使用 Ubuntu 仓库/源中的内核源码包(或提取其中的证书文件)。
  • 关闭模块签名机制。

这里选择第一种方式,具体操作请参考配置内核签名文件

Hyper-V 虚拟机驱动缺失

在不设置 Hyper-V 相关选项直接编译安装时可能会发生如下错误:

1
2
3
4
5
6
7
VFS: Cannot open root device "mapper/ubuntu--vg-ubuntu--lv" or unknown-block(0,0): errpr -6
Please append a correct "root=" boot option: here are the available partitions:
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)

... ...

---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---

一开始以为是因为开始 LVM 的原因,经过多次尝试后,尝试在无 LVM 虚拟机上以同样步骤构建内核,发现有同样的问题,仔细分析之后发现内核没有找到硬盘分区,猜想是磁盘驱动问题,解决方案请参考配置 Hyper-V 第二代虚拟机驱动

PS:加上驱动后还是有这个问题,但是变化是 here are the available partitions: 后面有几行详细的分区信息了。LVM 分区还是有问题,始终无法解决,只好放弃带 LVM 系统的内核编译。

编译安装完成后重启直接进入原内核

因为是降级内核,GRUB 生成引导菜单时会将更新的内核作为默认启动项,可以通过修改 GRUB 默认启动项解决,也可以修改 GRUB 配置后在启动菜单手动选择旧版内核启动。解决方案请参考修改 GRUB 默认配置

内核签名错误

GRUB 加载内核后会出现 error: bad shim signature 错误,是因为开了 UEFI 安全启动但没有对内核进行签名,解决方案请参考内核签名

参考文献

  1. HyperV Kernel Configuration
  2. 【译】如何为 Secure Boot 签名