heads
heads copied to clipboard
Introduce io386 to heads and use it to finalize chipset at runtime
On some newer platforms of intel (confirmed on nehalem, sandy/ivy bridge), coreboot after commit 2ac149d294af795710eb4bb20f093e9920604abd registers an SMI to lockdown some registers on the chipset, as well as access to the SPI flash, optionally. The SMI will always be triggered by coreboot during S3 resume, but can be triggered by either coreboot or the payload during normal boot path.
Enabling lockdown access to SPI flash will effectly write-protect it, but there is no runtime option for coreboot to control it, so letting coreboot to trigger such SMI will leave the owner of the machine lost any possibility to program the SPI flash with its own OS, and becomes a nightmare if the machine is uneasy to disassemble, so a scheme could be implement, in which the SMI to lockdown chipset and SPI flash is left for a payload to trigger, and temporarily disabling such triggering in order to program the SPI flash needs authentication.
I have implemented a passcode-protected runtime-disableable lockdown with grub, described here. In order to implement a similar scheme for Heads, I wrote io386.
With this commit, io386 will be called before entering boot routine to trigger the SMI to finalize the chipset and write protect the SPI flash at the same time. Entering recovery shell will leave the flash writable.
(The authentication routine implemented in previous revisions has been split as an independent commit.)
This seems like a generally useful sort of tool. What do you think about merging the io386 and flashtools trees? They have some overlap (flashtools provides peek and poke to do raw memory, for instance), so adding inb/outb/ioperm etc seems like a natural extension of those tools.
I follow the Unix philosophy to do one thing and do it well. Your flashtools is a simplified flashrom, while hardenedlinux's io386 is a tool dedicated for I/O operations. I do not believe that they should be merged.
Thanks for writing this! I guess we'll need something like this sooner or later. Could you link to datasheets or docs you used? Where does 0x2b and 0xcb come from? What exactly does coreboot's LOCK_SPI_FLASH_RO differently or not? thanks!
ps. IMO it would make sense to add io386 or something similar to the flashtools repo.
@merge
Could you link to datasheets or docs you used?
I did these by imitating what coreboot does.
Where does 0x2b and 0xcb come from?
They are inside src/include/cpu/x86/smm.h of coreboot source tree. You can see #define APM_CNT 0xb2 and #define APM_CNT_FINALIZE 0xcb, and know how coreboot uses these by grepping them.
What exactly does coreboot's LOCK_SPI_FLASH_RO differently or not?
Setting LOCK_SPI_FLASH_RO will lock the SPI flash BEFORE any payload is called, so one have no way even to disable the locking temporarily at runtime (but locking done with grub or heads can be disabled at runtime allowing the owner to (re) program the SPI flash in-system, and one can write codes for authentication to protect it from unauthorized access). On platforms whose SPI flash is hard to access (e.g. accessing the flash needs to tear down the whole machine), setting an SPI lock impossible to disable at runtime will be a nightmare to update their firmware.
@merge
Could you link to datasheets or docs you used?
I did these by imitating what coreboot does.
Where does 0x2b and 0xcb come from?
They are inside src/include/cpu/x86/smm.h of coreboot source tree. You can see
#define APM_CNT 0xb2and#define APM_CNT_FINALIZE 0xcb, and know how coreboot uses these by grepping them.
good. thanks.
What exactly does coreboot's LOCK_SPI_FLASH_RO differently or not?
Setting LOCK_SPI_FLASH_RO will lock the SPI flash BEFORE any payload is called, so one have no way even to disable the locking temporarily at runtime (but locking done with grub or heads can be disabled at runtime allowing the owner to (re) program the SPI flash in-system, and one can write codes for authentication to protect it from unauthorized access).
what do you mean by "locking in heads (using io386?) can be disabled at runtime"? That seems different to what coreboot does(?). I guess what we need is a irrevesable one-time write-protection that is off at power-on. maybe I got you wrong though..
On platforms whose SPI flash is hard to access (e.g. accessing the flash needs to tear down the whole machine), setting an SPI lock impossible to disable at runtime will be a nightmare to update their firmware.
We configure coreboot not to "finalize"/"write-protect" the SPI flash and do it ourselves, (at least) just before we kexec away on disc. That way we have all our flashrom upgrade functions in Heads available, and no need to disassemble, right?
what do you mean by "locking in heads (using io386?) can be disabled at runtime"? That seems different to what coreboot does(?). I guess what we need is a irrevesable one-time write-protection that is off at power-on. maybe I got you wrong though..
It is the same as what coreboot does, and locking in heads (using io386?) can indeed be disabled at runtime by not to call the io386 itself, as long as the wrapping scripts allows it.
We configure coreboot not to "finalize"/"write-protect" the SPI flash and do it ourselves, (at least) just before we kexec away on disc.
That at least is a way to make things easier.
That way we have all our flashrom upgrade functions in Heads available, and no need to disassemble, right?
It is a policy, not a mechanism, while leaving the flash temporarily unlocked is a mechanism. With it, the owner can choose to kexec into the OS ,or even chroot to the rootfs on disk to update the firmware, not limited to your "flashrom upgrade functions in Heads" stuff.
thanks. yes, that's policy, and I think we can leave that an open issue in Heads. Denying anything else than Heads (still the user) to write, helps and is a good start.
TPM measurements won't succeed after changes in flash anyways, and we can come up with more, later.
Did a first test of this by just calling lock_chip somewhere in the (gui) menu before I boot, but nothing really changed: I could reprogram the flash (flashrom -p internal) just like before. has anyone else tested this?
@merge Please tell us on which hardware and software environment you did your first test.
@merge Please tell us on which hardware and software environment you did your first test.
on the X230. I run Heads' master branch ( a few commits back ) plus https://github.com/osresearch/heads/pull/568 which is only a coreboot bump from 4.8 to 4.11. How do you test this? thanks
@persmule ?
@merge The SPI flash locking mechanism is rewritten in coreboot after 78feacc44057916161365d079ae92aa0baa679f8, in which "what to be locked" and "how to perform locking" should also be configured with flags defined in src/security/lockdown/Kconfig, otherwise none flash region is locked by default.
These settings does not take effect until the SMI is triggered via outb(0x2b, 0xcb), either by coreboot itself (if INTEL_CHIPSET_LOCKDOWN=y) or by later stages.
@merge The SPI flash locking mechanism is rewritten in coreboot after 78feacc44057916161365d079ae92aa0baa679f8, in which "what to be locked" and "how to perform locking" should also be configured with flags defined in src/security/lockdown/Kconfig, otherwise none flash region is locked by default.
Is this something which could be repurposed to add more general write-protect support to Flashrom?
@persmule @merge @osresearch
EDIT: tested and works!
Changes to streamline this PR (and make it effective into both generic-init and gui-init with reduced required changes and maintenance in future):
- Removal of changes in generic-init.
- usb-init and kexec-select-boot and all other heads boot methods are at the end calling kexec-boot to kexec. So lockdown happens only there (removed other occurences under generic-init)
- Coreboot CONFIG_BOOTMEDIA_LOCK_CONTROLLER is set, while CONFIG_INTEL_CHIPSET_LOCKDOWN is not.
- x230-hotp-maximized board config adds the required config option CONFIG_IO386
Here is the patch on top of #1015 (will test later):
diff --git a/boards/x230-hotp-maximized/x230-hotp-maximized.config b/boards/x230-hotp-maximized/x230-hotp-maximized.config
index 7fe317d..2b60112 100644
--- a/boards/x230-hotp-maximized/x230-hotp-maximized.config
+++ b/boards/x230-hotp-maximized/x230-hotp-maximized.config
@@ -26,6 +26,7 @@ CONFIG_UTIL_LINUX=y
CONFIG_LVM2=y
CONFIG_MBEDTLS=y
CONFIG_PCIUTILS=y
+CONFIG_IO386=y
#Remote attestation support
#TPM based requirements
diff --git a/config/coreboot-x230-hotp-maximized.config b/config/coreboot-x230-hotp-maximized.config
index e5f0e43..86417a5 100644
--- a/config/coreboot-x230-hotp-maximized.config
+++ b/config/coreboot-x230-hotp-maximized.config
@@ -9,11 +9,13 @@ CONFIG_HAVE_IFD_BIN=y
CONFIG_BOARD_LENOVO_X230=y
CONFIG_LINUX_COMMAND_LINE="intel_iommu=igfx_off quiet"
CONFIG_UART_PCI_ADDR=0
+# CONFIG_INTEL_CHIPSET_LOCKDOWN is not set
CONFIG_HAVE_ME_BIN=y
CONFIG_HAVE_GBE_BIN=y
CONFIG_NO_GFX_INIT=y
CONFIG_DRIVERS_PS2_KEYBOARD=y
CONFIG_TPM_MEASURED_BOOT=y
+CONFIG_BOOTMEDIA_LOCK_CONTROLLER=y
CONFIG_CONSOLE_CBMEM_BUFFER_SIZE=0x80000
CONFIG_PAYLOAD_LINUX=y
CONFIG_PAYLOAD_FILE="../../build/x230-hotp-maximized/bzImage"
diff --git a/initrd/bin/generic-init b/initrd/bin/generic-init
index 8471c9a..fa91642 100755
--- a/initrd/bin/generic-init
+++ b/initrd/bin/generic-init
@@ -40,18 +40,12 @@ while true; do
fi
if [ "$totp_confirm" = "u" ]; then
- if [ "$CONFIG_IO386" = y ]; then
- lock_chip
- fi
exec /bin/usb-init
continue
fi
if [ "$totp_confirm" = "m" ]; then
# Try to select a kernel from the menu
- if [ "$CONFIG_IO386" = y ]; then
- lock_chip
- fi
mount_boot
kexec-select-boot -m -b /boot -c "grub.cfg"
continue
@@ -59,9 +53,6 @@ while true; do
if [ "$totp_confirm" = "y" -o -n "$totp_confirm" ]; then
# Try to boot the default
- if [ "$CONFIG_IO386" = y ]; then
- lock_chip
- fi
mount_boot
kexec-select-boot -b /boot -c "grub.cfg" \
|| recovery "Failed default boot"
diff --git a/initrd/bin/kexec-boot b/initrd/bin/kexec-boot
index fb9e9ed..ccd6be1 100755
--- a/initrd/bin/kexec-boot
+++ b/initrd/bin/kexec-boot
@@ -127,6 +127,10 @@ fi
if [ "$dryrun" = "y" ]; then exit 0; fi
+if [ "$CONFIG_IO386" = y ]; then
+ lock_chip
+fi
+
echo "Loading the new kernel:"
echo "$kexeccmd"
eval "$kexeccmd" \
@MrChromebox : this was one of the blocker to #836, which if combined with a proper authentication prior of accessing recovery shell, could be merged.
https://github.com/tlaurion/heads/commit/3343f8dcbbc608043f32a608ce85e746bba51412 PoC for x230-hotp-maximized shows that only Heads would be able to flash ROM.
The other one being a proper recovery shell authentication mechanism #881's implementation, which #361 implements correctly but leaves the user locked out if his public key expired. Discussion: https://github.com/osresearch/heads/issues/881#issuecomment-917095442
@tlaurion I like where this is going, and a quick perusal of coreboot's code seems to indicate this should work properly on newer platforms as well. So we just need to figure out the Recovery Shell piece
@MrChromebox tagged you under #881
@tlaurion CONFIG_BOOTMEDIA_LOCK_CONTROLLER=y will have coreboot lock things down in ramstage, before Heads loads, preventing Heads from updating itself, saving config/keys, etc
@mrchromebox CONFIG_INTEL_CHIPSET_LOCKDOWN=y would lock in coreboot. Having it undefined makes the payload responsible to finish lockdown, which is what io386 does here.
You can test functionality easily from recovery by: flash-gui.sh (Flash rom. Success)
. /etc/functions lock_chip flash-gui.sh (Flash rom. Fails)
Having it undefined lets the payload responsible to lock it, which is what io386 does here.
Unless you say that the behavior changed in coreboot 4.14+ from your tests?
I'm saying you don't want CONFIG_BOOTMEDIA_LOCK_CONTROLLER=y as that will lock the chip in ramstage regardless of CONFIG_INTEL_CHIPSET_LOCKDOWN being set or not
@MrChromebox so basically, just having lockdown not set should do it. 4.15 locks it by default. Will retest
@mrchromebox: rempoving CONFIG_BOOTMEDIA_LOCK_CONTROLLER=y and calling
. /etc/functions
lock_chip
flash.sh new.rom
succeeds.
On 4.13 CONFIG_BOOTMEDIA_LOCK_CONTROLLER=y is required.
Adding it doesn't lock platform in in ramstage nor romstage.
Calling lock_chip is locking it.
user@heads-x230:~/heads$ git diff tlaurion-github/maximized_boards-coreboot-4_13
diff --git a/Makefile b/Makefile
index 4073aa1..9e51476 100644
--- a/Makefile
+++ b/Makefile
@@ -500,6 +500,7 @@ bin_modules-$(CONFIG_LVM2) += lvm2
bin_modules-$(CONFIG_DROPBEAR) += dropbear
bin_modules-$(CONFIG_FLASHTOOLS) += flashtools
bin_modules-$(CONFIG_NEWT) += newt
+bin_modules-$(CONFIG_IO386) += io386
bin_modules-$(CONFIG_CAIRO) += cairo
bin_modules-$(CONFIG_FBWHIPTAIL) += fbwhiptail
bin_modules-$(CONFIG_HOTPKEY) += hotp-verification
diff --git a/boards/x230-hotp-maximized/x230-hotp-maximized.config b/boards/x230-hotp-maximized/x230-hotp-maximized.config
index 7fe317d..2b60112 100644
--- a/boards/x230-hotp-maximized/x230-hotp-maximized.config
+++ b/boards/x230-hotp-maximized/x230-hotp-maximized.config
@@ -26,6 +26,7 @@ CONFIG_UTIL_LINUX=y
CONFIG_LVM2=y
CONFIG_MBEDTLS=y
CONFIG_PCIUTILS=y
+CONFIG_IO386=y
#Remote attestation support
#TPM based requirements
diff --git a/config/coreboot-x230-hotp-maximized.config b/config/coreboot-x230-hotp-maximized.config
index e5f0e43..86417a5 100644
--- a/config/coreboot-x230-hotp-maximized.config
+++ b/config/coreboot-x230-hotp-maximized.config
@@ -9,11 +9,13 @@ CONFIG_HAVE_IFD_BIN=y
CONFIG_BOARD_LENOVO_X230=y
CONFIG_LINUX_COMMAND_LINE="intel_iommu=igfx_off quiet"
CONFIG_UART_PCI_ADDR=0
+# CONFIG_INTEL_CHIPSET_LOCKDOWN is not set
CONFIG_HAVE_ME_BIN=y
CONFIG_HAVE_GBE_BIN=y
CONFIG_NO_GFX_INIT=y
CONFIG_DRIVERS_PS2_KEYBOARD=y
CONFIG_TPM_MEASURED_BOOT=y
+CONFIG_BOOTMEDIA_LOCK_CONTROLLER=y
CONFIG_CONSOLE_CBMEM_BUFFER_SIZE=0x80000
CONFIG_PAYLOAD_LINUX=y
CONFIG_PAYLOAD_FILE="../../build/x230-hotp-maximized/bzImage"
diff --git a/initrd/bin/generic-init b/initrd/bin/generic-init
index b63b0a0..fa91642 100755
--- a/initrd/bin/generic-init
+++ b/initrd/bin/generic-init
@@ -58,6 +58,8 @@ while true; do
|| recovery "Failed default boot"
fi
+
+
done
recovery "Something failed during boot"
diff --git a/initrd/bin/kexec-boot b/initrd/bin/kexec-boot
index fb9e9ed..ccd6be1 100755
--- a/initrd/bin/kexec-boot
+++ b/initrd/bin/kexec-boot
@@ -127,6 +127,10 @@ fi
if [ "$dryrun" = "y" ]; then exit 0; fi
+if [ "$CONFIG_IO386" = y ]; then
+ lock_chip
+fi
+
echo "Loading the new kernel:"
echo "$kexeccmd"
eval "$kexeccmd" \
diff --git a/initrd/etc/functions b/initrd/etc/functions
index bc84840..7deab64 100755
--- a/initrd/etc/functions
+++ b/initrd/etc/functions
@@ -10,6 +10,13 @@ warn() {
echo >&2 "$*";
}
+lock_chip() {
+ APM_CNT=0xb2
+ FIN_CODE=0xcb
+ echo "Finalizing chipset"
+ io386 -o b -b x $APM_CNT $FIN_CODE
+}
+
CONFIG_LVM2=y
CONFIG_MBEDTLS=y
CONFIG_PCIUTILS=y
+CONFIG_IO386=y
#Remote attestation support
#TPM based requirements
diff --git a/config/coreboot-x230-hotp-maximized.config b/config/coreboot-x230-hotp-maximized.config
index e5f0e43..86417a5 100644
--- a/config/coreboot-x230-hotp-maximized.config
+++ b/config/coreboot-x230-hotp-maximized.config
@@ -9,11 +9,13 @@ CONFIG_HAVE_IFD_BIN=y
CONFIG_BOARD_LENOVO_X230=y
CONFIG_LINUX_COMMAND_LINE="intel_iommu=igfx_off quiet"
CONFIG_UART_PCI_ADDR=0
+# CONFIG_INTEL_CHIPSET_LOCKDOWN is not set
CONFIG_HAVE_ME_BIN=y
CONFIG_HAVE_GBE_BIN=y
CONFIG_NO_GFX_INIT=y
CONFIG_DRIVERS_PS2_KEYBOARD=y
CONFIG_TPM_MEASURED_BOOT=y
+CONFIG_BOOTMEDIA_LOCK_CONTROLLER=y
CONFIG_CONSOLE_CBMEM_BUFFER_SIZE=0x80000
CONFIG_PAYLOAD_LINUX=y
CONFIG_PAYLOAD_FILE="../../build/x230-hotp-maximized/bzImage"
diff --git a/initrd/bin/generic-init b/initrd/bin/generic-init
index b63b0a0..fa91642 100755
--- a/initrd/bin/generic-init
+++ b/initrd/bin/generic-init
@@ -58,6 +58,8 @@ while true; do
|| recovery "Failed default boot"
fi
+
+
done
Can try to test newer coreboot versions....
@tlaurion what version of coreboot are you testing against? in 4.14+, BOOTMEDIA_LOCK_CONTROLLER is used in boot_device_security_lockdown() in lockdown.c, which is called not by SMM, but before resource assignment in ramstage
edit: this looks to be the case for 4.13 too. So I'm not sure how you're able to enable this and still update via Heads, because it will already have been locked. the lock_chip() function is making an SMM call, that's completely separate
@MrChromebox this was on top https://github.com/osresearch/heads/pull/1015 (coreboot 4.13)
@MrChromebox : So this branch: https://github.com/tlaurion/heads/tree/maximized_boards-coreboot-4_13-io386_lockdown This commit: https://github.com/tlaurion/heads/commit/3343f8dcbbc608043f32a608ce85e746bba51412
Having both chipset_lockdown unset and boot_media_lock_controller set:

