在写下上一篇coreboot的粗浅认识之后,我基本上把所有的payload都试了一遍,然后最终决定所有的(除了商业支持的depthcharge等)payload都是垃圾,主要原因是实现功能完整的bootloader难度不亚于一个微型操作系统,次要原因是coreboot的功能实在过于强大(因为设计给x86这种巨型系统的)导致嵌入式社区也无法有效利用和反馈(相比之下U-Boot的社区活跃多了)。
在所有的常见的通用型payload中,SeaBIOS和edk2完全不提供安全性,grub2提供少量的安全性,dasharo提供了不错的功能和安全性但是和我的设想不太一致。 只有LinuxBoot提供了强大的定制能力,强大到我可以实现我的设计目标的同时把我需要面对的威胁全部覆盖住。
LinuxBoot,即使用Linux内核这个几乎不依赖任何BIOS功能和驱动的成熟而活跃的大型软件来完成coreboot之后的所有启动初始化工作,并最终将控制权转交给实际的操作系统的方案。之前提到过Heads是一个设计目标明确的LinuxBoot,其目标用户群往往需要了解自身面对的安全威胁,这个和Tails这种设计目标是对自身面对的安全威胁不甚了解的人群相差甚远,导致很多人刷Heads可能根本就没闹明白我为什么要这个,以及这个到底干嘛了,反而是对Heads繁杂的要求和欠缺的保护大为吐槽。对于我来说,我所面临的安全威胁并不需要Heads的模式,而我不愿意承担Heads的代价。那么,我的显而易见的选择就是制作自己的LinuxBoot。
啊这个不难啊,不是有一个项目就叫做LinuxBoot吗?啊……这个真的帮不了我,因为coreboot其实已经集成了LinuxBoot,但是没有提供LinuxBoot的定制,毕竟这东西的可定制性基本就是无上限,提供了也没什么用。所以这里缺失的是设计和组装LinuxBoot,而这等同于制作一个发行版。是的,一个LinuxBoot就是一个完整的发行版,而这个发行版的基本功能要求为:
- 能驱动内置存储器
- 能加载操作系统本身或其bootloader
- 能移交控制权给操作系统
重要功能为:
- 能驱动外部存储器(用于安装/修复系统)
- 能驱动显示器和键盘(用于中断启动或者进行配置)
- 能对非核心系统环境进行早期初始化
客户需求为:
- 全盘加密,没有任何明文内容
- 在设计安全范围内无需用户交互完成启动
- 不考虑硬件拆解(包括开壳)的情况下没有攻击面
目标平台是X230,就是玩coreboot的基本上都玩过的那个ThinkPad X230笔记本电脑,配备了TPM 1.2,但是有一个不需要开壳就能拆卸的SATA硬盘位(这可是功能啊,但是在安全威胁日益增加的今天这个功能成了缺陷),因此硬盘上不能有任何明文是刚需。Linux天然可以解锁luks2这个优势使得不需要任何复杂的外部软件就可以实现真全盘加密,而密钥的保存就需要交给TPM了。
我这里忽略一切除了上面这个目标以外的事情,例如怎么驱动SATA和怎么kexec,这些网上都有,重点说说网上没有的。
TPM是一个设计拉垮,商家不买账用户也不买账的东西,其主要设计目的是实现DRM,即你买了这个数字音乐才能听,而且你能听你不能拷给别人听。因为设计实在是拉垮,最后把DRM这个功能几乎完全从最终的TPM 1.2规范中删除了,保留下来的都是一些通用的安全功能。除开安全协议有点老(比如只有SHA1和RSA2048),其实TPM 1.2是一个很纯粹的设计,相比之下TPM 2.0是终于把一开始的设计目标实现了:它除开通用安全功能以外最主要提供的是远程管理。但是不管怎么说,TPM 1.2落后了,所有的社区实现都不支持TPM 1.2进行启动密钥管理。因此,第一个挑战就是,凭空驱动TPM 1.2。
TPM协议栈最早由IBM设计,年代非常非常久远(2006年的)而且秉承IBM的一贯尿性,以至于我看到原始的IBM版TCG TPM Software Stack(TSS)实现的时候我觉得这个东西就是90年代写的。其设计思路是,TPM芯片本身不是多用户的,那么在多用户环境下我们就需要一个中心汇聚器,因此我们需要一个守护进程来作为TPM的唯一入口,这个入口叫做裤子TrouSerS。然后用户要和TPM交互的时候使用一些工具和裤子通讯,这个工具包叫做tpm-tools(为什么不叫裤腰带或者裤兜?)。出于原本的DRM的考虑,这个守护进程甚至可以远程运行,因此裤子和裤兜都支持TCP通讯。那么,要完整支持这个方案,我们就需要:1、多用户支持,2、TCP协议栈支持。还好我们不需要真的激活网卡,不然就是个筛子了,但是TCP本身就是一个极其复杂导致攻击面巨大,而且对于我们这个项目来说,体型过于巨大的存在。
没事,Linux用户都会自己修bug。因此我们要做的第一件事就是patch掉这两个工具的通讯协议,使得它们通过unix socket通讯,这样我们只需要启用基本网络栈,而且也不需要操心鉴权等问题因为unix socket天然就有文件系统权限保护着。
第一版上swtpm测试,首先就发现这东西设计的坑爹是名不虚传,它居然没法判定TPM目前是不是已经配置(owned)的状态,状态猜错了所有操作报错。翻了很久的贴子(毕竟快20年前的东西了)发现裤子启动的时候会去检查磁盘上是不是存在TPM ownership state文件(/var/lib/tpm/system.data),有就是owned,但是这个说是state文件,其实就是一个boolean,因为只要是owned这个文件的内容都一样……这个好办,Linux内核是可以准确看到TPM状态的,因此读一下/sys/class/tpm/tpm0/owned,如果unowned把state删掉就好。
第二版上swtpm测试,可以和TPM通讯了哎。然后撞到第二个坑爹设计,tpm-tools里面的工具大概没什么人真的用,都是鼓励你用API去二次开发的,导致各种缺功能。我们操作TPM的nvram的时候需要先检查是不是存在nvram项目以及配置是不是正确,然而tpm_nvinfo工具的输出是不可机读的,要可靠读取显然很困难。没事,Linux用户继续修bug,把tpm_nvinfo改成可机读。然后很多工具的命令行参数不一致(比如不支持命令行提供owner密码),没事,我接着patch。不支持OpenSSL 3.0,没事,我还是patch。原版的tpm-tools没有tpm_extendpcr,没事,我提供。
别问我为什么不用libtpms,一是我不想自己开发,二是我当时不知道有这么个东西。
这个过程大概持续了一个月(纯兴趣驱动,代码改得其实不多),终于可以实现:
- 可以对TPM进行清空和初始化
- 可以读写NVRAM的定义
- 可以将二进制数据封存在NVRAM中并和当前PCR值绑定
- 可以封锁对TPM的再次操作
整个安全机制的设计为BIOS完全接管TPM,操作系统不能使用TPM。硬盘上双分区,boot是一个BIOS可解锁的普通分区,和普通的boot分区没有很大区别,但是里面存放着kernel bootargs并且用luks2加密。之后的分区有几个,是不是lvm其实就没有分别了,但是为了加快开机速度,使用单luks2保护的lvm2来存放其他所有数据。
BIOS会对整个BIOS所有的内容进行measure,这个是coreboot自带的功能,其结果在PCR2当中。解锁boot的密钥和PCR2绑定,即如果BIOS被修改,boot分区即无法解锁。BIOS会将Linux kernel、initramfs和bootargs三个measure到PCR4当中,但是这个PCR并不绑定任何NVRAM,而是OS启动之后可以借此验算磁盘上的文件没有被修改。因为没找到很好的办法对内存中的内核和initramfs进行校验,只能寄希望OS启动的尽早阶段如initramfs阶段就对此进行校验,此时主分区还没有解锁,initramfs是从luks2保护的boot分区读取的,不易遭到篡改。之所以PCR4不直接绑定主分区的解锁密钥是因为在TPM 1.2中是无法对PCR值进行预先绑定的。如果你有TPM2,升级内核的时候你可以算出来正确的PCR值并且预先设定策略允许此新的PCR值在下一次启动时解锁主分区,这样可以用户无感升级。但是TPM 1.2必须是你对PCR直接进行extend之后将NVRAM绑定为”当前的PCR值“,无法指定PCR值,所以你升级内核就必然要输入密码了。
BIOS的防止未授权更改部分非常简单,就是不告诉你怎么打断启动。通常BIOS都会给你显示一句Press F2 to interrupt boot然后给你点时间打断它,但是我的设计是你根本不知道什么键位在什么时机打断(说了就不灵了哈哈)。如果启动被打断,或者启动流程失败(硬盘被拔出、替换、覆写等),那么会启动一个标准的Linux终端登录。这个登录的密码同样保存在TPM中,不可绕过,和通常的BIOS管理密码类似。
一旦OS加载,你就可以使用任何方法来继续解锁主分区了。由于要修改boot分区等效于你获得了root权限,获得了root权限的机器是没有任何可信度的,因此BIOS并不对内核进行签名校验(不过做了个Secure Boot Key Verification,反正不难),但是还是可以认为initramfs可信,所以你可以用常规手法把密钥存放在initramfs里。
你如果问,LinuxBoot的最大弊端就是无法更新,因为升级内核就需要刷BIOS,怎么解决?首先,这个BIOS的确允许你在输入BIOS密码之后刷写,所以非得要升级也不是不可以。但是这个设计的最大要点就是,没有攻击面。不考虑开壳硬刷BIOS或者窃听TPM通讯的话(这俩都不是X230能防御的,缺少BootGuard,属于硬件缺陷),BIOS正常启动流程里面通常的高危设备例如USB、网卡等等驱动统统都没有加载,除了主硬盘、显示器和键盘,没有其他硬件了。主硬盘被luks2保护,攻击等同于击穿luks2。显示器实际上是coreboot驱动的,Linux只是继续利用,所以攻击等同于击穿coreboot。唯一的入口就是键盘,而这个的攻击面在于Linux的终端驱动和键盘驱动没有漏洞,否则的话你就等效于可以直接对任何一台服务器的console login进行爆破了。
很显然,这个BIOS没有任何花哨的功能,因为它设计来就是在X230上提供一个中等安全性和高度便利性的方案。是的它的安全模型是低于Heads的,但是我不需要对抗这么高级的对手,我更希望它省心和防呆,所以它有且仅有一种工作模式。
最后,这个BIOS的主要缺点是什么?慢,非常慢,但是比Heads快。主要的原因在于考虑到启动成功率,SPI flash的读取速率是有限制的,通常是大大低于最大速度。其次,TPM extending的速度,比flash的速度又慢了一大半。最后是LinuxBoot本身就需要先启动一次系统,这里面有一些硬件比如SATA是需要时间初始化的,然后额外的两次luks2解锁消耗时间,外加上其他林林总总的。最后结果就是我的BIOS能在25秒内进桌面,不管是冷启动还是热启动。这个比起一般的BIOS能在15秒内完成就算是很慢的了,不过能有一台让人安心的便携式电脑,这个还是值得的。