heads icon indicating copy to clipboard operation
heads copied to clipboard

Introduce io386 to heads and use it to finalize chipset at runtime

Open persmule opened this issue 7 years ago • 36 comments

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.)

persmule avatar Feb 24 '18 07:02 persmule

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.

osresearch avatar Mar 14 '18 14:03 osresearch

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.

persmule avatar Mar 15 '18 04:03 persmule

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 avatar May 27 '19 12:05 merge

@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.

persmule avatar May 27 '19 15:05 persmule

@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.

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?

merge avatar May 27 '19 16:05 merge

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.

persmule avatar May 27 '19 16:05 persmule

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.

merge avatar May 27 '19 17:05 merge

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 avatar Nov 28 '19 10:11 merge

@merge Please tell us on which hardware and software environment you did your first test.

persmule avatar Nov 29 '19 17:11 persmule

@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

merge avatar Dec 02 '19 07:12 merge

@persmule ?

tlaurion avatar Dec 13 '19 14:12 tlaurion

@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.

persmule avatar Dec 07 '20 05:12 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.

Is this something which could be repurposed to add more general write-protect support to Flashrom?

ln2max avatar Mar 03 '21 13:03 ln2max

@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 avatar Sep 07 '21 17:09 tlaurion

@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 avatar Sep 10 '21 20:09 MrChromebox

@MrChromebox tagged you under #881

tlaurion avatar Sep 11 '21 00:09 tlaurion

@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 avatar Sep 20 '21 23:09 MrChromebox

@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?

tlaurion avatar Sep 20 '21 23:09 tlaurion

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 avatar Sep 21 '21 01:09 MrChromebox

@MrChromebox so basically, just having lockdown not set should do it. 4.15 locks it by default. Will retest

tlaurion avatar Sep 25 '21 04:09 tlaurion

@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 avatar Sep 28 '21 16:09 tlaurion

@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 avatar Sep 28 '21 17:09 MrChromebox

@MrChromebox this was on top https://github.com/osresearch/heads/pull/1015 (coreboot 4.13)

tlaurion avatar Sep 28 '21 18:09 tlaurion

@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: heads-io386-version

Without calling lock_chip, Flashing ROM

heads-io386-flash-without-lock

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

heads-io386-flash-same-rom-with-lock

Then attempting to flash new ROM, which fails

heads-io386-flash-new-rom-with-lock

tlaurion avatar Sep 28 '21 18:09 tlaurion

Having chipset_lockdown unset but without boot_media_lock_controller set

lock_chip does nothing and permit flashing prior and after the call.

tlaurion avatar Sep 28 '21 18:09 tlaurion

@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

MrChromebox avatar Sep 28 '21 18:09 MrChromebox

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.

tlaurion avatar Sep 29 '21 13:09 tlaurion

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 avatar Sep 29 '21 15:09 MrChromebox

@MrChromebox any input here on how it is supposed to be for other platforms?

tlaurion avatar Oct 08 '21 22:10 tlaurion

@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

MrChromebox avatar Oct 08 '21 23:10 MrChromebox