Without calling lock_chip, Flashing ROM

Calling lock_chip, then reflashing the same ROM (notice warning of locked regions)

Then attempting to flash new ROM, which fails

Having chipset_lockdown unset but without boot_media_lock_controller set
lock_chip does nothing and permit flashing prior and after the call.
@tlaurion then this is a highly platform-specific solution then, likely only works on Sandy/Ivy. On the L14, if BOOTMEDIA_LOCK_CONTROLLER is set, then nothing is flashable regardless if lock_chip has been called or not. I had to externally flash to clear it
Hmmm. Ok. Tested on coreboot 4.14 on x230. Same behavior.
CONFIG_BOOTMEDIA_LOCK_CONTROLLER=y
# CONFIG_INTEL_CHIPSET_LOCKDOWN is not set
When removing CONFIG_BOOTMEDIA_LOCK_CONTROLLER=y and keeping # CONFIG_INTEL_CHIPSET_LOCKDOWN is not set; calling lock_chip does absolutely nothing.
then I need to take a closer look at the SNB/IVB code to see why it's different there vs SKL and newer
@MrChromebox any input here on how it is supposed to be for other platforms?
@tlaurion it's simply not going to work on newer platforms where FSP locks those registers regardless of any coreboot settings. They simply can't be set by the payload, even in SMM.
CONFIG_INTEL_CHIPSET_LOCKDOWN has no effect on Skylake and newer. CONFIG_BOOTMEDIA_LOCK_CONTROLLER, I don't understand how it's not locking things prior to the payload execution on older platforms. I'd want to see a cbmem log from an x230 (eg) with SMM logging enabled to try and make sense of it