Skip to content

Commit

Permalink
preinstall: move some extra TPM tests to only run during pre-install
Browse files Browse the repository at this point in the history
We were already skipping the lockout hierarchy auth value test during
post-install because this doesn't make sense, but also skip similar
tests for other hierarchies and also skip the lockout checkout and the
test for sufficient number of NV counters.
  • Loading branch information
chrisccoulson committed Oct 17, 2024
1 parent e98362b commit e0c45dc
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 82 deletions.
87 changes: 44 additions & 43 deletions efi/preinstall/check_tpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,26 +84,6 @@ func openAndCheckTPM2Device(env internal_efi.HostEnvironment, flags checkTPM2Dev
return nil, false, fmt.Errorf("cannot obtain value for TPM_PT_PERMANENT: %w", err)
}

// Make sure that the DA lockout mode is not activated.
if tpm2.PermanentAttributes(perm)&tpm2.AttrInLockout > 0 {
return nil, false, ErrTPMLockout
}

// Make sure the lockout hierarchy auth value hasn't been set, unless we are
// being called in post-install mode.
if flags&checkTPM2DevicePostInstall == 0 {
if tpm2.PermanentAttributes(perm)&tpm2.AttrLockoutAuthSet > 0 {
return nil, false, ErrTPMLockoutAlreadyOwned
}
}
// Make sure the owner and endorsement hierarchy authorization values have never been set.
// We don't support this yet - this needs to be coordinated with snapd because snapd will need
// access to these values.
if tpm2.PermanentAttributes(perm)&(tpm2.AttrOwnerAuthSet|tpm2.AttrEndorsementAuthSet) > 0 {
// We don't support setting these at all yet
return nil, false, ErrUnsupportedTPMOwnership
}

// Check TPM2 device class. The class is associated with a TPM Profile (PTP) spec
// which says a lot about the TPM such as mandatory commands, algorithms, PCR banks
// and the minimum number of PCRs. In all honesty, we're only ever likely to see
Expand Down Expand Up @@ -137,29 +117,6 @@ func openAndCheckTPM2Device(env internal_efi.HostEnvironment, flags checkTPM2Dev
}
}

// Make sure we have enough NV counters for PCR policy revocation. We need at least 2 (1 normally, and
// an extra 1 during reprovision). The platform firmware may use up some of the allocation.
nvCountersMax, err := tpm.GetCapabilityTPMProperty(tpm2.PropertyNVCountersMax)
if err != nil {
return nil, false, fmt.Errorf("cannot obtain value for TPM_NV_COUNTERS_MAX: %w", err)
}
if nvCountersMax > 0 {
// If the TPM returns 0, there are no limits to the number of counters other than
// available NV storage. Obtain the number of active counters.
nvCounters, err := tpm.GetCapabilityTPMProperty(tpm2.PropertyNVCounters)
if err != nil {
return nil, false, fmt.Errorf("cannot obtain value for TPM_NV_COUNTERS_MAX: %w", err)
}
required := uint32(1) // extra index for re-provision
if flags&checkTPM2DevicePostInstall == 0 {
// This is pre-install, so we need the initial index for provision
required += 1
}
if (nvCountersMax - nvCounters) < required {
return nil, false, ErrTPMInsufficientNVCounters
}
}

