在基于Busybox的Initramfs(Arch Linux mkinitcpio)中向休眠Cryptsetup进程传递解锁密码的技术问题
解决initramfs中向休眠的cryptsetup进程传递密码的问题
首先得明确你遇到的核心问题:直接写入/proc/200/fd/0或/dev/console没用,是因为你混淆了终端的输入和输出路径——写入/dev/console是输出到屏幕,而cryptsetup是从终端的输入缓冲区读取密码,不是从这个设备的写入端读取。
正常场景下的密码输入流程
当你在控制台执行cryptsetup open --type luks /dev/vda2 root后,进程进入休眠的本质是它在调用read()系统调用等待终端输入:
- 从你贴的
interactive_pass代码片段可以看到,cryptsetup优先尝试打开/dev/tty(进程的控制终端)作为输入输出fd;如果失败,才回退到STDIN_FILENO(0)和STDERR_FILENO(2)。 - 当用户在键盘输入密码并回车时,每个字符会被内核的输入子系统处理,插入到对应终端(比如
/dev/tty0)的输入缓冲区中。 - 内核检测到输入缓冲区有数据后,会唤醒处于休眠状态的cryptsetup进程,
read()调用返回读取到的字符(包括回车),之后cryptsetup就会进行解密操作。
可行的解决方案
要向休眠的cryptsetup进程传递密码,你需要模拟键盘输入,将字符插入到它正在监听的终端的输入缓冲区,而不是直接写入设备文件或进程fd。以下是具体步骤:
1. 编译一个静态二进制工具来模拟键盘输入
编写一个简单的C程序(比如叫sendpass),利用TIOCSTI ioctl向终端输入缓冲区插入字符:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/ioctl.h> #include <termios.h> #include <string.h> int main(int argc, char *argv[]) { if (argc != 3) { fprintf(stderr, "Usage: %s <tty-device> <password>\n", argv[0]); return 1; } int fd = open(argv[1], O_RDWR); if (fd == -1) { perror("Failed to open tty device"); return 1; } const char *pass = argv[2]; // 逐个插入密码字符 for (size_t i = 0; pass[i] != '\0'; i++) { if (ioctl(fd, TIOCSTI, &pass[i]) == -1) { perror("Failed to insert character"); close(fd); return 1; } } // 插入换行符模拟按下回车 char newline = '\n'; if (ioctl(fd, TIOCSTI, &newline) == -1) { perror("Failed to insert newline"); close(fd); return 1; } close(fd); return 0; }
编译成静态二进制(确保在initramfs中无需动态链接库):
gcc -static sendpass.c -o sendpass
2. 将工具加入initramfs
修改你的mkinitcpio配置,把sendpass工具添加到initramfs中:
- 在
/etc/mkinitcpio.conf的BINARIES数组中加入sendpass:BINARIES=(sendpass) - 重新生成initramfs:
mkinitcpio -P
3. 远程登录后传递密码
当你通过Dropbear远程登录到initramfs环境后,执行以下命令(假设cryptsetup监听的是/dev/tty0,如果不确定可以尝试其他/dev/tty*设备):
sendpass /dev/tty0 "your-luks-password"
这个命令会把密码字符和换行符插入到控制台终端的输入缓冲区,内核会唤醒休眠的cryptsetup进程,读取到密码后即可完成解密,继续启动流程。
为什么之前的方法无效?
- 直接写入
/proc/200/fd/0:虽然这个fd指向/dev/console,但写入这个fd是向控制台输出内容,而不是作为输入传递给进程——cryptsetup是从终端的输入缓冲区读取,不是从这个fd的写入端读取。 - 写入
/dev/tty*设备文件:同样,直接写入设备文件是输出到屏幕,不会进入输入缓冲区,因此cryptsetup的read()调用不会被唤醒。
内容的提问来源于stack exchange,提问作者zar3bski




