blob: c461db0d13efbe2ab49d68e0b05e1d5e0aa6ca5d [file] [log] [blame]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package firmware
import (
"context"
"strconv"
"strings"
"time"
"github.com/4lon/crc8"
"golang.org/x/exp/slices"
"go.chromium.org/tast-tests/cros/common/servo"
"go.chromium.org/tast-tests/cros/remote/firmware"
"go.chromium.org/tast-tests/cros/remote/firmware/fixture"
"go.chromium.org/tast-tests/cros/remote/firmware/reporters"
"go.chromium.org/tast/core/ctxutil"
"go.chromium.org/tast/core/errors"
"go.chromium.org/tast/core/ssh"
"go.chromium.org/tast/core/testing"
"go.chromium.org/tast/core/testing/hwdep"
)
const (
// The definitions of KERNEL_ANTIROLLBACK_SPACE_BYTES, KERNEL_NV_INDEX, and
// TPM_NVRAM_EXPECTED_VALUES are located in third_party/coreboot/src/security/vboot/antirollback.h.
// These definitions were originally part of the rollback_index module in the platform/vboot_reference,
// which has been split into a bottom half and top half. The top half contains generic code which hides
// the underlying storage implementation, and the bottom half implements the storage abstraction.
// With this change, the bottom half is moved to coreboot, while the top half stays in vboot_reference.
// (https://chromium-review.googlesource.com/c/chromiumos/third_party/coreboot/+/206065)
kernelAntirollbackSpaceBytes = 0xd
kernelAntirollbackSpaceBytesV10 = 0x28
kernelNvIndex = 0x1008
// TPM kernel data format version 1.0
// byte[0]: version. bit[7:4]=Major, bit[3:0]=Minor.
// byte[1]: byte size. 0x28, 40(0x28) bytes
// byte[2]: crc8
// byte[3]: flag
// byte[7-4]: kernel version. 0x00010001
// byte[39:8]: ECFW hash in sha256.
tpmNVRAMV10Header = "10 28 "
tpmNVRAMV10CRCOffset = 2
)
// TODO(seancarpenter): This test intends to check whether the kernel version
// stored in the TPM's NVRAM is corrupted during a transition from normal
// mode to dev mode (according to it's objective in an internal tool named
// test tracker). Figure out why a specific kernel version is checked for,
// and take a look at not requiring a specific kernel version. Examine
// transitioning to crossystem for the normal mode read of the kernel
// version (e.g. look at things like whether a successful exit code from
// a crossystem reading of the kernel version can establish whether the
// kernel version starts in a valid state).
var tpmNVRAMExpectedValues = []string{
"1 4c 57 52 47 1 0 1 0 0 0 0 0", "2 4c 57 52 47 1 0 1 0 0 0 0 55",
"2 4c 57 52 47 0 0 0 0 0 0 0 e8",
}
func init() {
testing.AddTest(&testing.Test{
Func: TPMNotCorruptedDevMode,
Desc: "Ensure kernel and fw version in TPM isn't corrupted in dev mode",
Contacts: []string{
"chromeos-faft@google.com",
"cienet-firmware@cienet.corp-partner.google.com",
},
BugComponent: "b:792402", // ChromeOS > Platform > Enablement > Firmware > FAFT
Attr: []string{"group:firmware", "firmware_usb", "firmware_bios", "firmware_level2"},
Requirements: []string{"sys-fw-0021-v01", "sys-fw-0024-v01", "sys-fw-0025-v01"},
Timeout: 2 * time.Hour,
SoftwareDeps: []string{"crossystem"},
ServiceDeps: []string{"tast.cros.firmware.TPMService"},
Vars: []string{"firmware.skipFlashUSB"},
Fixture: fixture.DevMode,
HardwareDeps: hwdep.D(hwdep.ChromeEC()),
})
}
// TPMNotCorruptedDevMode verifies that the kernel anti-rollback info stored
// in the TPM NVRAM, and then boots to USB and checks the firmware version
// and kernel version via crossystem for corruption.
func TPMNotCorruptedDevMode(ctx context.Context, s *testing.State) {
h := s.FixtValue().(*fixture.Value).Helper
if err := h.RequireServo(ctx); err != nil {
s.Fatal("Failed to init servo: ", err)
}
if err := h.RequireConfig(ctx); err != nil {
s.Fatal("Failed to create config: ", err)
}
if err := checkTPMC(ctx, h); err != nil {
s.Fatal("Failed to check TPMC: ", err)
}
s.Log("Setup USB key")
skipFlashUSB := false
if skipFlashUSBStr, ok := s.Var("firmware.skipFlashUSB"); ok {
var err error
skipFlashUSB, err = strconv.ParseBool(skipFlashUSBStr)
if err != nil {
s.Fatalf("Invalid value for var firmware.skipFlashUSB: got %q, want true/false", skipFlashUSBStr)
}
}
var cs *testing.CloudStorage
if !skipFlashUSB {
cs = s.CloudStorage()
}
if err := h.SetupUSBKey(ctx, cs); err != nil {
s.Fatal("USBKey not working: ", err)
}
if err := h.EnableDevBootUSB(ctx); err != nil {
s.Fatal("Failed to enable USB boot: ", err)
}
s.Log("Removing the USB")
if err := h.Servo.SetUSBMuxState(ctx, servo.USBMuxOff); err != nil {
s.Fatal("Failed to remove the USB: ", err)
}
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 4*time.Minute)
defer cancel()
defer func(ctx context.Context) {
if err := h.EnsureDUTBooted(ctx); err != nil {
s.Fatal("Failed to reconnect to dut: ", err)
}
if err := h.DisableDevBootUSB(ctx); err != nil {
s.Fatal("Failed to set crossystem dev_boot_usb to 0: ", err)
}
if err := h.DUT.Conn().CommandContext(ctx, "crossystem", "dev_default_boot=disk").Run(ssh.DumpLogOnError); err != nil {
s.Fatal("Failed to set crossystem dev_default_boot to disk: ", err)
}
}(cleanupCtx)
s.Log("Rebooting dut by warm reset")
if err := h.Servo.SetPowerState(ctx, servo.PowerStateWarmReset); err != nil {
s.Fatal("Failed to warm reset dut: ", err)
}
s.Logf("Sleeping for %s (FirmwareScreen) ", h.Config.FirmwareScreen)
// GoBigSleepLint: Delay to wait for the firmware screen during boot-up.
if err := testing.Sleep(ctx, h.Config.FirmwareScreen); err != nil {
s.Fatalf("Failed to sleep for %s: %v", h.Config.FirmwareScreen, err)
}
s.Log("Resetting firmware screen timeout")
if err := h.Servo.PressKey(ctx, " ", servo.DurTab); err != nil {
s.Fatal("Failed to press space key: ", err)
}
s.Log("Setting DFP mode")
if err := h.Servo.SetDUTPDDataRole(ctx, servo.DFP); err != nil {
s.Logf("Failed to set pd data role to DFP: %.400s", err)
}
s.Log("Inserting a valid USB to DUT")
if err := h.Servo.SetUSBMuxState(ctx, servo.USBMuxDUT); err != nil {
s.Fatal("Failed to insert USB to DUT: ", err)
}
// GoBigSleepLint: It may take some time for usb mux state to
// take effect.
if err := testing.Sleep(ctx, firmware.UsbVisibleTime); err != nil {
s.Fatalf("Failed to sleep for %v s: %v", firmware.UsbDisableTime, err)
}
// On KeyboardDevSwitcher machines, pressing space triggers the
// to_norm screen. Revert to the developer screen with the
// esc key.
if h.Config.ModeSwitcherType == firmware.KeyboardDevSwitcher {
if err := h.Servo.PressKey(ctx, "<esc>", servo.DurTab); err != nil {
s.Fatal("Failed to press esc: ", err)
}
// GoBigSleepLint: Sleep for model specific time.
if err := testing.Sleep(ctx, h.Config.KeypressDelay); err != nil {
s.Fatalf("Failed to sleep for %s (KeypressDelay): %v", h.Config.KeypressDelay, err)
}
}
ms, err := firmware.NewModeSwitcher(ctx, h)
if err != nil {
s.Fatal("Creating mode switcher: ", err)
}
if err := ms.BypassDevBootUSB(ctx); err != nil {
s.Fatal("Failed to bypass dev boot USB: ", err)
}
if err := h.WaitDUTConnectDuringBootFromUSB(ctx, true); err != nil {
s.Fatal("Failed to boot from USB: ", err)
}
if err := readKernFwVer(ctx, h); err != nil {
s.Fatal("Failed to read kernel and firmware version: ", err)
}
}
// checkTPMC verifies that the kernel anti-rollback data from the tpmc output
// is one of the expected values.
func checkTPMC(ctx context.Context, h *firmware.Helper) error {
kernelRollbackSpace, err := h.ReadTPMC(ctx, "read", strconv.FormatInt(kernelNvIndex, 16), strconv.FormatInt(kernelAntirollbackSpaceBytes, 16))
if err != nil {
return errors.Wrap(err, "failed to read kernel anti-rollback info")
}
// Check that there are no extra lines of output beyond the expected single line of TPM data.
lines := strings.Split(kernelRollbackSpace, "\n")
if len(lines[1]) != 0 {
return errors.Errorf("unexpected additional data beyond the first line of TPM output: %s", kernelRollbackSpace)
}
tpmcOutput := lines[0]
// Check if TPM kernel data is in the format of version 1.0.
if strings.HasPrefix(tpmcOutput, tpmNVRAMV10Header) {
return checkTPMCV10(ctx, h)
}
if !slices.Contains(tpmNVRAMExpectedValues, tpmcOutput) {
return errors.Errorf("TPM data is not expected: %s", tpmcOutput)
}
return nil
}
func checkTPMCV10(ctx context.Context, h *firmware.Helper) error {
kernelRollbackSpace, err := h.ReadTPMC(ctx, "read", strconv.FormatInt(kernelNvIndex, 16), strconv.FormatInt(kernelAntirollbackSpaceBytesV10, 16))
if err != nil {
return errors.Wrap(err, "failed to read kernel anti-rollback info")
}
tpmcOutput := string(kernelRollbackSpace)
// Check that there are no extra lines of output beyond the expected single line of TPM data.
if out := strings.Split(tpmcOutput, "\n"); len(out[1]) > 0 {
return errors.Errorf("unexpected additional data beyond the first line of TPM output: %s", kernelRollbackSpace)
}
kernelData := strings.Fields(tpmcOutput)
// Check if kernel data is long enough to check CRC.
if len(kernelData) <= tpmNVRAMV10CRCOffset {
return errors.New("kernel data is too short for CRC check")
}
// Convert kernel data to a byte slice.
kernelDataBytes := make([]byte, len(kernelData))
for i, k := range kernelData {
v, err := strconv.ParseUint(k, 16, 8)
if err != nil {
return errors.Wrapf(err, "failed to parse kernel data %q as hex", k)
}
kernelDataBytes[i] = byte(v)
}
// For all the bytes from offset 3, calculate CRC8.
crc8Calc := checkSum(kernelDataBytes[tpmNVRAMV10CRCOffset+1:])
if kernelDataBytes[tpmNVRAMV10CRCOffset] != crc8Calc {
return errors.Wrapf(err, "kernel Data CRC(%x) is not correct: should be %x", kernelDataBytes[tpmNVRAMV10CRCOffset], crc8Calc)
}
testing.ContextLog(ctx, "Kernel data v1.0 CRC is checked")
return nil
}
// readKernFwVer checks the firmware and kernel version reported by crossystem.
func readKernFwVer(ctx context.Context, h *firmware.Helper) error {
testing.ContextLog(ctx, "Checking kernel and fw version via crossystem")
checkVals := map[reporters.CrossystemParam]string{
reporters.CrossystemParamTpmKernelVer: "0xFFFFFFFF",
reporters.CrossystemParamTpmFwVer: "0xFFFFFFFF",
}
if matched, err := h.Reporter.CrossystemChecker(ctx, checkVals); err != nil {
return errors.Wrap(err, "failed to verify crossystem params")
} else if matched {
return errors.New("found invalid kernel and firmware versions stored in the TPM")
}
return nil
}
func checkSum(data []byte) byte {
table := crc8.MakeTable(crc8.CRC8)
crc := crc8.Checksum([]byte(data), table)
return crc
}