// Determine whether we have a discrete TPM by querying the manufacturer.
// Assume that Intel is firmware (ie, Intel PTT) and everything else is discrete
// unless we are in a VM.
Expand All @@ -177,5 +134,49 @@ func openAndCheckTPM2Device(env internal_efi.HostEnvironment, flags checkTPM2Dev
if flags&checkTPM2DeviceInVM > 0 {
discreteTPM = false
}

if flags&checkTPM2DevicePostInstall == 0 {
// Perform some checks only during pre-install.

// Make sure that the DA lockout mode is not activated.
if tpm2.PermanentAttributes(perm)&tpm2.AttrInLockout > 0 {
return nil, false, ErrTPMLockout
}

// Make sure the lockout hierarchy auth value is not set.
if tpm2.PermanentAttributes(perm)&tpm2.AttrLockoutAuthSet > 0 {
return nil, false, &TPM2HierarchyOwnedError{Hierarchy: tpm2.HandleLockout}
}

// Make sure the owner hierarchy authorization value is not set.
if tpm2.PermanentAttributes(perm)&(tpm2.AttrOwnerAuthSet) > 0 {
return nil, false, &TPM2HierarchyOwnedError{Hierarchy: tpm2.HandleOwner}
}

// Make sure the endorsement hierarchy authorization value is not set.
if tpm2.PermanentAttributes(perm)&(tpm2.AttrEndorsementAuthSet) > 0 {
return nil, false, &TPM2HierarchyOwnedError{Hierarchy: tpm2.HandleEndorsement}
}

// Make sure we have enough NV counters for PCR policy revocation. We need at least 2 (1 normally, and
// an extra 1 during reprovision). The platform firmware may use up some of the allocation.
nvCountersMax, err := tpm.GetCapabilityTPMProperty(tpm2.PropertyNVCountersMax)
if err != nil {
return nil, false, fmt.Errorf("cannot obtain value for TPM_NV_COUNTERS_MAX: %w", err)
}
if nvCountersMax > 0 {
// If the TPM returns 0, there are no limits to the number of counters other than
// available NV storage. If there are a finite number of counters, obtain the number
// of active counters.
nvCounters, err := tpm.GetCapabilityTPMProperty(tpm2.PropertyNVCounters)
if err != nil {
return nil, false, fmt.Errorf("cannot obtain value for TPM_NV_COUNTERS_MAX: %w", err)
}
if (nvCountersMax - nvCounters) < 2 {
return nil, false, ErrTPMInsufficientNVCounters
}
}
}

return tpm, discreteTPM, nil
}
161 changes: 135 additions & 26 deletions efi/preinstall/check_tpm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package preinstall_test

import (
"bytes"
"errors"

"github.com/canonical/go-tpm2"
"github.com/canonical/go-tpm2/mu"
Expand Down Expand Up @@ -158,7 +159,7 @@ func (s *tpmSuite) TestOpenAndCheckTPM2DeviceGoodPreInstallNoVMFiniteCountersDis
c.Check(dev.NumberOpen(), Equals, int(1))
}

func (s *tpmSuite) TestOpenAndCheckTPM2DeviceGoodPostInstallNoVMFiniteCountersDiscreteTPM(c *C) {
func (s *tpmSuite) TestOpenAndCheckTPM2DeviceGoodPostInstallNoVMCountersCheckSkippedDiscreteTPM(c *C) {
s.addTPMPropertyModifiers(c, map[tpm2.Property]uint32{
tpm2.PropertyNVCountersMax: 6,
tpm2.PropertyNVCounters: 5,
Expand All @@ -179,12 +180,9 @@ func (s *tpmSuite) TestOpenAndCheckTPM2DeviceGoodPostInstallNoVMFiniteCountersDi
}

func (s *tpmSuite) TestOpenAndCheckTPM2DeviceGoodPreInstallVMInfiniteCounters(c *C) {
family, err := s.TPM.GetCapabilityTPMProperty(tpm2.PropertyFamilyIndicator)
c.Assert(err, IsNil)

s.addTPMPropertyModifiers(c, map[tpm2.Property]uint32{
tpm2.PropertyNVCountersMax: 0,
tpm2.PropertyPSFamilyIndicator: family,
tpm2.PropertyPSFamilyIndicator: 1,
tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerMSFT),
})

Expand All @@ -200,6 +198,95 @@ func (s *tpmSuite) TestOpenAndCheckTPM2DeviceGoodPreInstallVMInfiniteCounters(c
c.Check(dev.NumberOpen(), Equals, int(1))
}

func (s *tpmSuite) TestOpenAndCheckTPM2DeviceGoodPostInstallNoVMLockoutCheckSkipped(c *C) {
s.addTPMPropertyModifiers(c, map[tpm2.Property]uint32{
tpm2.PropertyNVCountersMax: 6,
tpm2.PropertyNVCounters: 4,
tpm2.PropertyPSFamilyIndicator: 1,
tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerINTC),
})

// Trip the DA logic by setting newMaxTries to 0
c.Assert(s.TPM.DictionaryAttackParameters(s.TPM.LockoutHandleContext(), 0, 10000, 10000, nil), IsNil)

dev := tpm2_testutil.NewTransportBackedDevice(s.Transport, false)
env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithTPMDevice(dev))
tpm, discreteTPM, err := OpenAndCheckTPM2Device(env, CheckTPM2DevicePostInstall)
c.Check(err, IsNil)
c.Assert(tpm, NotNil)
var tmpl tpm2_testutil.TransportWrapper
c.Assert(tpm.Transport(), Implements, &tmpl)
c.Check(tpm.Transport().(tpm2_testutil.TransportWrapper).Unwrap(), Equals, s.Transport)
c.Check(discreteTPM, testutil.IsFalse)
c.Check(dev.NumberOpen(), Equals, int(1))
}

func (s *tpmSuite) TestOpenAndCheckTPM2DeviceGoodPostInstallNoVMLockoutOwnedCheckSkipped(c *C) {
s.addTPMPropertyModifiers(c, map[tpm2.Property]uint32{
tpm2.PropertyNVCountersMax: 0,
tpm2.PropertyPSFamilyIndicator: 1,
tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerINTC),
})

// Set the lockout hierarchy auth value.
c.Assert(s.TPM.HierarchyChangeAuth(s.TPM.LockoutHandleContext(), []byte{1, 2, 3, 4}, nil), IsNil)

dev := tpm2_testutil.NewTransportBackedDevice(s.Transport, false)
env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithTPMDevice(dev))
tpm, discreteTPM, err := OpenAndCheckTPM2Device(env, CheckTPM2DevicePostInstall)
c.Check(err, IsNil)
c.Assert(tpm, NotNil)
var tmpl tpm2_testutil.TransportWrapper
c.Assert(tpm.Transport(), Implements, &tmpl)
c.Check(tpm.Transport().(tpm2_testutil.TransportWrapper).Unwrap(), Equals, s.Transport)
c.Check(discreteTPM, testutil.IsFalse)
c.Check(dev.NumberOpen(), Equals, int(1))
}

