blob: 2c5385c52146f94c44eb25943c6228b53a45b1f5 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package hwsec
import (
"context"
"fmt"
"time"
"chromiumos/tast/common/hwsec"
"chromiumos/tast/errors"
hwsecremote "chromiumos/tast/remote/hwsec"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: InstallAttributes,
Desc: "Checks that install attributes works",
Contacts: []string{
"zuan@chromium.org", // Test author
"cros-hwsec@google.com",
},
Attr: []string{"group:hwsec_destructive_func"},
SoftwareDeps: []string{"tpm", "reboot"},
Timeout: 5 * time.Minute,
})
}
const (
waitForInstallAttributesTimeout = 2 * time.Minute
testAttributesUndefined = "Naproxen"
tamperedAttributes = "Methadone"
databasePath = "/home/.shadow/install_attributes.pb"
)
var testAttributes = [...]string{"Ibuprofen", "Acetaminophen", "Acetylsalicylic Acid"}
var testValues = [...]string{"C13H18O2", "C8H9NO2", "C9H8O4"}
// getInstallAttributesStates returns isReady, isInitialized, isInvalid, isFirstInstall, isSecure, count and any error encountered.
func getInstallAttributesStates(ctx context.Context, utility *hwsec.CryptohomeClient) (isReady, isInitialized, isInvalid, isFirstInstall, isSecure bool, count int, returnError error) {
// Default return values.
isReady = false
isInitialized = false
isInvalid = false
isFirstInstall = false
isSecure = false
count = -1
// Get the values through InstallAttributesStatus().
status, err := utility.InstallAttributesStatus(ctx)
if err != nil {
returnError = errors.Wrap(err, "failed to get cryptohome install attributes status")
return
}
switch status {
case "FIRST_INSTALL":
isInitialized = true
isFirstInstall = true
isReady = true
case "VALID":
isInitialized = true
isReady = true
case "INVALID":
isInvalid = true
isReady = true
case "TPM_NOT_OWNED":
// Do nothing.
default:
returnError = errors.Wrapf(err, "unexpected install attributes states %q", status)
return
}
count, err = utility.InstallAttributesCount(ctx)
if err != nil {
returnError = errors.Wrap(err, "failed to get count")
return
}
isSecure, err = utility.InstallAttributesIsSecure(ctx)
if err != nil {
returnError = errors.Wrap(err, "failed to get is secure")
return
}
return
}
// waitForInstallAttributes waits for install attributes to be ready.
func waitForInstallAttributes(ctx context.Context, utility *hwsec.CryptohomeClient) error {
// Wait for, and check TPM attributes after taking ownership.
if err := testing.Poll(ctx, func(ctx context.Context) error {
isReady, isInitialized, isInvalid, isFirstInstall, _, count, err := getInstallAttributesStates(ctx, utility)
if err != nil {
return err
}
if !isReady || !isInitialized || isInvalid || !isFirstInstall || count != 0 {
return errors.Errorf("unexpected Install Attributes state after taking ownership; ready=%t, initialized=%t, invalid=%t, firstInstall=%t, count=%d", isReady, isInitialized, isInvalid, isFirstInstall, count)
}
return nil
}, &testing.PollOptions{Timeout: waitForInstallAttributesTimeout}); err != nil {
return errors.Wrap(err, "failed waiting for install attributes after taking ownership")
}
return nil
}
// takeOwnershipAndWaitForInstallAttributes takes ownership and wait for install attributes to be ready.
func takeOwnershipAndWaitForInstallAttributes(ctx context.Context, utility *hwsec.CryptohomeClient, helper *hwsecremote.CmdHelperRemote) error {
if err := helper.EnsureTPMIsReady(ctx, hwsec.DefaultTakingOwnershipTimeout); err != nil {
return errors.Wrap(err, "time out waiting for TPM to be ready")
}
return waitForInstallAttributes(ctx, utility)
}
// checkAllTestAttributes is a helper function that checks the install attributes retrieved through cryptohome's API is what we are expecting.
func checkAllTestAttributes(ctx context.Context, utility *hwsec.CryptohomeClient) error {
for i, attributeName := range testAttributes {
attributeValue := testValues[i]
readbackValue, err := utility.InstallAttributesGet(ctx, attributeName)
if err != nil {
return errors.Wrapf(err, "failed to get install attributes %q", attributeName)
}
if readbackValue != attributeValue {
return errors.Errorf("incorrect attribute value for attribute %q, expected %q got %q", attributeName, attributeValue, readbackValue)
}
}
return nil
}
// attemptChangeAndCheckShouldSucceed checks that install attributes are settable when it should be, it also verifies the install attributes values.
func attemptChangeAndCheckShouldSucceed(ctx context.Context, utility *hwsec.CryptohomeClient) error {
if err := utility.InstallAttributesSet(ctx, testAttributes[0], testValues[1]); err != nil {
return errors.Wrap(err, "failed to set install attributes when it should still be settable")
}
if err := utility.InstallAttributesSet(ctx, testAttributes[0], testValues[0]); err != nil {
return errors.Wrap(err, "failed to set install attributes back to its original value when it should still be settable")
}
// Lastly, check the attributes.
return checkAllTestAttributes(ctx, utility)
}
// attemptChangeAndCheckShouldFail checks that install attributes are not settable, it also verifies the install attributes values.
func attemptChangeAndCheckShouldFail(ctx context.Context, utility *hwsec.CryptohomeClient) error {
if err := utility.InstallAttributesSet(ctx, testAttributes[0], testValues[1]); err == nil {
return errors.New("setting install attributes to a different value succeeded when it shouldn't")
}
if err := utility.InstallAttributesSet(ctx, testAttributes[0], testValues[0]); err == nil {
return errors.New("setting install attributes to same value succeeded when it shouldn't")
}
if err := utility.InstallAttributesSet(ctx, testAttributesUndefined, testValues[0]); err == nil {
return errors.New("setting previously undefined install attributes succeeded when it shouldn't")
}
// Lastly, check the attributes.
return checkAllTestAttributes(ctx, utility)
}
// tamperWithInstallAttributes attempts to modify the install attributes by directly modifying its database.
func tamperWithInstallAttributes(ctx context.Context, r hwsec.CmdRunner) error {
// Check that the tampered string is the same length as the original attribute.
if len(tamperedAttributes) != len(testAttributes[0]) {
panic("Incorrect tampered attribute string length.")
}
// Check that the string is in the file.
if _, err := r.Run(ctx, "grep", "-q", testAttributes[0], databasePath); err != nil {
// The attribute is not found in the database.
return errors.Wrapf(err, "the database doesn't contain the test attributes %q", testAttributes[0])
}
// Now replace the string, thus tampering the database.
if _, err := r.Run(ctx, "sed", "-bi", fmt.Sprintf("s/%s/%s/g", testAttributes[0], tamperedAttributes), databasePath); err != nil {
// Failed to replace the attribute.
return errors.Wrap(err, "failed to replace the attribute")
}
return nil
}
func InstallAttributes(ctx context.Context, s *testing.State) {
r := hwsecremote.NewCmdRunner(s.DUT())
helper, err := hwsecremote.NewHelper(r, s.DUT())
if err != nil {
s.Fatal("Helper creation error: ", err)
}
utility := helper.CryptohomeClient()
s.Log("Start resetting TPM if needed")
if err := helper.EnsureTPMAndSystemStateAreReset(ctx); err != nil {
s.Fatal("Failed to ensure resetting TPM: ", err)
}
s.Log("TPM is confirmed to be reset")
// Check install attributes right after resetting the TPM.
isReady, isInitialized, isInvalid, isFirstInstall, _, count, err := getInstallAttributesStates(ctx, utility)
if err != nil {
s.Fatal("Failed to parse cryptohome status: ", err)
}
if isReady || isInitialized || isInvalid || isFirstInstall || count != 0 {
s.Fatalf("Unexpected Install Attributes state after TPM reset; ready=%t, initialized=%t, invalid=%t, firstInstall=%t, count=%d", isReady, isInitialized, isInvalid, isFirstInstall, count)
}
// Take ownership then wait for install attributes.
if err := takeOwnershipAndWaitForInstallAttributes(ctx, utility, helper); err != nil {
s.Fatal("Failed to take ownership or wait for install attributes: ", err)
}
// Now the install attributes are ready, let's write some attributes and see if it works.
for i, attributeName := range testAttributes {
attributeValue := testValues[i]
if err = utility.InstallAttributesSet(ctx, attributeName, attributeValue); err != nil {
s.Fatal("Failed to set install attributes "+attributeName+" to value "+attributeValue+": ", err)
}
}
// Check attributes and test setting after finalizing.
if err = attemptChangeAndCheckShouldSucceed(ctx, utility); err != nil {
s.Fatal("Check install attributes failed pre finalization: ", err)
}
// Next finalize it.
if err = utility.InstallAttributesFinalize(ctx); err != nil {
s.Fatal("Failed to finalize install attributes: ", err)
}
// Check install attributes right after finalizing.
isReady, isInitialized, isInvalid, isFirstInstall, _, count, err = getInstallAttributesStates(ctx, utility)
if err != nil {
s.Fatal("Failed to parse cryptohoattemptChangeAndCheckShouldFailme status: ", err)
}
if !isReady || !isInitialized || isInvalid || isFirstInstall || count != 3 {
s.Fatalf("Unexpected Install Attributes state after install attribute finalize; ready=%t, initialized=%t, invalid=%t, firstInstall=%t, count=%d", isReady, isInitialized, isInvalid, isFirstInstall, count)
}
// Check that trying to set install attribute now fails.
if err := attemptChangeAndCheckShouldFail(ctx, utility); err != nil {
s.Fatal("Checking install attributes failed post finalization: ", err)
}
// Reboot to check that everything is alright after reboot.
if err := helper.Reboot(ctx); err != nil {
s.Fatal("Failed to reboot: ", err)
}
// Check install attributes after reboot.
isReady, isInitialized, isInvalid, isFirstInstall, _, count, err = getInstallAttributesStates(ctx, utility)
if err != nil {
s.Fatal("Failed to parse cryptohome status: ", err)
}
if !isReady || !isInitialized || isInvalid || isFirstInstall || count != 3 {
s.Fatalf("Unexpected Install Attributes state after reboot; ready=%t, initialized=%t, invalid=%t, firstInstall=%t, count=%d", isReady, isInitialized, isInvalid, isFirstInstall, count)
}
// Recheck the install attributes
if err := attemptChangeAndCheckShouldFail(ctx, utility); err != nil {
s.Fatal("Checking install attributes failed post finalization reboot: ", err)
}
// Now tamper with the attributes.
if err := tamperWithInstallAttributes(ctx, r); err != nil {
s.Fatal("Failed to tamper with the install attributes database: ", err)
}
// Reboot so that it'll take effect.
if err := helper.Reboot(ctx); err != nil {
s.Fatal("Failed to reboot: ", err)
}
// Check install attributes after tampering with install attributes.
isReady, isInitialized, isInvalid, isFirstInstall, _, count, err = getInstallAttributesStates(ctx, utility)
if err != nil {
s.Fatal("Failed to parse cryptohome status: ", err)
}
if !isReady || !isInitialized || isInvalid || !isFirstInstall || count != 0 {
s.Fatalf("Unexpected Install Attributes state after tampering with install attributes; ready=%t, initialized=%t, invalid=%t, firstInstall=%t, count=%d", isReady, isInitialized, isInvalid, isFirstInstall, count)
}
// Check that neither the original nor the tampered attributes are readable.
if readbackValue, err := utility.InstallAttributesGet(ctx, testAttributes[0]); err == nil {
s.Fatalf("Able to read install attributes %q after tampering the database, got %q", testAttributes[0], readbackValue)
}
if readbackValue, err := utility.InstallAttributesGet(ctx, tamperedAttributes); err == nil {
s.Fatalf("Able to read install attributes %q after tampering the database, got %q", tamperedAttributes, readbackValue)
}
}