Skip to content

Commit

Permalink
overlord/fdestate: EFI DBX support (#14615)
Browse files Browse the repository at this point in the history
Final pieces of EFI DBX support in fdestate, which implement the following logical 'actions' triggered through the snapd API:

'prepare' - triggered before fwupd or other agent performs the actual update, results in resealing of keys with the content of the DBX update
'cleanup - triggered after the update has completed, results in resealing of keys with the current content of DBX
'startup' - triggered whenever said agent starts up, such that we can detect whenever an unexpected reboot occurred or the agent crashed/restarted, which may trigger a reseal if an update was previously started.
The updates are tracked as 'external' operations in fdestate.
* tests/nested/manual/core20-fde-dbx: spread test for DBX related operations

Add a spread test for DBX related operations

Signed-off-by: Maciej Borzecki <[email protected]>

* overlord/fdestate: implement EFI DBX operations

Implement operations for notifying snapd of changes to the EFI DBX.

Signed-off-by: Maciej Borzecki <[email protected]>

* fixup! overlord/fdestate: implement EFI DBX operations

Signed-off-by: Maciej Borzecki <[email protected]>

* fixup! overlord/fdestate: implement EFI DBX operations

Signed-off-by: Maciej Borzecki <[email protected]>

* fixup! tests/nested/manual/core20-fde-dbx: spread test for DBX related operations

Signed-off-by: Maciej Borzecki <[email protected]>

* fixup! tests/nested/manual/core20-fde-dbx: spread test for DBX related operations

Signed-off-by: Maciej Borzecki <[email protected]>

* fixup! overlord/fdestate: implement EFI DBX operations

Signed-off-by: Maciej Borzecki <[email protected]>

* fixup! tests/nested/manual/core20-fde-dbx: spread test for DBX related operations

Signed-off-by: Maciej Borzecki <[email protected]>

* fixup! overlord/fdestate: implement EFI DBX operations

Signed-off-by: Maciej Borzecki <[email protected]>

* fixup! tests/nested/manual/core20-fde-dbx: spread test for DBX related operations

Signed-off-by: Maciej Borzecki <[email protected]>

* fixup! overlord/fdestate: implement EFI DBX operations

Signed-off-by: Maciej Borzecki <[email protected]>

* fixup! overlord/fdestate: implement EFI DBX operations

Signed-off-by: Maciej Borzecki <[email protected]>

* fixup! overlord/fdestate: implement EFI DBX operations

Signed-off-by: Maciej Borzecki <[email protected]>

* fixup! tests/nested/manual/core20-fde-dbx: spread test for DBX related operations

Signed-off-by: Maciej Borzecki <[email protected]>

* fixup! overlord/fdestate: implement EFI DBX operations

Signed-off-by: Maciej Borzecki <[email protected]>

---------

Signed-off-by: Maciej Borzecki <[email protected]>
  • Loading branch information
bboozzoo authored Jan 7, 2025
1 parent dc36749 commit 5802840
Show file tree
Hide file tree
Showing 8 changed files with 2,110 additions and 48 deletions.
602 changes: 602 additions & 0 deletions overlord/fdestate/dbx.go

Large diffs are not rendered by default.

1,264 changes: 1,264 additions & 0 deletions overlord/fdestate/dbx_test.go

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions overlord/fdestate/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,18 @@ var (

UpdateParameters = updateParameters

IsEFISecurebootDBUpdateBlocked = isEFISecurebootDBUpdateBlocked

FindFirstPendingExternalOperationByKind = findFirstPendingExternalOperationByKind
FindFirstExternalOperationByChangeID = findFirstExternalOperationByChangeID
AddExternalOperation = addExternalOperation
AddEFISecurebootDBUpdateChange = addEFISecurebootDBUpdateChange
UpdateExternalOperation = updateExternalOperation

NotifyDBXUpdatePrepareDoneOK = notifyDBXUpdatePrepareDoneOK
DbxUpdatePreparedOKChan = dbxUpdatePreparedOKChan

DbxUpdateAffectedSnaps = dbxUpdateAffectedSnaps
)

type ExternalOperation = externalOperation
Expand All @@ -45,4 +53,12 @@ func MockBackendResealKeyForBootChains(f func(manager backend.FDEStateManager, m
return restore
}

func MockBackendResealKeysForSignaturesDBUpdate(f func(updateState backend.FDEStateManager, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, payload []byte) error) (restore func()) {
restore = testutil.Backup(&backendResealKeysForSignaturesDBUpdate)
backendResealKeysForSignaturesDBUpdate = f
return restore
}

var NewModel = newModel

func (m *FDEManager) IsFunctional() error { return m.isFunctional() }
16 changes: 16 additions & 0 deletions overlord/fdestate/fdemgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/fdestate/backend"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/secboot"
"github.com/snapcore/snapd/snapdenv"
Expand Down Expand Up @@ -87,6 +88,21 @@ func Manager(st *state.State, runner *state.TaskRunner) (*FDEManager, error) {
defer st.Unlock()
st.Cache(fdeMgrKey{}, m)

snapstate.RegisterAffectedSnapsByKind("efi-secureboot-db-update", dbxUpdateAffectedSnaps)

runner.AddHandler("efi-secureboot-db-update-prepare",
m.doEFISecurebootDBUpdatePrepare, m.undoEFISecurebootDBUpdatePrepare)
runner.AddCleanup("efi-secureboot-db-update-prepare", m.doEFISecurebootDBUpdatePrepareCleanup)
runner.AddHandler("efi-secureboot-db-update", m.doEFISecurebootDBUpdate, nil)
runner.AddBlocked(func(t *state.Task, running []*state.Task) bool {
switch t.Kind() {
case "efi-secureboot-db-update":
return isEFISecurebootDBUpdateBlocked(t)
}

return false
})

return m, nil
}

Expand Down
38 changes: 36 additions & 2 deletions overlord/fdestate/fdemgr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ import (
"github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/gadget/device"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil/disks"
"github.com/snapcore/snapd/overlord"
"github.com/snapcore/snapd/overlord/fdestate"
"github.com/snapcore/snapd/overlord/fdestate/backend"
"github.com/snapcore/snapd/overlord/ifacestate/ifacerepo"
"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/secboot"
Expand All @@ -54,6 +58,7 @@ type fdeMgrSuite struct {
rootdir string
st *state.State
runner *state.TaskRunner
o *overlord.Overlord
}

var _ = Suite(&fdeMgrSuite{})
Expand All @@ -67,8 +72,15 @@ func (s *fdeMgrSuite) SetUpTest(c *C) {
dirs.SetRootDir(s.rootdir)
s.AddCleanup(func() { dirs.SetRootDir("") })

s.st = state.New(nil)
s.runner = state.NewTaskRunner(s.st)
s.o = overlord.Mock()

s.st = s.o.State()
s.runner = s.o.TaskRunner()

s.st.Lock()
repo := interfaces.NewRepository()
ifacerepo.Replace(s.st, repo)
s.st.Unlock()

buf, restore := logger.MockLogger()
s.AddCleanup(restore)
Expand All @@ -92,6 +104,10 @@ func (s *fdeMgrSuite) SetUpTest(c *C) {
s.AddCleanup(fdestate.MockVerifyPrimaryKeyDigest(func(devicePath string, alg crypto.Hash, salt, digest []byte) (bool, error) {
panic("VerifyPrimaryKeyDigest is not mocked")
}))
s.AddCleanup(fdestate.MockBackendResealKeysForSignaturesDBUpdate(
func(mgr backend.FDEStateManager, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, update []byte) error {
panic("BackendResealKeysForSignaturesDBUpdate not mocked")
}))

m := boot.Modeenv{
Mode: boot.ModeRun,
Expand All @@ -107,6 +123,24 @@ func (s *fdeMgrSuite) TearDownTest(c *C) {
s.BaseTest.TearDownTest(c)
}

func (s *fdeMgrSuite) mockDeviceInState(model *asserts.Model) {
s.st.Lock()
defer s.st.Unlock()

s.AddCleanup(snapstatetest.MockDeviceContext(&snapstatetest.TrivialDeviceContext{
DeviceModel: model,
}))
}

func (s *fdeMgrSuite) runnerIterationLocked(c *C) {
err := func() error {
s.st.Unlock()
defer s.st.Lock()
return s.runner.Ensure()
}()
c.Assert(err, IsNil)
}

type instrumentedUnlocker struct {
state *state.State
unlocked int
Expand Down
65 changes: 19 additions & 46 deletions overlord/fdestate/fdestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,61 +25,20 @@ import (

"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/gadget/device"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/osutil/disks"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/secboot"
)

var errNotImplemented = errors.New("not implemented")

var (
disksDMCryptUUIDFromMountPoint = disks.DMCryptUUIDFromMountPoint
secbootGetPrimaryKeyDigest = secboot.GetPrimaryKeyDigest
secbootVerifyPrimaryKeyDigest = secboot.VerifyPrimaryKeyDigest
)

// EFISecureBootDBManagerStartup indicates that the local EFI key database
// manager has started.
func EFISecureBootDBManagerStartup(st *state.State) error {
if _, err := device.SealedKeysMethod(dirs.GlobalRootDir); err == device.ErrNoSealedKeys {
return nil
}

return errNotImplemented
}

type EFISecurebootKeyDatabase int

const (
EFISecurebootPK EFISecurebootKeyDatabase = iota
EFISecurebootKEK
EFISecurebootDB
EFISecurebootDBX
)

// EFISecureBootDBUpdatePrepare notifies notifies that the local EFI key
// database manager is about to update the database.
func EFISecureBootDBUpdatePrepare(st *state.State, db EFISecurebootKeyDatabase, payload []byte) error {
if _, err := device.SealedKeysMethod(dirs.GlobalRootDir); err == device.ErrNoSealedKeys {
return nil
}

return errNotImplemented
}

// EFISecureBootDBUpdateCleanup notifies that the local EFI key database manager
// has reached a cleanup stage of the update process.
func EFISecureBootDBUpdateCleanup(st *state.State) error {
if _, err := device.SealedKeysMethod(dirs.GlobalRootDir); err == device.ErrNoSealedKeys {
return nil
}

return errNotImplemented
}

// Model is a json serializable secboot.ModelForSealing
type Model struct {
SeriesValue string `json:"series"`
Expand Down Expand Up @@ -120,8 +79,8 @@ func (m *Model) SignKeyID() string {
return m.SignKeyIDValue
}

func newModel(m secboot.ModelForSealing) Model {
return Model{
func newModel(m secboot.ModelForSealing) *Model {
return &Model{
SeriesValue: m.Series(),
BrandIDValue: m.BrandID(),
ModelValue: m.Model(),
Expand Down Expand Up @@ -317,8 +276,7 @@ func (s *FdeState) updateParameters(role string, containerRole string, bootModes

var convertedModels []*Model
for _, model := range models {
m := newModel(model)
convertedModels = append(convertedModels, &m)
convertedModels = append(convertedModels, newModel(model))
}

if roleInfo.Parameters == nil {
Expand Down Expand Up @@ -392,6 +350,21 @@ func withFdeState(st *state.State, op func(fdeSt *FdeState) (modified bool, err
return nil
}

// fdeRelevantSnaps returns a list of snaps that are relevant in the context of
// FDE and associated boot policies. Specifically this includes the kernel,
// gadget and base snaps.
func fdeRelevantSnaps(st *state.State) ([]string, error) {
devCtx, err := snapstate.DeviceCtx(st, nil, nil)
if err != nil {
return nil, err
}

// these snaps, or either their content is measured during boot
// TODO do we need anything for components?

return []string{devCtx.Gadget(), devCtx.Kernel(), devCtx.Base()}, nil
}

func MockDMCryptUUIDFromMountPoint(f func(mountpoint string) (string, error)) (restore func()) {
osutil.MustBeTestBinary("mocking DMCryptUUIDFromMountPoint can be done only from tests")

Expand Down
Binary file not shown.
Loading

0 comments on commit 5802840

Please sign in to comment.