func (s *tpmSuite) TestOpenAndCheckTPM2DeviceGoodPostInstallNoVMOwnerOwnedCheckSkipped(c *C) {
s.addTPMPropertyModifiers(c, map[tpm2.Property]uint32{
tpm2.PropertyNVCountersMax: 0,
tpm2.PropertyPSFamilyIndicator: 1,
tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerINTC),
})

// Set the owner hierarchy auth value.
c.Assert(s.TPM.HierarchyChangeAuth(s.TPM.OwnerHandleContext(), []byte{1, 2, 3, 4}, nil), IsNil)

dev := tpm2_testutil.NewTransportBackedDevice(s.Transport, false)
env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithTPMDevice(dev))
tpm, discreteTPM, err := OpenAndCheckTPM2Device(env, CheckTPM2DevicePostInstall)
c.Check(err, IsNil)
c.Assert(tpm, NotNil)
var tmpl tpm2_testutil.TransportWrapper
c.Assert(tpm.Transport(), Implements, &tmpl)
c.Check(tpm.Transport().(tpm2_testutil.TransportWrapper).Unwrap(), Equals, s.Transport)
c.Check(discreteTPM, testutil.IsFalse)
c.Check(dev.NumberOpen(), Equals, int(1))
}

func (s *tpmSuite) TestOpenAndCheckTPM2DeviceGoodPostInstallNoVMEndorsmentOwnedCheckSkipped(c *C) {
s.addTPMPropertyModifiers(c, map[tpm2.Property]uint32{
tpm2.PropertyNVCountersMax: 0,
tpm2.PropertyPSFamilyIndicator: 1,
tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerINTC),
})

// Set the endorsement hierarchy auth value.
c.Assert(s.TPM.HierarchyChangeAuth(s.TPM.EndorsementHandleContext(), []byte{1, 2, 3, 4}, nil), IsNil)

dev := tpm2_testutil.NewTransportBackedDevice(s.Transport, false)
env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithTPMDevice(dev))
tpm, discreteTPM, err := OpenAndCheckTPM2Device(env, CheckTPM2DevicePostInstall)
c.Check(err, IsNil)
c.Assert(tpm, NotNil)
var tmpl tpm2_testutil.TransportWrapper
c.Assert(tpm.Transport(), Implements, &tmpl)
c.Check(tpm.Transport().(tpm2_testutil.TransportWrapper).Unwrap(), Equals, s.Transport)
c.Check(discreteTPM, testutil.IsFalse)
c.Check(dev.NumberOpen(), Equals, int(1))
}

