Skip to content

Commit 179d439

Browse files
committed
overlord/fdestate: run post install checks during auto repair
1 parent c5091e2 commit 179d439

9 files changed

Lines changed: 377 additions & 32 deletions

File tree

overlord/devicestate/devicestate_systems_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3439,7 +3439,7 @@ func mockHelperForEncryptionAvailabilityCheck(s suiteWithAddCleanup, c *C, isSup
34393439
s.DeviceManager().SetEncryptionSupportInfoInCacheUnlocked(cacheLabel, encInfo)
34403440
}
34413441

3442-
restore := install.MockSecbootPreinstallCheck(func(ctx context.Context, bootImagePaths []string) (*secboot.PreinstallCheckContext, []secboot.PreinstallErrorDetails, error) {
3442+
restore := install.MockSecbootPreinstallCheck(func(ctx context.Context, bootImagePaths []bootloader.BootFile) (*secboot.PreinstallCheckContext, []secboot.PreinstallErrorDetails, error) {
34433443
callCnt.checkCnt++
34443444
c.Assert(bootImagePaths, HasLen, 3)
34453445
c.Assert(isSupportedUbuntuHybrid, Equals, true)

overlord/fdestate/autorepair.go

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@
2020
package fdestate
2121

2222
import (
23+
"context"
2324
"errors"
2425
"fmt"
2526
"os"
27+
"path/filepath"
28+
"strings"
2629

2730
"github.com/snapcore/snapd/boot"
31+
"github.com/snapcore/snapd/bootloader"
2832
"github.com/snapcore/snapd/dirs"
2933
"github.com/snapcore/snapd/gadget/device"
3034
"github.com/snapcore/snapd/logger"
@@ -34,8 +38,13 @@ import (
3438
)
3539

3640
var (
41+
bootloaderFind = bootloader.Find
42+
43+
bootReadModeenv = boot.ReadModeenv
44+
3745
secbootProvisionTPM = secboot.ProvisionTPM
3846
secbootShouldAttemptRepair = secboot.ShouldAttemptRepair
47+
secbootPostinstallCheck = secboot.PostinstallCheck
3948

4049
osutilBootID = osutil.BootID
4150
)
@@ -97,6 +106,105 @@ func getRepairAttemptResult(st *state.State) (*repairState, error) {
97106
return rs.State, nil
98107
}
99108

109+
func getBootChain() ([]bootloader.BootFile, error) {
110+
modeenv, err := bootReadModeenv(dirs.GlobalRootDir)
111+
if err != nil {
112+
return nil, fmt.Errorf("cannot read modeenv: %w", err)
113+
}
114+
115+
rbl, err := bootloaderFind(boot.InitramfsUbuntuSeedDir, &bootloader.Options{
116+
Role: bootloader.RoleRecovery,
117+
})
118+
if err != nil {
119+
return nil, fmt.Errorf("cannot find recovery bootloader: %w", err)
120+
}
121+
122+
tbl, ok := rbl.(bootloader.TrustedAssetsBootloader)
123+
if !ok {
124+
return nil, fmt.Errorf("internal error: recovery bootloader does not support trusted assets")
125+
}
126+
127+
bl, err := bootloaderFind(boot.InitramfsUbuntuBootDir, &bootloader.Options{
128+
Role: bootloader.RoleRunMode,
129+
NoSlashBoot: true,
130+
})
131+
if err != nil {
132+
return nil, fmt.Errorf("cannot find run bootloader: %w", err)
133+
}
134+
135+
ebl, ok := bl.(bootloader.ExtractedRunKernelImageBootloader)
136+
if !ok {
137+
return nil, fmt.Errorf("internal error: run bootloader does not support kernel extraction")
138+
}
139+
140+
info, err := ebl.TryKernel()
141+
if err != nil {
142+
if err == bootloader.ErrNoTryKernelRef {
143+
info, err = ebl.Kernel()
144+
}
145+
if err != nil {
146+
return nil, err
147+
}
148+
}
149+
150+
trustedAssets, err := tbl.TrustedAssets()
151+
if err != nil {
152+
return nil, err
153+
}
154+
155+
kernelPath := info.MountFile()
156+
157+
runModeBootChains, err := tbl.BootChains(bl, kernelPath)
158+
if err != nil {
159+
return nil, err
160+
}
161+
162+
// runModeBootChains is all possible run boot chains, but only one should exist (there
163+
// are legacy boot chains before we registered UEFI boot entries).
164+
// The "BootFile"s for the gadget part points to identifier names instead of real path, so we
165+
// need to resolve those. To resolve those we need to cross check with the modeenv, and then
166+
// find the file in the cache. The last one, is the kernel and should be pointing to the right place.
167+
for _, runModeBootChain := range runModeBootChains {
168+
var chain []bootloader.BootFile
169+
170+
if len(runModeBootChain) == 0 {
171+
// That is not possible for a boot chain to be size 0, because that would mean there is no
172+
// kernel. We should not ignore this, there are bigger problems.
173+
return nil, fmt.Errorf("internal error: no file in boot chain")
174+
}
175+
176+
ignoreChain := false
177+
for _, bf := range runModeBootChain[:len(runModeBootChain)-1] {
178+
path := bf.Path
179+
name, ok := trustedAssets[path]
180+
if !ok {
181+
return nil, fmt.Errorf("internal error: unknown trusted asset %s from boot chain", path)
182+
}
183+
var hashes []string
184+
if bf.Role == bootloader.RoleRecovery {
185+
hashes, ok = modeenv.CurrentTrustedRecoveryBootAssets[name]
186+
} else {
187+
hashes, ok = modeenv.CurrentTrustedBootAssets[name]
188+
}
189+
if !ok {
190+
ignoreChain = true
191+
break
192+
}
193+
194+
// In theory we should only have one hash here. Multiple would be when we are trying
195+
// a boot chain, and this should have been cleaned. It should be safe to take the last one (newest).
196+
hash := hashes[len(hashes)-1]
197+
p := filepath.Join(dirs.SnapBootAssetsDir, bl.Name(), fmt.Sprintf("%s-%s", name, hash))
198+
chain = append(chain, bootloader.NewBootFile("", p, bf.Role))
199+
}
200+
if !ignoreChain {
201+
return append(chain, runModeBootChain[len(runModeBootChain)-1]), nil
202+
}
203+
}
204+
205+
return nil, fmt.Errorf("cannot find the active boot chain")
206+
}
207+
100208
func autoRepair(st *state.State) (AutoRepairResult, error) {
101209
method, err := device.SealedKeysMethod(dirs.GlobalRootDir)
102210
if err != nil {
@@ -106,8 +214,23 @@ func autoRepair(st *state.State) (AutoRepairResult, error) {
106214
switch method {
107215
case device.SealingMethodFDESetupHook:
108216
case device.SealingMethodTPM, device.SealingMethodLegacyTPM:
109-
// FIXME: re-run platform checks (post install checks?)
110-
// Then maybe return AutoRepairFailedEncryptionSupport
217+
images, err := getBootChain()
218+
if err != nil {
219+
return AutoRepairNotAttempted, err
220+
}
221+
222+
if _, details, err := secbootPostinstallCheck(context.Background(), images); len(details) > 0 || err != nil {
223+
if err != nil {
224+
logger.Noticef("WARNING: could not auto repair keyslots due to failed platform initialization: %v", err)
225+
} else {
226+
var messages []string
227+
for _, detail := range details {
228+
messages = append(messages, fmt.Sprintf("- %s", detail.Message))
229+
}
230+
logger.Noticef("WARNING: could not auto repair keyslots due to failed platform initialization:\n%s", strings.Join(messages, "\n"))
231+
}
232+
return AutoRepairFailedPlatformInit, nil
233+
}
111234

112235
lockoutAuthFile := device.TpmLockoutAuthUnder(boot.InstallHostFDESaveDir)
113236
if err := secbootProvisionTPM(secboot.TPMPartialReprovision, lockoutAuthFile); err != nil {

overlord/fdestate/autorepair_test.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,23 @@
2121
package fdestate_test
2222

2323
import (
24+
"context"
2425
"fmt"
2526
"os"
2627

2728
. "gopkg.in/check.v1"
2829

2930
sb "github.com/snapcore/secboot"
3031
"github.com/snapcore/snapd/boot"
32+
"github.com/snapcore/snapd/bootloader"
33+
"github.com/snapcore/snapd/bootloader/bootloadertest"
3134
"github.com/snapcore/snapd/dirs"
3235
"github.com/snapcore/snapd/gadget/device"
3336
"github.com/snapcore/snapd/logger"
3437
"github.com/snapcore/snapd/overlord/fdestate"
3538
"github.com/snapcore/snapd/overlord/fdestate/backend"
3639
"github.com/snapcore/snapd/secboot"
40+
"github.com/snapcore/snapd/snap"
3741
"github.com/snapcore/snapd/testutil"
3842
)
3943

@@ -54,6 +58,58 @@ func (s *autoRepairSuite) SetUpTest(c *C) {
5458
}))
5559
}
5660

61+
func (s *autoRepairSuite) mockPostInstallChecks(c *C) {
62+
recoveryBl := bootloadertest.Mock("recovery", "").WithTrustedAssets()
63+
recoveryBl.TrustedAssetsMap = map[string]string{
64+
"EFI/ubuntu/shim.efi": "ubuntu:shim",
65+
"EFI/ubuntu/grub.efi": "ubuntu:grub",
66+
}
67+
recoveryBl.KernelBootFileBuilder = func(kernelPath string) bootloader.BootFile {
68+
return bootloader.NewBootFile("some-kernel", "kernel.efi", bootloader.RoleRunMode)
69+
}
70+
recoveryBl.BootChainList = []bootloader.BootFile{
71+
bootloader.NewBootFile("", "EFI/ubuntu/shim.efi", bootloader.RoleRecovery),
72+
bootloader.NewBootFile("", "EFI/ubuntu/grub.efi", bootloader.RoleRecovery),
73+
bootloader.NewBootFile("", "EFI/ubuntu/grub.efi", bootloader.RoleRunMode),
74+
}
75+
76+
runBl := bootloadertest.Mock("run", "").WithExtractedRunKernelImage()
77+
runBl.SetEnabledKernel(&snap.Info{SuggestedName: "some-kernel", InstanceKey: "x1", SnapType: snap.TypeKernel})
78+
79+
s.AddCleanup(fdestate.MockBootloaderFind(func(rootdir string, opts *bootloader.Options) (bootloader.Bootloader, error) {
80+
if opts.Role == bootloader.RoleRecovery {
81+
return recoveryBl, nil
82+
} else if opts.Role == bootloader.RoleRunMode {
83+
return runBl, nil
84+
} else {
85+
c.Errorf("unexpected")
86+
return nil, fmt.Errorf("unexpected")
87+
}
88+
}))
89+
90+
s.AddCleanup(fdestate.MockBootReadModeenv(func(rootdir string) (*boot.Modeenv, error) {
91+
return &boot.Modeenv{
92+
CurrentTrustedBootAssets: map[string][]string{
93+
"ubuntu:grub": {
94+
"hash-grub-run",
95+
},
96+
},
97+
CurrentTrustedRecoveryBootAssets: map[string][]string{
98+
"ubuntu:shim": {
99+
"hash-shim-recovery",
100+
},
101+
"ubuntu:grub": {
102+
"hash-grub-recovery",
103+
},
104+
},
105+
}, nil
106+
}))
107+
108+
s.AddCleanup(fdestate.MockSecbootPostinstallCheck(func(ctx context.Context, bootImagePaths []bootloader.BootFile) (*secboot.PreinstallCheckContext, []secboot.PreinstallErrorDetails, error) {
109+
return nil, nil, nil
110+
}))
111+
}
112+
57113
func (s *autoRepairSuite) TestAttemptAutoRepairNeeded(c *C) {
58114
const onClassic = false
59115
s.startedManager(c, onClassic)
@@ -85,6 +141,8 @@ func (s *autoRepairSuite) TestAttemptAutoRepairNeeded(c *C) {
85141
return nil
86142
})()
87143

144+
s.mockPostInstallChecks(c)
145+
88146
err := fdestate.AttemptAutoRepairIfNeeded(s.st, nil)
89147
c.Assert(err, IsNil)
90148

@@ -174,6 +232,8 @@ func (s *autoRepairSuite) TestAttemptAutoRepairNeededBadReprovision(c *C) {
174232
return fmt.Errorf("Unexpected call")
175233
})()
176234

235+
s.mockPostInstallChecks(c)
236+
177237
err := fdestate.AttemptAutoRepairIfNeeded(s.st, nil)
178238
c.Assert(err, IsNil)
179239

@@ -251,6 +311,8 @@ func (s *autoRepairSuite) TestAttemptAutoRepairErrorNoActivateStateRecovery(c *C
251311
}, nil
252312
})()
253313

314+
s.mockPostInstallChecks(c)
315+
254316
logbuf, restore := logger.MockLogger()
255317
defer restore()
256318

@@ -355,6 +417,8 @@ func (s *autoRepairSuite) TestAttemptAutoRepairNeededBadReseal(c *C) {
355417
return fmt.Errorf("some error")
356418
})()
357419

420+
s.mockPostInstallChecks(c)
421+
358422
err := fdestate.AttemptAutoRepairIfNeeded(s.st, nil)
359423
c.Assert(err, IsNil)
360424

@@ -390,3 +454,106 @@ func (s *autoRepairSuite) TestIgnoreOldAutoRepairResult(c *C) {
390454
c.Assert(err, IsNil)
391455
c.Check(result.Result, Equals, fdestate.AutoRepairResult("failed-platform-init"))
392456
}
457+
458+
func (s *autoRepairSuite) TestAttemptAutoRepairFailedPostinstallChecks(c *C) {
459+
const onClassic = false
460+
s.startedManager(c, onClassic)
461+
462+
s.st.Lock()
463+
defer s.st.Unlock()
464+
465+
c.Assert(device.StampSealedKeys(dirs.GlobalRootDir, device.SealingMethodTPM), IsNil)
466+
467+
s.createUnlockedState(c, sb.ActivationSucceededWithPlatformKey)
468+
469+
defer fdestate.MockSecbootProvisionTPM(func(mode secboot.TPMProvisionMode, lockoutAuthFile string) error {
470+
c.Errorf("unexpected call")
471+
return fmt.Errorf("unexpected call")
472+
})()
473+
474+
defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState) bool {
475+
return true
476+
})()
477+
478+
s.mockBootAssetsStateForModeenv(c)
479+
480+
defer fdestate.MockBackendResealKeyForBootChains(func(manager backend.FDEStateManager, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams) error {
481+
c.Errorf("unexpected call")
482+
return fmt.Errorf("unexpected call")
483+
})()
484+
485+
s.mockPostInstallChecks(c)
486+
defer fdestate.MockSecbootPostinstallCheck(func(ctx context.Context, bootImagePaths []bootloader.BootFile) (*secboot.PreinstallCheckContext, []secboot.PreinstallErrorDetails, error) {
487+
return nil, nil, fmt.Errorf("some error")
488+
})()
489+
490+
logbuf, restore := logger.MockLogger()
491+
defer restore()
492+
493+
err := fdestate.AttemptAutoRepairIfNeeded(s.st, nil)
494+
c.Assert(err, IsNil)
495+
496+
result, err := fdestate.GetRepairAttemptResult(s.st)
497+
c.Assert(err, IsNil)
498+
499+
c.Check(result.Result, Equals, fdestate.AutoRepairResult("failed-platform-init"))
500+
501+
c.Check(logbuf.String(), testutil.Contains, `WARNING: could not auto repair keyslots due to failed platform initialization: some error`)
502+
}
503+
504+
func (s *autoRepairSuite) TestAttemptAutoRepairFailedPostinstallChecksWithDetails(c *C) {
505+
const onClassic = false
506+
s.startedManager(c, onClassic)
507+
508+
s.st.Lock()
509+
defer s.st.Unlock()
510+
511+
c.Assert(device.StampSealedKeys(dirs.GlobalRootDir, device.SealingMethodTPM), IsNil)
512+
513+
s.createUnlockedState(c, sb.ActivationSucceededWithPlatformKey)
514+
515+
defer fdestate.MockSecbootProvisionTPM(func(mode secboot.TPMProvisionMode, lockoutAuthFile string) error {
516+
c.Errorf("unexpected call")
517+
return fmt.Errorf("unexpected call")
518+
})()
519+
520+
defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState) bool {
521+
return true
522+
})()
523+
524+
s.mockBootAssetsStateForModeenv(c)
525+
526+
defer fdestate.MockBackendResealKeyForBootChains(func(manager backend.FDEStateManager, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams) error {
527+
c.Errorf("unexpected call")
528+
return fmt.Errorf("unexpected call")
529+
})()
530+
531+
s.mockPostInstallChecks(c)
532+
defer fdestate.MockSecbootPostinstallCheck(func(ctx context.Context, bootImagePaths []bootloader.BootFile) (*secboot.PreinstallCheckContext, []secboot.PreinstallErrorDetails, error) {
533+
var details = []secboot.PreinstallErrorDetails{
534+
{
535+
Kind: "kind-1",
536+
Message: "error-1",
537+
},
538+
{
539+
Kind: "kind-2",
540+
Message: "error-2",
541+
},
542+
}
543+
544+
return nil, details, nil
545+
})()
546+
547+
logbuf, restore := logger.MockLogger()
548+
defer restore()
549+
550+
err := fdestate.AttemptAutoRepairIfNeeded(s.st, nil)
551+
c.Assert(err, IsNil)
552+
553+
result, err := fdestate.GetRepairAttemptResult(s.st)
554+
c.Assert(err, IsNil)
555+
556+
c.Check(result.Result, Equals, fdestate.AutoRepairResult("failed-platform-init"))
557+
558+
c.Check(logbuf.String(), testutil.Contains, "WARNING: could not auto repair keyslots due to failed platform initialization:\n- error-1\n- error-2\n")
559+
}

0 commit comments

Comments
 (0)