GRUB 源代码分析:打开 LUKS 加密的 boot 磁盘分区的过程
1. 安装 GRUB 时生成加密配置文件
在安装 GRUB 的过程中,grub-install
会检测 --boot-directory
参数对应的磁盘分区是否为加密盘。如果检测到是加密盘,生成的配置文件中会包含 cryptomount
命令。这个命令非常重要,因为它在 GRUB 启动时用于挂载加密盘。若缺少这一步,GRUB 将无法识别并解锁加密盘。
- 注意,这里的
boot
分区并不是ESP
分区,ESP
分区不能被加密。 - 这里的
boot
分区是存放Linux 内核镜像
和initramfs
的分区。
1.1 grub.cfg
配置示例
生成的 grub.cfg
文件通常包含以下内容:
- 注意,这个
grub.cfg
配置文件是在ESP
分区中的,用于引导加载过程的第一阶段, 打开加密盘之前的配置。 configfile $prefix/grub.cfg
指定的这个配置文件,才是用于加载 Linux 内核的配置文件,通常位于加密的boot
分区中。
cryptomount -u 868ca75c-932a-4d20-aa53-05101e356bc2
search.fs_uuid 6a31fab0-7b6d-49d4-95b2-53ed48ebeabf root cryptouuid/868ca75c932a4d20aa5305101e356bc2
set prefix=($root)'/grub'
configfile $prefix/grub.cfg
在这里,cryptomount
使用 UUID 来标识并解锁特定加密磁盘,从而为后续的文件系统挂载和配置文件加载提供必要的支持。
2. GRUB 挂载加密盘的过程
2.1 加密盘模块的初始化
GRUB 在启动时会首先初始化加密盘相关模块,并注册 cryptomount
命令。相关代码如下:
static struct grub_disk_dev grub_cryptodisk_dev = {
.name = "cryptodisk",
.id = GRUB_DISK_DEVICE_CRYPTODISK_ID,
.disk_iterate = grub_cryptodisk_iterate,
.disk_open = grub_cryptodisk_open,
.disk_close = grub_cryptodisk_close,
.disk_read = grub_cryptodisk_read,
.disk_write = grub_cryptodisk_write,
#ifdef GRUB_UTIL
.disk_memberlist = grub_cryptodisk_memberlist,
#endif
.next = 0
};
GRUB_MOD_INIT(cryptodisk)
{
grub_disk_dev_register(&grub_cryptodisk_dev);
cmd = grub_register_extcmd("cryptomount", grub_cmd_cryptomount, 0,
N_("[ [-p password] | [-k keyfile"
" [-O keyoffset] [-S keysize] ] ] [-H file]"
" <SOURCE|-u UUID|-a|-b>"),
N_("Mount a crypto device."), options);
grub_procfs_register("luks_script", &luks_script);
}
grub_disk_dev_register
把加密盘模块注册成一个磁盘设备,在grub_disk_open
时会调用到这里的回调函数grub_cryptodisk_open
。
当然,还会初始化具体的加密模块,例如 LUKS
:
GRUB_MOD_INIT(luks)
{
COMPILE_TIME_ASSERT(sizeof(((struct grub_luks_phdr *)0)->uuid)
< GRUB_CRYPTODISK_MAX_UUID_LENGTH);
grub_cryptodisk_dev_register(&luks_crypto);
}
LUKS
通过 grub_cryptodisk_dev_register
注册到加密盘模块中,也就是把 LUKS
添加到全局加密模块列表 grub_cryptodisk_list
中。后面 cryptomount
会利用这个加密模块列表来匹配和解锁参数中指定的加密磁盘。
通过以上代码,GRUB 的加密磁盘支持被系统注册和准备好,用于解锁指定加密设备。
2.2 执行挂载加密盘命令
做好准备工作后,GRUB 会执行 grub.cfg
配置文件中的指令,第一条就是 cryptomount
命令,cryptomount
会触发调用 GRUB 源代码中的 grub_cmd_cryptomount
函数。这一函数的作用是扫描并尝试挂载所有支持的加密盘。
2.2.1 扫描加密盘的过程
grub_cmd_cryptomount
会调用 grub_cryptodisk_scan_device_real
来遍历 grub_cryptodisk_list
,并调用对应的 scan
函数来检测是否支持该加密盘格式。如果找到匹配的加密盘,则继续解锁并插入到全局的加密盘列表 cryptodisk_list
中。
示例代码如下:
for (cr = grub_cryptodisk_list; cr; cr = cr->next)
{
dev = cr->scan(source, cargs);
if (grub_errno)
goto error_no_close;
if (!dev)
continue;
ret = cr->recover_key(source, dev, cargs);
if (ret != GRUB_ERR_NONE)
goto error;
ret = grub_cryptodisk_insert(dev, name, source);
if (ret != GRUB_ERR_NONE)
goto error;
}
recover_key
函数会设置好用于磁盘加解密的密钥,为后续的磁盘读写做好准备。
3. GRUB 打开加密盘的过程
3.1 search.fs_uuid
的处理流程
- 一旦加密盘成功挂载,
search.fs_uuid
命令会调用grub_device_open
->grub_disk_open
来完成磁盘打开操作。
3.1.1 grub_disk_open
函数的处理逻辑
grub_disk_open
会依次调用已注册的磁盘模块中的dev->disk_open
函数来尝试打开磁盘。如果某个模块能够成功打开指定磁盘,就会停止后续调用。否则,继续尝试下一个模块的open
函数。- 还记得吗,在 GRUB 初始化时,通过
grub_disk_dev_register
函数注册了一个磁盘模块。 - 所以这里最终会调用到 GRUB 初始化时注册的
grub_cryptodisk_dev->grub_cryptodisk_open()
函数来打开加密盘。
- 还记得吗,在 GRUB 初始化时,通过
- 由于加密盘已经在
cryptomount
时被插入到了cryptodisk_list
列表中,grub_cryptodisk_open
可以顺利找到磁盘。
static grub_err_t
grub_cryptodisk_open(const char *name, grub_disk_t disk)
{
if (grub_memcmp(name, "cryptouuid/", sizeof("cryptouuid/") - 1) == 0)
{
for (dev = cryptodisk_list; dev != NULL; dev = dev->next)
if (grub_uuidcasecmp(name + sizeof("cryptouuid/") - 1, dev->uuid, sizeof(dev->uuid)) == 0)
break;
}
if (dev == NULL) {
return GRUB_ERR_UNKNOWN_DEVICE;
}
disk->data = dev;
disk->log_sector_size = dev->log_sector_size;
disk->total_sectors = dev->total_sectors;
disk->max_agglomerate = GRUB_DISK_MAX_MAX_AGGLOMERATE;
disk->id = dev->id;
dev->ref++;
return GRUB_ERR_NONE;
}
3.2 加载 boot 分区中的 grub.cfg
- 一旦
grub_disk_open
成功打开加密盘,search.fs_uuid
会将$root
变量的值设置为cryptouuid/868ca75c932a4d20aa5305101e356bc2
,用于后续的文件系统操作和引导加载。 configfile $prefix/grub.cfg
命令就可以成功打开加密盘中的配置文件了。- 然后加载 GRUB 菜单,并继续引导流程。
4. 磁盘透明加解密的过程简述
由于本文重点讨论的是加密盘打开的过程,这里只是简要说明一下加密磁盘读写的过程,以保持逻辑的完整性。
- 还记得在 GRUB 初始化时注册的
grub_cryptodisk_dev
结构吗?读写磁盘时最终会调用到这里的函数。- 例如在从磁盘读取数据时会调用到
.disk_read = grub_cryptodisk_read
。 - 在
grub_cryptodisk_read
中会先使用grub_disk_read
从磁盘读取加密的数据,然后调用grub_cryptodisk_endecrypt
解密数据后返回。 - 写入数据的过程也类似,先调用
grub_cryptodisk_endecrypt
加密数据,然后再写入磁盘。
- 例如在从磁盘读取数据时会调用到
- 由于在
cryptomount
时已经设置好了密钥,所以grub_cryptodisk_endecrypt
可以正确地进行加解密。