func (s *tpmSuite) TestOpenAndCheckTPM2DeviceNoTPM(c *C) {
env := efitest.NewMockHostEnvironmentWithOpts()
_, _, err := OpenAndCheckTPM2Device(env, 0)
Expand All @@ -219,6 +306,11 @@ func (s *tpmSuite) TestOpenAndCheckTPM2DeviceDisabled(c *C) {
}

func (s *tpmSuite) TestOpenAndCheckTPM2DeviceLockout(c *C) {
s.addTPMPropertyModifiers(c, map[tpm2.Property]uint32{
tpm2.PropertyPSFamilyIndicator: 1,
tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerINTC),
})

// Trip the DA logic by setting newMaxTries to 0
c.Assert(s.TPM.DictionaryAttackParameters(s.TPM.LockoutHandleContext(), 0, 10000, 10000, nil), IsNil)

Expand All @@ -229,77 +321,94 @@ func (s *tpmSuite) TestOpenAndCheckTPM2DeviceLockout(c *C) {
c.Check(dev.NumberOpen(), Equals, int(0))
}

func (s *tpmSuite) TestOpenAndCheckTPM2DeviceAlreadyOwned(c *C) {
func (s *tpmSuite) TestOpenAndCheckTPM2DeviceAlreadyOwnedLockout(c *C) {
s.addTPMPropertyModifiers(c, map[tpm2.Property]uint32{
tpm2.PropertyPSFamilyIndicator: 1,
tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerINTC),
})

// Set the lockout hierarchy auth value so we get an error indicating that the TPM is already owned.
c.Assert(s.TPM.HierarchyChangeAuth(s.TPM.LockoutHandleContext(), []byte{1, 2, 3, 4}, nil), IsNil)

dev := tpm2_testutil.NewTransportBackedDevice(s.Transport, false)
env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithTPMDevice(dev))
_, _, err := OpenAndCheckTPM2Device(env, 0)
c.Check(err, Equals, ErrTPMLockoutAlreadyOwned)
c.Check(err, ErrorMatches, `TPM lockout hierarchy is currently owned`)
var he *TPM2HierarchyOwnedError
c.Check(errors.As(err, &he), testutil.IsTrue)
c.Check(dev.NumberOpen(), Equals, int(0))
}

func (s *tpmSuite) TestOpenAndCheckTPM2DeviceAlreadyOwned2(c *C) {
// Set the owner hierarchy auth value and run the post-install test. We get an error indicating that the TPM is already owned
// because we don't support setting this value yet, but should in the value (setting this needs to be coordinated with snapd)
func (s *tpmSuite) TestOpenAndCheckTPM2DeviceAlreadyOwnedOwner(c *C) {
s.addTPMPropertyModifiers(c, map[tpm2.Property]uint32{
tpm2.PropertyPSFamilyIndicator: 1,
tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerINTC),
})

// Set the owner hierarchy auth value so we get an error indicating that the TPM is already owned.
c.Assert(s.TPM.HierarchyChangeAuth(s.TPM.OwnerHandleContext(), []byte{1, 2, 3, 4}, nil), IsNil)

dev := tpm2_testutil.NewTransportBackedDevice(s.Transport, false)
env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithTPMDevice(dev))
_, _, err := OpenAndCheckTPM2Device(env, CheckTPM2DevicePostInstall)
c.Check(err, Equals, ErrUnsupportedTPMOwnership)
_, _, err := OpenAndCheckTPM2Device(env, 0)
c.Check(err, ErrorMatches, `TPM owner hierarchy is currently owned`)
var he *TPM2HierarchyOwnedError
c.Check(errors.As(err, &he), testutil.IsTrue)
c.Check(dev.NumberOpen(), Equals, int(0))
}

func (s *tpmSuite) TestOpenAndCheckTPM2DeviceIsNotPCClient(c *C) {
func (s *tpmSuite) TestOpenAndCheckTPM2DeviceAlreadyOwnedEndorsement(c *C) {
s.addTPMPropertyModifiers(c, map[tpm2.Property]uint32{
tpm2.PropertyPSFamilyIndicator: 2, // This is defined as PDA in the reference library specs
tpm2.PropertyPSFamilyIndicator: 1,
tpm2.PropertyManufacturer: uint32(tpm2.TPMManufacturerINTC),
})

// Set the endorsement hierarchy auth value so we get an error indicating that the TPM is already owned.
c.Assert(s.TPM.HierarchyChangeAuth(s.TPM.EndorsementHandleContext(), []byte{1, 2, 3, 4}, nil), IsNil)

dev := tpm2_testutil.NewTransportBackedDevice(s.Transport, false)
env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithTPMDevice(dev))
_, _, err := OpenAndCheckTPM2Device(env, 0)
c.Check(err, Equals, ErrNoPCClientTPM)
c.Check(err, ErrorMatches, `TPM endorsement hierarchy is currently owned`)
var he *TPM2HierarchyOwnedError
c.Check(errors.As(err, &he), testutil.IsTrue)
c.Check(dev.NumberOpen(), Equals, int(0))
}

func (s *tpmSuite) TestOpenAndCheckTPM2DeviceIsNotPCClientWithSWTPMWorkaround(c *C) {
func (s *tpmSuite) TestOpenAndCheckTPM2DeviceIsNotPCClient(c *C) {
s.addTPMPropertyModifiers(c, map[tpm2.Property]uint32{
tpm2.PropertyPSFamilyIndicator: 2, // This is defined as PDA in the reference library specs
})

dev := tpm2_testutil.NewTransportBackedDevice(s.Transport, false)
env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithTPMDevice(dev))
_, _, err := OpenAndCheckTPM2Device(env, CheckTPM2DeviceInVM)
_, _, err := OpenAndCheckTPM2Device(env, 0)
c.Check(err, Equals, ErrNoPCClientTPM)
c.Check(dev.NumberOpen(), Equals, int(0))
}

func (s *tpmSuite) TestOpenAndCheckTPM2DeviceInsufficientNVCountersPreInstall(c *C) {
func (s *tpmSuite) TestOpenAndCheckTPM2DeviceIsNotPCClientWithSWTPMWorkaround(c *C) {
s.addTPMPropertyModifiers(c, map[tpm2.Property]uint32{
tpm2.PropertyNVCountersMax: 6,
tpm2.PropertyNVCounters: 5,
tpm2.PropertyPSFamilyIndicator: 1,
tpm2.PropertyPSFamilyIndicator: 2, // This is defined as PDA in the reference library specs
})

dev := tpm2_testutil.NewTransportBackedDevice(s.Transport, false)
env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithTPMDevice(dev))
_, _, err := OpenAndCheckTPM2Device(env, 0)
c.Check(err, Equals, ErrTPMInsufficientNVCounters)
_, _, err := OpenAndCheckTPM2Device(env, CheckTPM2DeviceInVM)
c.Check(err, Equals, ErrNoPCClientTPM)
c.Check(dev.NumberOpen(), Equals, int(0))
}

func (s *tpmSuite) TestOpenAndCheckTPM2DeviceInsufficientNVCountersPostInstall(c *C) {
func (s *tpmSuite) TestOpenAndCheckTPM2DeviceInsufficientNVCountersPreInstall(c *C) {
s.addTPMPropertyModifiers(c, map[tpm2.Property]uint32{
tpm2.PropertyNVCountersMax: 6,
tpm2.PropertyNVCounters: 6,
tpm2.PropertyNVCounters: 5,
tpm2.PropertyPSFamilyIndicator: 1,
})

dev := tpm2_testutil.NewTransportBackedDevice(s.Transport, false)
env := efitest.NewMockHostEnvironmentWithOpts(efitest.WithTPMDevice(dev))
_, _, err := OpenAndCheckTPM2Device(env, CheckTPM2DevicePostInstall)
_, _, err := OpenAndCheckTPM2Device(env, 0)
c.Check(err, Equals, ErrTPMInsufficientNVCounters)
c.Check(dev.NumberOpen(), Equals, int(0))
}
Loading

0 comments on commit e0c45dc

Please sign in to comment.