github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/boot/systems_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 Canonical Ltd
     5  *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18  */
    19  
    20  package boot_test
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/boot"
    31  	"github.com/snapcore/snapd/boot/boottest"
    32  	"github.com/snapcore/snapd/bootloader"
    33  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    34  	"github.com/snapcore/snapd/secboot"
    35  	"github.com/snapcore/snapd/seed"
    36  	"github.com/snapcore/snapd/snap"
    37  	"github.com/snapcore/snapd/testutil"
    38  	"github.com/snapcore/snapd/timings"
    39  )
    40  
    41  type baseSystemsSuite struct {
    42  	baseBootenvSuite
    43  }
    44  
    45  func (s *baseSystemsSuite) SetUpTest(c *C) {
    46  	s.baseBootenvSuite.SetUpTest(c)
    47  	c.Assert(os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755), IsNil)
    48  	c.Assert(os.MkdirAll(boot.InitramfsUbuntuSeedDir, 0755), IsNil)
    49  }
    50  
    51  type systemsSuite struct {
    52  	baseSystemsSuite
    53  
    54  	uc20dev boot.Device
    55  
    56  	runKernelBf      bootloader.BootFile
    57  	recoveryKernelBf bootloader.BootFile
    58  	seedKernelSnap   *seed.Snap
    59  	seedGadgetSnap   *seed.Snap
    60  }
    61  
    62  var _ = Suite(&systemsSuite{})
    63  
    64  func (s *systemsSuite) mockTrustedBootloaderWithAssetAndChains(c *C, runKernelBf, recoveryKernelBf bootloader.BootFile) *bootloadertest.MockTrustedAssetsBootloader {
    65  	mockAssetsCache(c, s.rootdir, "trusted", []string{
    66  		"asset-asset-hash-1",
    67  	})
    68  
    69  	mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets()
    70  	mtbl.TrustedAssetsList = []string{"asset-1"}
    71  	mtbl.StaticCommandLine = "static cmdline"
    72  	mtbl.BootChainList = []bootloader.BootFile{
    73  		bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
    74  		runKernelBf,
    75  	}
    76  	mtbl.RecoveryBootChainList = []bootloader.BootFile{
    77  		bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
    78  		recoveryKernelBf,
    79  	}
    80  	bootloader.Force(mtbl)
    81  	return mtbl
    82  }
    83  
    84  func (s *systemsSuite) SetUpTest(c *C) {
    85  	s.baseBootenvSuite.SetUpTest(c)
    86  
    87  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { return nil })
    88  	s.AddCleanup(restore)
    89  
    90  	s.uc20dev = boottest.MockUC20Device("", nil)
    91  
    92  	// run kernel
    93  	s.runKernelBf = bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_500.snap",
    94  		"kernel.efi", bootloader.RoleRunMode)
    95  	// seed (recovery) kernel
    96  	s.recoveryKernelBf = bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap",
    97  		"kernel.efi", bootloader.RoleRecovery)
    98  
    99  	s.seedKernelSnap = mockKernelSeedSnap(c, snap.R(1))
   100  	s.seedGadgetSnap = mockGadgetSeedSnap(c, nil)
   101  }
   102  
   103  func (s *systemsSuite) TestSetTryRecoverySystemEncrypted(c *C) {
   104  	mockAssetsCache(c, s.rootdir, "trusted", []string{
   105  		"asset-asset-hash-1",
   106  	})
   107  
   108  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   109  	bootloader.Force(mtbl)
   110  	defer bootloader.Force(nil)
   111  
   112  	// system is encrypted
   113  	s.stampSealedKeys(c, s.rootdir)
   114  
   115  	model := s.uc20dev.Model()
   116  
   117  	modeenv := &boot.Modeenv{
   118  		Mode: "run",
   119  		// keep this comment to make old gofmt happy
   120  		CurrentRecoverySystems: []string{"20200825"},
   121  		GoodRecoverySystems:    []string{"20200825"},
   122  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   123  			"asset": []string{"asset-hash-1"},
   124  		},
   125  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   126  			"asset": []string{"asset-hash-1"},
   127  		},
   128  		CurrentKernels: []string{"pc-kernel_500.snap"},
   129  
   130  		Model:          model.Model(),
   131  		BrandID:        model.BrandID(),
   132  		Grade:          string(model.Grade()),
   133  		ModelSignKeyID: model.SignKeyID(),
   134  	}
   135  	c.Assert(modeenv.WriteTo(""), IsNil)
   136  
   137  	var readSeedSeenLabels []string
   138  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   139  		// the mock bootloader can only mock a single recovery boot
   140  		// chain, so pretend both seeds use the same kernel, but keep track of the labels
   141  		readSeedSeenLabels = append(readSeedSeenLabels, label)
   142  		return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
   143  	})
   144  	defer restore()
   145  
   146  	resealCalls := 0
   147  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   148  		resealCalls++
   149  		// bootloader variables have already been modified
   150  		c.Check(mtbl.SetBootVarsCalls, Equals, 1)
   151  		c.Assert(params, NotNil)
   152  		c.Assert(params.ModelParams, HasLen, 1)
   153  		switch resealCalls {
   154  		case 1:
   155  			c.Check(params.KeyFiles, DeepEquals, []string{
   156  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   157  			})
   158  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   159  				"snapd_recovery_mode=recover snapd_recovery_system=1234 static cmdline",
   160  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
   161  				"snapd_recovery_mode=run static cmdline",
   162  			})
   163  			return nil
   164  		case 2:
   165  			c.Check(params.KeyFiles, DeepEquals, []string{
   166  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   167  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   168  			})
   169  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   170  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
   171  			})
   172  			return nil
   173  		default:
   174  			c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
   175  			return fmt.Errorf("unexpected call")
   176  		}
   177  	})
   178  	defer restore()
   179  
   180  	err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
   181  	c.Assert(err, IsNil)
   182  
   183  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   184  	c.Assert(err, IsNil)
   185  	c.Check(vars, DeepEquals, map[string]string{
   186  		"try_recovery_system":    "1234",
   187  		"recovery_system_status": "try",
   188  	})
   189  	// run and recovery keys
   190  	c.Check(resealCalls, Equals, 2)
   191  	c.Check(readSeedSeenLabels, DeepEquals, []string{
   192  		"20200825", "1234", // current recovery systems for run key
   193  		"20200825", // good recovery systems for recovery keys
   194  	})
   195  
   196  	modeenvRead, err := boot.ReadModeenv("")
   197  	c.Assert(err, IsNil)
   198  	c.Check(modeenvRead.DeepEqual(&boot.Modeenv{
   199  		Mode: "run",
   200  		// keep this comment to make old gofmt happy
   201  		CurrentRecoverySystems: []string{"20200825", "1234"},
   202  		GoodRecoverySystems:    []string{"20200825"},
   203  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   204  			"asset": []string{"asset-hash-1"},
   205  		},
   206  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   207  			"asset": []string{"asset-hash-1"},
   208  		},
   209  		CurrentKernels: []string{"pc-kernel_500.snap"},
   210  
   211  		Model:          model.Model(),
   212  		BrandID:        model.BrandID(),
   213  		Grade:          string(model.Grade()),
   214  		ModelSignKeyID: model.SignKeyID(),
   215  	}), Equals, true)
   216  }
   217  
   218  func (s *systemsSuite) TestSetTryRecoverySystemRemodelEncrypted(c *C) {
   219  	mockAssetsCache(c, s.rootdir, "trusted", []string{
   220  		"asset-asset-hash-1",
   221  	})
   222  
   223  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   224  	bootloader.Force(mtbl)
   225  	defer bootloader.Force(nil)
   226  
   227  	// system is encrypted
   228  	s.stampSealedKeys(c, s.rootdir)
   229  
   230  	model := s.uc20dev.Model()
   231  	newModel := boottest.MakeMockUC20Model(map[string]interface{}{
   232  		"model": "my-new-model",
   233  	})
   234  
   235  	modeenv := &boot.Modeenv{
   236  		Mode: "run",
   237  		// keep this comment to make old gofmt happy
   238  		CurrentRecoverySystems: []string{"20200825"},
   239  		GoodRecoverySystems:    []string{"20200825"},
   240  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   241  			"asset": []string{"asset-hash-1"},
   242  		},
   243  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   244  			"asset": []string{"asset-hash-1"},
   245  		},
   246  		CurrentKernels: []string{"pc-kernel_500.snap"},
   247  
   248  		Model:          model.Model(),
   249  		BrandID:        model.BrandID(),
   250  		Grade:          string(model.Grade()),
   251  		ModelSignKeyID: model.SignKeyID(),
   252  	}
   253  	c.Assert(modeenv.WriteTo(""), IsNil)
   254  
   255  	var readSeedSeenLabels []string
   256  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   257  		// the mock bootloader can only mock a single recovery boot
   258  		// chain, so pretend both seeds use the same kernel, but keep track of the labels
   259  		readSeedSeenLabels = append(readSeedSeenLabels, label)
   260  		systemModel := model
   261  		if label == "1234" {
   262  			systemModel = newModel
   263  		}
   264  		return systemModel, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
   265  	})
   266  	defer restore()
   267  
   268  	resealCalls := 0
   269  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   270  		resealCalls++
   271  		// bootloader variables have already been modified
   272  		c.Check(mtbl.SetBootVarsCalls, Equals, 1)
   273  		c.Assert(params, NotNil)
   274  		switch resealCalls {
   275  		case 1:
   276  			c.Assert(params.ModelParams, HasLen, 2)
   277  			c.Check(params.KeyFiles, DeepEquals, []string{
   278  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   279  			})
   280  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   281  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
   282  				"snapd_recovery_mode=run static cmdline",
   283  			})
   284  			c.Assert(params.ModelParams[1].KernelCmdlines, DeepEquals, []string{
   285  				"snapd_recovery_mode=recover snapd_recovery_system=1234 static cmdline",
   286  				"snapd_recovery_mode=run static cmdline",
   287  			})
   288  			return nil
   289  		case 2:
   290  			c.Assert(params.ModelParams, HasLen, 1)
   291  			c.Check(params.KeyFiles, DeepEquals, []string{
   292  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   293  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   294  			})
   295  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   296  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
   297  			})
   298  			return nil
   299  		default:
   300  			c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
   301  			return fmt.Errorf("unexpected call")
   302  		}
   303  	})
   304  	defer restore()
   305  
   306  	// a remodel will pass the new device
   307  	newUC20Device := boottest.MockUC20Device("run", newModel)
   308  	err := boot.SetTryRecoverySystem(newUC20Device, "1234")
   309  	c.Assert(err, IsNil)
   310  
   311  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   312  	c.Assert(err, IsNil)
   313  	c.Check(vars, DeepEquals, map[string]string{
   314  		"try_recovery_system":    "1234",
   315  		"recovery_system_status": "try",
   316  	})
   317  	// run and recovery keys
   318  	c.Check(resealCalls, Equals, 2)
   319  	c.Check(readSeedSeenLabels, DeepEquals, []string{
   320  		"20200825", "1234", // current recovery systems for run key and current model from modeenv
   321  		"20200825", "1234", // current recovery systems for run key and try model from modeenv
   322  		"20200825", // good recovery systems for recovery keys
   323  	})
   324  
   325  	modeenvRead, err := boot.ReadModeenv("")
   326  	c.Assert(err, IsNil)
   327  	c.Check(modeenvRead, testutil.JsonEquals, boot.Modeenv{
   328  		Mode: "run",
   329  		// keep this comment to make old gofmt happy
   330  		CurrentRecoverySystems: []string{"20200825", "1234"},
   331  		GoodRecoverySystems:    []string{"20200825"},
   332  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   333  			"asset": []string{"asset-hash-1"},
   334  		},
   335  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   336  			"asset": []string{"asset-hash-1"},
   337  		},
   338  		CurrentKernels: []string{"pc-kernel_500.snap"},
   339  
   340  		Model:          model.Model(),
   341  		BrandID:        model.BrandID(),
   342  		Grade:          string(model.Grade()),
   343  		ModelSignKeyID: model.SignKeyID(),
   344  
   345  		TryModel:          newModel.Model(),
   346  		TryBrandID:        newModel.BrandID(),
   347  		TryGrade:          string(newModel.Grade()),
   348  		TryModelSignKeyID: newModel.SignKeyID(),
   349  	})
   350  }
   351  
   352  func (s *systemsSuite) TestSetTryRecoverySystemSimple(c *C) {
   353  	mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets()
   354  	bootloader.Force(mtbl)
   355  	defer bootloader.Force(nil)
   356  
   357  	model := s.uc20dev.Model()
   358  	modeenv := &boot.Modeenv{
   359  		Mode: "run",
   360  		// keep this comment to make old gofmt happy
   361  		CurrentRecoverySystems: []string{"20200825"},
   362  
   363  		Model:          model.Model(),
   364  		BrandID:        model.BrandID(),
   365  		Grade:          string(model.Grade()),
   366  		ModelSignKeyID: model.SignKeyID(),
   367  	}
   368  	c.Assert(modeenv.WriteTo(""), IsNil)
   369  
   370  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   371  		return fmt.Errorf("unexpected call")
   372  	})
   373  	s.AddCleanup(restore)
   374  
   375  	err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
   376  	c.Assert(err, IsNil)
   377  
   378  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   379  	c.Assert(err, IsNil)
   380  	c.Check(vars, DeepEquals, map[string]string{
   381  		"try_recovery_system":    "1234",
   382  		"recovery_system_status": "try",
   383  	})
   384  
   385  	modeenvRead, err := boot.ReadModeenv("")
   386  	c.Assert(err, IsNil)
   387  	c.Check(modeenvRead, testutil.JsonEquals, boot.Modeenv{
   388  		Mode: "run",
   389  		// keep this comment to make old gofmt happy
   390  		CurrentRecoverySystems: []string{"20200825", "1234"},
   391  
   392  		Model:          model.Model(),
   393  		BrandID:        model.BrandID(),
   394  		Grade:          string(model.Grade()),
   395  		ModelSignKeyID: model.SignKeyID(),
   396  	})
   397  }
   398  
   399  func (s *systemsSuite) TestSetTryRecoverySystemSetBootVarsErr(c *C) {
   400  	mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets()
   401  	bootloader.Force(mtbl)
   402  	defer bootloader.Force(nil)
   403  
   404  	modeenv := &boot.Modeenv{
   405  		Mode: "run",
   406  		// keep this comment to make old gofmt happy
   407  		CurrentRecoverySystems: []string{"20200825"},
   408  	}
   409  	c.Assert(modeenv.WriteTo(""), IsNil)
   410  
   411  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   412  		return fmt.Errorf("unexpected call")
   413  	})
   414  	s.AddCleanup(restore)
   415  
   416  	mtbl.BootVars = map[string]string{
   417  		"try_recovery_system":    "mock",
   418  		"recovery_system_status": "mock",
   419  	}
   420  	mtbl.SetErrFunc = func() error {
   421  		switch mtbl.SetBootVarsCalls {
   422  		case 1:
   423  			return fmt.Errorf("set boot vars fails")
   424  		case 2:
   425  			// called during cleanup
   426  			return nil
   427  		default:
   428  			return fmt.Errorf("unexpected call")
   429  		}
   430  	}
   431  
   432  	err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
   433  	c.Assert(err, ErrorMatches, "set boot vars fails")
   434  
   435  	// cleared
   436  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   437  	c.Assert(err, IsNil)
   438  	c.Check(vars, DeepEquals, map[string]string{
   439  		"try_recovery_system":    "",
   440  		"recovery_system_status": "",
   441  	})
   442  
   443  	modeenvRead, err := boot.ReadModeenv("")
   444  	c.Assert(err, IsNil)
   445  	// modeenv is unchanged
   446  	c.Check(modeenvRead.DeepEqual(modeenv), Equals, true)
   447  }
   448  
   449  func (s *systemsSuite) TestSetTryRecoverySystemCleanupOnErrorBeforeReseal(c *C) {
   450  	mockAssetsCache(c, s.rootdir, "trusted", []string{
   451  		"asset-asset-hash-1",
   452  	})
   453  
   454  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   455  	defer bootloader.Force(nil)
   456  
   457  	// system is encrypted
   458  	s.stampSealedKeys(c, s.rootdir)
   459  
   460  	model := s.uc20dev.Model()
   461  
   462  	modeenv := &boot.Modeenv{
   463  		Mode: "run",
   464  		// keep this comment to make old gofmt happy
   465  		CurrentRecoverySystems: []string{"20200825"},
   466  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   467  			"asset": []string{"asset-hash-1"},
   468  		},
   469  
   470  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   471  			"asset": []string{"asset-hash-1"},
   472  		},
   473  
   474  		Model:          model.Model(),
   475  		BrandID:        model.BrandID(),
   476  		Grade:          string(model.Grade()),
   477  		ModelSignKeyID: model.SignKeyID(),
   478  	}
   479  	c.Assert(modeenv.WriteTo(""), IsNil)
   480  
   481  	readSeedCalls := 0
   482  	cleanupTriggered := false
   483  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   484  		readSeedCalls++
   485  		// this is the reseal cleanup path
   486  		switch readSeedCalls {
   487  		case 1:
   488  			// called for the first system
   489  			c.Assert(label, Equals, "20200825")
   490  			c.Check(mtbl.SetBootVarsCalls, Equals, 1)
   491  			return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
   492  		case 2:
   493  			// called for the 'try' system
   494  			c.Assert(label, Equals, "1234")
   495  			// modeenv is updated first
   496  			modeenvRead, err := boot.ReadModeenv("")
   497  			c.Assert(err, IsNil)
   498  			c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{
   499  				"20200825", "1234",
   500  			})
   501  			c.Check(mtbl.SetBootVarsCalls, Equals, 1)
   502  			// we are triggering the cleanup by returning an error now
   503  			cleanupTriggered = true
   504  			return nil, nil, fmt.Errorf("seed read essential fails")
   505  		case 3:
   506  			// (cleanup) recovery boot chains for run key, called
   507  			// for the first system only
   508  			fallthrough
   509  		case 4:
   510  			// (cleanup) recovery boot chains for recovery keys
   511  			c.Assert(label, Equals, "20200825")
   512  			// boot variables already updated
   513  			c.Check(mtbl.SetBootVarsCalls, Equals, 2)
   514  			return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
   515  		default:
   516  			return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls)
   517  		}
   518  	})
   519  	defer restore()
   520  
   521  	resealCalls := 0
   522  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   523  		resealCalls++
   524  		if cleanupTriggered {
   525  			return nil
   526  		}
   527  		return fmt.Errorf("unexpected call")
   528  	})
   529  	defer restore()
   530  
   531  	err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
   532  	c.Assert(err, ErrorMatches, ".*: seed read essential fails")
   533  
   534  	// failed after the call to read the 'try' system seed
   535  	c.Check(readSeedCalls, Equals, 4)
   536  	// called twice during cleanup for run and recovery keys
   537  	c.Check(resealCalls, Equals, 2)
   538  
   539  	modeenvRead, err := boot.ReadModeenv("")
   540  	c.Assert(err, IsNil)
   541  	// modeenv is unchanged
   542  	c.Check(modeenvRead.DeepEqual(modeenv), Equals, true)
   543  	// bootloader variables have been cleared
   544  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   545  	c.Assert(err, IsNil)
   546  	c.Check(vars, DeepEquals, map[string]string{
   547  		"try_recovery_system":    "",
   548  		"recovery_system_status": "",
   549  	})
   550  }
   551  
   552  func (s *systemsSuite) TestSetTryRecoverySystemCleanupOnErrorAfterReseal(c *C) {
   553  	mockAssetsCache(c, s.rootdir, "trusted", []string{
   554  		"asset-asset-hash-1",
   555  	})
   556  
   557  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   558  	bootloader.Force(mtbl)
   559  	defer bootloader.Force(nil)
   560  
   561  	// system is encrypted
   562  	s.stampSealedKeys(c, s.rootdir)
   563  
   564  	model := s.uc20dev.Model()
   565  
   566  	modeenv := &boot.Modeenv{
   567  		Mode: "run",
   568  		// keep this comment to make old gofmt happy
   569  		CurrentRecoverySystems: []string{"20200825"},
   570  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   571  			"asset": []string{"asset-hash-1"},
   572  		},
   573  
   574  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   575  			"asset": []string{"asset-hash-1"},
   576  		},
   577  
   578  		Model:          model.Model(),
   579  		BrandID:        model.BrandID(),
   580  		Grade:          string(model.Grade()),
   581  		ModelSignKeyID: model.SignKeyID(),
   582  	}
   583  	c.Assert(modeenv.WriteTo(""), IsNil)
   584  
   585  	readSeedCalls := 0
   586  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   587  		readSeedCalls++
   588  		// this is the reseal cleanup path
   589  
   590  		switch readSeedCalls {
   591  		case 1:
   592  			// called for the first system
   593  			c.Assert(label, Equals, "20200825")
   594  			c.Check(mtbl.SetBootVarsCalls, Equals, 1)
   595  			return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
   596  		case 2:
   597  			// called for the 'try' system
   598  			c.Assert(label, Equals, "1234")
   599  			// modeenv is updated first
   600  			modeenvRead, err := boot.ReadModeenv("")
   601  			c.Assert(err, IsNil)
   602  			c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{
   603  				"20200825", "1234",
   604  			})
   605  			c.Check(mtbl.SetBootVarsCalls, Equals, 1)
   606  			// still good
   607  			return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
   608  		case 3:
   609  			// recovery boot chains for a good recovery system
   610  			c.Check(mtbl.SetBootVarsCalls, Equals, 1)
   611  			fallthrough
   612  		case 4:
   613  			// (cleanup) recovery boot chains for run key, called
   614  			// for the first system only
   615  			fallthrough
   616  		case 5:
   617  			// (cleanup) recovery boot chains for recovery keys
   618  			c.Assert(label, Equals, "20200825")
   619  			// boot variables already updated
   620  			if readSeedCalls >= 4 {
   621  				c.Check(mtbl.SetBootVarsCalls, Equals, 2)
   622  			}
   623  			return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
   624  		default:
   625  			return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls)
   626  		}
   627  	})
   628  	defer restore()
   629  
   630  	resealCalls := 0
   631  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   632  		resealCalls++
   633  		switch resealCalls {
   634  		case 1:
   635  			// attempt to reseal the run key
   636  			return fmt.Errorf("reseal fails")
   637  		case 2, 3:
   638  			// reseal of run and recovery keys
   639  			return nil
   640  		default:
   641  			return fmt.Errorf("unexpected call")
   642  
   643  		}
   644  	})
   645  	defer restore()
   646  
   647  	err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
   648  	c.Assert(err, ErrorMatches, "cannot reseal the encryption key: reseal fails")
   649  
   650  	// failed after the call to read the 'try' system seed
   651  	c.Check(readSeedCalls, Equals, 5)
   652  	// called 3 times, once when mocked failure occurs, twice during cleanup
   653  	// for run and recovery keys
   654  	c.Check(resealCalls, Equals, 3)
   655  
   656  	modeenvRead, err := boot.ReadModeenv("")
   657  	c.Assert(err, IsNil)
   658  	// modeenv is unchanged
   659  	c.Check(modeenvRead.DeepEqual(modeenv), Equals, true)
   660  	// bootloader variables have been cleared
   661  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   662  	c.Assert(err, IsNil)
   663  	c.Check(vars, DeepEquals, map[string]string{
   664  		"try_recovery_system":    "",
   665  		"recovery_system_status": "",
   666  	})
   667  }
   668  
   669  func (s *systemsSuite) TestSetTryRecoverySystemCleanupError(c *C) {
   670  	mockAssetsCache(c, s.rootdir, "trusted", []string{
   671  		"asset-asset-hash-1",
   672  	})
   673  
   674  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   675  	bootloader.Force(mtbl)
   676  	defer bootloader.Force(nil)
   677  
   678  	// system is encrypted
   679  	s.stampSealedKeys(c, s.rootdir)
   680  
   681  	model := s.uc20dev.Model()
   682  	modeenv := &boot.Modeenv{
   683  		Mode: "run",
   684  		// keep this comment to make old gofmt happy
   685  		CurrentRecoverySystems: []string{"20200825"},
   686  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   687  			"asset": []string{"asset-hash-1"},
   688  		},
   689  
   690  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   691  			"asset": []string{"asset-hash-1"},
   692  		},
   693  
   694  		Model:          model.Model(),
   695  		BrandID:        model.BrandID(),
   696  		Grade:          string(model.Grade()),
   697  		ModelSignKeyID: model.SignKeyID(),
   698  	}
   699  	c.Assert(modeenv.WriteTo(""), IsNil)
   700  
   701  	readSeedCalls := 0
   702  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   703  		readSeedCalls++
   704  		// this is the reseal cleanup path
   705  		c.Logf("call %v label %v", readSeedCalls, label)
   706  		switch readSeedCalls {
   707  		case 1:
   708  			// called for the first system
   709  			c.Assert(label, Equals, "20200825")
   710  			return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
   711  		case 2:
   712  			// called for the 'try' system
   713  			c.Assert(label, Equals, "1234")
   714  			// still good
   715  			return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
   716  		case 3:
   717  			// recovery boot chains for a good recovery system
   718  			fallthrough
   719  		case 4:
   720  			// (cleanup) recovery boot chains for run key, called
   721  			// for the first system only
   722  			fallthrough
   723  		case 5:
   724  			// (cleanup) recovery boot chains for recovery keys
   725  			c.Check(label, Equals, "20200825")
   726  			return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
   727  		default:
   728  			return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls)
   729  		}
   730  	})
   731  	defer restore()
   732  
   733  	resealCalls := 0
   734  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   735  		resealCalls++
   736  		switch resealCalls {
   737  		case 1:
   738  			return fmt.Errorf("reseal fails")
   739  		case 2, 3:
   740  			// reseal of run and recovery keys
   741  			return fmt.Errorf("reseal in cleanup fails too")
   742  		default:
   743  			return fmt.Errorf("unexpected call")
   744  
   745  		}
   746  	})
   747  	defer restore()
   748  
   749  	err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
   750  	c.Assert(err, ErrorMatches, `cannot reseal the encryption key: reseal fails \(cleanup failed: cannot reseal the encryption key: reseal in cleanup fails too\)`)
   751  
   752  	// failed after the call to read the 'try' system seed
   753  	c.Check(readSeedCalls, Equals, 5)
   754  	// called twice, once when enabling the try system, once on cleanup
   755  	c.Check(resealCalls, Equals, 2)
   756  
   757  	modeenvRead, err := boot.ReadModeenv("")
   758  	c.Assert(err, IsNil)
   759  	// modeenv is unchanged
   760  	c.Check(modeenvRead.DeepEqual(modeenv), Equals, true)
   761  	// bootloader variables have been cleared regardless of reseal failing
   762  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   763  	c.Assert(err, IsNil)
   764  	c.Check(vars, DeepEquals, map[string]string{
   765  		"try_recovery_system":    "",
   766  		"recovery_system_status": "",
   767  	})
   768  }
   769  
   770  func (s *systemsSuite) testInspectRecoverySystemOutcomeHappy(c *C, mtbl *bootloadertest.MockTrustedAssetsBootloader, expectedOutcome boot.TryRecoverySystemOutcome, expectedErr string) {
   771  	bootloader.Force(mtbl)
   772  	defer bootloader.Force(nil)
   773  
   774  	// system is encrypted
   775  	s.stampSealedKeys(c, s.rootdir)
   776  
   777  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   778  		return nil, nil, fmt.Errorf("unexpected call")
   779  	})
   780  	defer restore()
   781  
   782  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   783  		return fmt.Errorf("unexpected call")
   784  	})
   785  	defer restore()
   786  
   787  	outcome, label, err := boot.InspectTryRecoverySystemOutcome(s.uc20dev)
   788  	if expectedErr == "" {
   789  		c.Assert(err, IsNil)
   790  	} else {
   791  		c.Assert(err, ErrorMatches, expectedErr)
   792  	}
   793  	c.Check(outcome, Equals, expectedOutcome)
   794  	switch outcome {
   795  	case boot.TryRecoverySystemOutcomeSuccess, boot.TryRecoverySystemOutcomeFailure:
   796  		c.Check(label, Equals, "1234")
   797  	default:
   798  		c.Check(label, Equals, "")
   799  	}
   800  }
   801  
   802  func (s *systemsSuite) TestInspectRecoverySystemOutcomeHappySuccess(c *C) {
   803  	triedVars := map[string]string{
   804  		"recovery_system_status": "tried",
   805  		"try_recovery_system":    "1234",
   806  	}
   807  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   808  	err := mtbl.SetBootVars(triedVars)
   809  	c.Assert(err, IsNil)
   810  
   811  	m := boot.Modeenv{
   812  		Mode: boot.ModeRun,
   813  		// keep this comment to make old gofmt happy
   814  		CurrentRecoverySystems: []string{"29112019", "1234"},
   815  	}
   816  	err = m.WriteTo("")
   817  	c.Assert(err, IsNil)
   818  
   819  	s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeSuccess, "")
   820  
   821  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   822  	c.Assert(err, IsNil)
   823  	c.Check(vars, DeepEquals, triedVars)
   824  }
   825  
   826  func (s *systemsSuite) TestInspectRecoverySystemOutcomeFailureMissingSystemInModeenv(c *C) {
   827  	triedVars := map[string]string{
   828  		"recovery_system_status": "tried",
   829  		"try_recovery_system":    "1234",
   830  	}
   831  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   832  	err := mtbl.SetBootVars(triedVars)
   833  	c.Assert(err, IsNil)
   834  
   835  	m := boot.Modeenv{
   836  		Mode: boot.ModeRun,
   837  		// we don't have the tried recovery system in the modeenv
   838  		CurrentRecoverySystems: []string{"29112019"},
   839  	}
   840  	err = m.WriteTo("")
   841  	c.Assert(err, IsNil)
   842  
   843  	s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeFailure, `recovery system "1234" was tried, but is not present in the modeenv CurrentRecoverySystems`)
   844  
   845  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   846  	c.Assert(err, IsNil)
   847  	c.Check(vars, DeepEquals, triedVars)
   848  }
   849  
   850  func (s *systemsSuite) TestInspectRecoverySystemOutcomeHappyFailure(c *C) {
   851  	tryVars := map[string]string{
   852  		"recovery_system_status": "try",
   853  		"try_recovery_system":    "1234",
   854  	}
   855  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   856  	err := mtbl.SetBootVars(tryVars)
   857  	c.Assert(err, IsNil)
   858  	s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeFailure, "")
   859  
   860  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   861  	c.Assert(err, IsNil)
   862  	c.Check(vars, DeepEquals, tryVars)
   863  }
   864  
   865  func (s *systemsSuite) TestInspectRecoverySystemOutcomeNotTried(c *C) {
   866  	notTriedVars := map[string]string{
   867  		"recovery_system_status": "",
   868  		"try_recovery_system":    "",
   869  	}
   870  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   871  	err := mtbl.SetBootVars(notTriedVars)
   872  	c.Assert(err, IsNil)
   873  	s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeNoneTried, "")
   874  }
   875  
   876  func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentBogusStatus(c *C) {
   877  	badVars := map[string]string{
   878  		"recovery_system_status": "foo",
   879  		"try_recovery_system":    "1234",
   880  	}
   881  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   882  	err := mtbl.SetBootVars(badVars)
   883  	c.Assert(err, IsNil)
   884  	s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `unexpected recovery system status "foo"`)
   885  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   886  	c.Assert(err, IsNil)
   887  	c.Check(vars, DeepEquals, badVars)
   888  }
   889  
   890  func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentBadLabel(c *C) {
   891  	badVars := map[string]string{
   892  		"recovery_system_status": "tried",
   893  		"try_recovery_system":    "",
   894  	}
   895  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   896  	err := mtbl.SetBootVars(badVars)
   897  	c.Assert(err, IsNil)
   898  	s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `try recovery system is unset but status is "tried"`)
   899  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   900  	c.Assert(err, IsNil)
   901  	c.Check(vars, DeepEquals, badVars)
   902  }
   903  
   904  func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentUnexpectedLabel(c *C) {
   905  	badVars := map[string]string{
   906  		"recovery_system_status": "",
   907  		"try_recovery_system":    "1234",
   908  	}
   909  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   910  	err := mtbl.SetBootVars(badVars)
   911  	c.Assert(err, IsNil)
   912  	s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `unexpected recovery system status ""`)
   913  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   914  	c.Assert(err, IsNil)
   915  	c.Check(vars, DeepEquals, badVars)
   916  }
   917  
   918  type clearRecoverySystemTestCase struct {
   919  	systemLabel string
   920  	tryModel    *asserts.Model
   921  	resealErr   error
   922  	expectedErr string
   923  }
   924  
   925  func (s *systemsSuite) testClearRecoverySystem(c *C, mtbl *bootloadertest.MockTrustedAssetsBootloader, tc clearRecoverySystemTestCase) {
   926  	mockAssetsCache(c, s.rootdir, "trusted", []string{
   927  		"asset-asset-hash-1",
   928  	})
   929  
   930  	bootloader.Force(mtbl)
   931  	defer bootloader.Force(nil)
   932  
   933  	// system is encrypted
   934  	s.stampSealedKeys(c, s.rootdir)
   935  
   936  	model := s.uc20dev.Model()
   937  
   938  	modeenv := &boot.Modeenv{
   939  		Mode: "run",
   940  		// keep this comment to make old gofmt happy
   941  		CurrentRecoverySystems: []string{"20200825"},
   942  		GoodRecoverySystems:    []string{"20200825"},
   943  		CurrentKernels:         []string{},
   944  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   945  			"asset": []string{"asset-hash-1"},
   946  		},
   947  
   948  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   949  			"asset": []string{"asset-hash-1"},
   950  		},
   951  
   952  		Model:          model.Model(),
   953  		BrandID:        model.BrandID(),
   954  		Grade:          string(model.Grade()),
   955  		ModelSignKeyID: model.SignKeyID(),
   956  	}
   957  	if tc.systemLabel != "" {
   958  		modeenv.CurrentRecoverySystems = append(modeenv.CurrentRecoverySystems, tc.systemLabel)
   959  	}
   960  	if tc.tryModel != nil {
   961  		modeenv.TryModel = tc.tryModel.Model()
   962  		modeenv.TryBrandID = tc.tryModel.BrandID()
   963  		modeenv.TryGrade = string(tc.tryModel.Grade())
   964  		modeenv.TryModelSignKeyID = tc.tryModel.SignKeyID()
   965  	}
   966  	c.Assert(modeenv.WriteTo(""), IsNil)
   967  
   968  	var readSeedSeenLabels []string
   969  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   970  		// the mock bootloader can only mock a single recovery boot
   971  		// chain, so pretend both seeds use the same kernel, but keep track of the labels
   972  		readSeedSeenLabels = append(readSeedSeenLabels, label)
   973  		return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
   974  	})
   975  	defer restore()
   976  
   977  	resealCalls := 0
   978  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   979  		resealCalls++
   980  		c.Assert(params, NotNil)
   981  		c.Assert(params.ModelParams, HasLen, 1)
   982  		switch resealCalls {
   983  		case 1:
   984  			c.Check(params.KeyFiles, DeepEquals, []string{
   985  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   986  			})
   987  			return tc.resealErr
   988  		case 2:
   989  			c.Check(params.KeyFiles, DeepEquals, []string{
   990  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   991  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   992  			})
   993  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   994  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
   995  			})
   996  			return nil
   997  		default:
   998  			c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
   999  			return fmt.Errorf("unexpected call")
  1000  		}
  1001  	})
  1002  	defer restore()
  1003  
  1004  	err := boot.ClearTryRecoverySystem(s.uc20dev, tc.systemLabel)
  1005  	if tc.expectedErr == "" {
  1006  		c.Assert(err, IsNil)
  1007  	} else {
  1008  		c.Assert(err, ErrorMatches, tc.expectedErr)
  1009  	}
  1010  
  1011  	// only one seed system accessed
  1012  	c.Check(readSeedSeenLabels, DeepEquals, []string{"20200825", "20200825"})
  1013  	if tc.resealErr == nil {
  1014  		// called twice, for run and recovery keys
  1015  		c.Check(resealCalls, Equals, 2)
  1016  	} else {
  1017  		// fails on run key
  1018  		c.Check(resealCalls, Equals, 1)
  1019  	}
  1020  
  1021  	modeenvRead, err := boot.ReadModeenv("")
  1022  	c.Assert(err, IsNil)
  1023  	// modeenv systems list has one entry only
  1024  	c.Check(modeenvRead, testutil.JsonEquals, boot.Modeenv{
  1025  		Mode: "run",
  1026  		// keep this comment to make old gofmt happy
  1027  		CurrentRecoverySystems: []string{"20200825"},
  1028  		GoodRecoverySystems:    []string{"20200825"},
  1029  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1030  			"asset": []string{"asset-hash-1"},
  1031  		},
  1032  
  1033  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1034  			"asset": []string{"asset-hash-1"},
  1035  		},
  1036  
  1037  		Model:          model.Model(),
  1038  		BrandID:        model.BrandID(),
  1039  		Grade:          string(model.Grade()),
  1040  		ModelSignKeyID: model.SignKeyID(),
  1041  
  1042  		// try model if set, has been cleared
  1043  	})
  1044  }
  1045  
  1046  func (s *systemsSuite) TestClearRecoverySystemHappy(c *C) {
  1047  	setVars := map[string]string{
  1048  		"recovery_system_status": "try",
  1049  		"try_recovery_system":    "1234",
  1050  	}
  1051  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
  1052  	err := mtbl.SetBootVars(setVars)
  1053  	c.Assert(err, IsNil)
  1054  
  1055  	s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{systemLabel: "1234"})
  1056  	// bootloader variables have been cleared
  1057  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
  1058  	c.Assert(err, IsNil)
  1059  	c.Check(vars, DeepEquals, map[string]string{
  1060  		"try_recovery_system":    "",
  1061  		"recovery_system_status": "",
  1062  	})
  1063  }
  1064  
  1065  func (s *systemsSuite) TestClearRecoverySystemTriedHappy(c *C) {
  1066  	setVars := map[string]string{
  1067  		"recovery_system_status": "tried",
  1068  		"try_recovery_system":    "1234",
  1069  	}
  1070  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
  1071  	err := mtbl.SetBootVars(setVars)
  1072  	c.Assert(err, IsNil)
  1073  
  1074  	s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{systemLabel: "1234"})
  1075  	// bootloader variables have been cleared
  1076  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
  1077  	c.Assert(err, IsNil)
  1078  	c.Check(vars, DeepEquals, map[string]string{
  1079  		"try_recovery_system":    "",
  1080  		"recovery_system_status": "",
  1081  	})
  1082  }
  1083  
  1084  func (s *systemsSuite) TestClearRecoverySystemInconsistentStateHappy(c *C) {
  1085  	setVars := map[string]string{
  1086  		"recovery_system_status": "foo",
  1087  		"try_recovery_system":    "",
  1088  	}
  1089  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
  1090  	err := mtbl.SetBootVars(setVars)
  1091  	c.Assert(err, IsNil)
  1092  
  1093  	s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{systemLabel: "1234"})
  1094  	// bootloader variables have been cleared
  1095  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
  1096  	c.Assert(err, IsNil)
  1097  	c.Check(vars, DeepEquals, map[string]string{
  1098  		"try_recovery_system":    "",
  1099  		"recovery_system_status": "",
  1100  	})
  1101  }
  1102  
  1103  func (s *systemsSuite) TestClearRecoverySystemInconsistentNoLabelHappy(c *C) {
  1104  	setVars := map[string]string{
  1105  		"recovery_system_status": "this-will-be-gone",
  1106  		"try_recovery_system":    "this-too",
  1107  	}
  1108  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
  1109  	err := mtbl.SetBootVars(setVars)
  1110  	c.Assert(err, IsNil)
  1111  
  1112  	// clear without passing the system label, just clears the relevant boot
  1113  	// variables
  1114  	const noLabel = ""
  1115  	s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{systemLabel: noLabel})
  1116  	// bootloader variables have been cleared
  1117  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
  1118  	c.Assert(err, IsNil)
  1119  	c.Check(vars, DeepEquals, map[string]string{
  1120  		"try_recovery_system":    "",
  1121  		"recovery_system_status": "",
  1122  	})
  1123  }
  1124  
  1125  func (s *systemsSuite) TestClearRecoverySystemRemodelHappy(c *C) {
  1126  	setVars := map[string]string{
  1127  		"recovery_system_status": "try",
  1128  		"try_recovery_system":    "1234",
  1129  	}
  1130  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
  1131  	err := mtbl.SetBootVars(setVars)
  1132  	c.Assert(err, IsNil)
  1133  
  1134  	s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{
  1135  		systemLabel: "1234",
  1136  		tryModel: boottest.MakeMockUC20Model(map[string]interface{}{
  1137  			"tryModelodel": "my-new-model",
  1138  		}),
  1139  	})
  1140  	// bootloader variables have been cleared
  1141  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
  1142  	c.Assert(err, IsNil)
  1143  	c.Check(vars, DeepEquals, map[string]string{
  1144  		"try_recovery_system":    "",
  1145  		"recovery_system_status": "",
  1146  	})
  1147  }
  1148  
  1149  func (s *systemsSuite) TestClearRecoverySystemResealFails(c *C) {
  1150  	setVars := map[string]string{
  1151  		"recovery_system_status": "try",
  1152  		"try_recovery_system":    "1234",
  1153  	}
  1154  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
  1155  	err := mtbl.SetBootVars(setVars)
  1156  	c.Assert(err, IsNil)
  1157  
  1158  	s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{
  1159  		systemLabel: "1234",
  1160  		resealErr:   fmt.Errorf("reseal fails"),
  1161  		expectedErr: "cannot reseal the encryption key: reseal fails",
  1162  	})
  1163  	// bootloader variables have been cleared
  1164  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
  1165  	c.Assert(err, IsNil)
  1166  	// variables were cleared
  1167  	c.Check(vars, DeepEquals, map[string]string{
  1168  		"try_recovery_system":    "",
  1169  		"recovery_system_status": "",
  1170  	})
  1171  }
  1172  
  1173  func (s *systemsSuite) TestClearRecoverySystemSetBootVarsFails(c *C) {
  1174  	setVars := map[string]string{
  1175  		"recovery_system_status": "try",
  1176  		"try_recovery_system":    "1234",
  1177  	}
  1178  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
  1179  	err := mtbl.SetBootVars(setVars)
  1180  	c.Assert(err, IsNil)
  1181  	mtbl.SetErr = fmt.Errorf("set boot vars fails")
  1182  
  1183  	s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{
  1184  		systemLabel: "1234",
  1185  		expectedErr: "set boot vars fails",
  1186  	})
  1187  }
  1188  
  1189  func (s *systemsSuite) TestClearRecoverySystemReboot(c *C) {
  1190  	setVars := map[string]string{
  1191  		"recovery_system_status": "try",
  1192  		"try_recovery_system":    "1234",
  1193  	}
  1194  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
  1195  	err := mtbl.SetBootVars(setVars)
  1196  	c.Assert(err, IsNil)
  1197  
  1198  	mockAssetsCache(c, s.rootdir, "trusted", []string{
  1199  		"asset-asset-hash-1",
  1200  	})
  1201  
  1202  	bootloader.Force(mtbl)
  1203  	defer bootloader.Force(nil)
  1204  
  1205  	// system is encrypted
  1206  	s.stampSealedKeys(c, s.rootdir)
  1207  
  1208  	model := s.uc20dev.Model()
  1209  
  1210  	modeenv := &boot.Modeenv{
  1211  		Mode: "run",
  1212  		// keep this comment to make old gofmt happy
  1213  		CurrentRecoverySystems: []string{"20200825", "1234"},
  1214  		CurrentKernels:         []string{},
  1215  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1216  			"asset": []string{"asset-hash-1"},
  1217  		},
  1218  
  1219  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1220  			"asset": []string{"asset-hash-1"},
  1221  		},
  1222  
  1223  		Model:          model.Model(),
  1224  		BrandID:        model.BrandID(),
  1225  		Grade:          string(model.Grade()),
  1226  		ModelSignKeyID: model.SignKeyID(),
  1227  	}
  1228  	c.Assert(modeenv.WriteTo(""), IsNil)
  1229  
  1230  	var readSeedSeenLabels []string
  1231  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
  1232  		// the mock bootloader can only mock a single recovery boot
  1233  		// chain, so pretend both seeds use the same kernel, but keep track of the labels
  1234  		readSeedSeenLabels = append(readSeedSeenLabels, label)
  1235  		return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
  1236  	})
  1237  	defer restore()
  1238  
  1239  	resealCalls := 0
  1240  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
  1241  		resealCalls++
  1242  		c.Assert(params, NotNil)
  1243  		c.Assert(params.ModelParams, HasLen, 1)
  1244  		switch resealCalls {
  1245  		case 1:
  1246  			c.Check(params.KeyFiles, DeepEquals, []string{
  1247  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
  1248  			})
  1249  			panic("reseal panic")
  1250  		case 2:
  1251  			c.Check(params.KeyFiles, DeepEquals, []string{
  1252  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
  1253  			})
  1254  			return nil
  1255  		case 3:
  1256  			c.Check(params.KeyFiles, DeepEquals, []string{
  1257  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
  1258  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
  1259  			})
  1260  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
  1261  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
  1262  			})
  1263  			return nil
  1264  		default:
  1265  			c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
  1266  			return fmt.Errorf("unexpected call")
  1267  
  1268  		}
  1269  	})
  1270  	defer restore()
  1271  
  1272  	checkGoodState := func() {
  1273  		// modeenv was already written
  1274  		modeenvRead, err := boot.ReadModeenv("")
  1275  		c.Assert(err, IsNil)
  1276  		// modeenv systems list has one entry only
  1277  		c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{
  1278  			"20200825",
  1279  		})
  1280  		// bootloader variables have been cleared already
  1281  		vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
  1282  		c.Assert(err, IsNil)
  1283  		// variables were cleared
  1284  		c.Check(vars, DeepEquals, map[string]string{
  1285  			"try_recovery_system":    "",
  1286  			"recovery_system_status": "",
  1287  		})
  1288  	}
  1289  
  1290  	c.Assert(func() {
  1291  		boot.ClearTryRecoverySystem(s.uc20dev, "1234")
  1292  	}, PanicMatches, "reseal panic")
  1293  	// only one seed system accessed
  1294  	c.Check(readSeedSeenLabels, DeepEquals, []string{"20200825", "20200825"})
  1295  	// panicked on run key
  1296  	c.Check(resealCalls, Equals, 1)
  1297  	checkGoodState()
  1298  
  1299  	mtbl.SetErrFunc = func() error {
  1300  		panic("set boot vars panic")
  1301  	}
  1302  	c.Assert(func() {
  1303  		boot.ClearTryRecoverySystem(s.uc20dev, "1234")
  1304  	}, PanicMatches, "set boot vars panic")
  1305  	// we did not reach resealing yet
  1306  	c.Check(resealCalls, Equals, 1)
  1307  	checkGoodState()
  1308  
  1309  	mtbl.SetErrFunc = nil
  1310  	err = boot.ClearTryRecoverySystem(s.uc20dev, "1234")
  1311  	c.Assert(err, IsNil)
  1312  	checkGoodState()
  1313  }
  1314  
  1315  type recoverySystemGoodTestCase struct {
  1316  	systemLabelAddToCurrent bool
  1317  	systemLabelAddToGood    bool
  1318  	triedSystems            []string
  1319  
  1320  	resealRecoveryKeyErr              error
  1321  	resealRecoveryKeyDuringCleanupErr error
  1322  	resealCalls                       int
  1323  	expectedErr                       string
  1324  
  1325  	readSeedSystems            []string
  1326  	expectedCurrentSystemsList []string
  1327  	expectedGoodSystemsList    []string
  1328  }
  1329  
  1330  func (s *systemsSuite) testPromoteTriedRecoverySystem(c *C, systemLabel string, tc recoverySystemGoodTestCase) {
  1331  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
  1332  	mockAssetsCache(c, s.rootdir, "trusted", []string{
  1333  		"asset-asset-hash-1",
  1334  	})
  1335  
  1336  	bootloader.Force(mtbl)
  1337  	defer bootloader.Force(nil)
  1338  
  1339  	// system is encrypted
  1340  	s.stampSealedKeys(c, s.rootdir)
  1341  
  1342  	model := s.uc20dev.Model()
  1343  
  1344  	modeenv := &boot.Modeenv{
  1345  		Mode: "run",
  1346  		// keep this comment to make old gofmt happy
  1347  		CurrentRecoverySystems: []string{"20200825"},
  1348  		GoodRecoverySystems:    []string{"20200825"},
  1349  		CurrentKernels:         []string{},
  1350  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1351  			"asset": []string{"asset-hash-1"},
  1352  		},
  1353  
  1354  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1355  			"asset": []string{"asset-hash-1"},
  1356  		},
  1357  
  1358  		Model:          model.Model(),
  1359  		BrandID:        model.BrandID(),
  1360  		Grade:          string(model.Grade()),
  1361  		ModelSignKeyID: model.SignKeyID(),
  1362  	}
  1363  	if tc.systemLabelAddToCurrent {
  1364  		modeenv.CurrentRecoverySystems = append(modeenv.CurrentRecoverySystems, systemLabel)
  1365  	}
  1366  	if tc.systemLabelAddToGood {
  1367  		modeenv.GoodRecoverySystems = append(modeenv.GoodRecoverySystems, systemLabel)
  1368  	}
  1369  
  1370  	c.Assert(modeenv.WriteTo(""), IsNil)
  1371  
  1372  	var readSeedSeenLabels []string
  1373  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
  1374  		// the mock bootloader can only mock a single recovery boot
  1375  		// chain, so pretend both seeds use the same kernel, but keep track of the labels
  1376  		readSeedSeenLabels = append(readSeedSeenLabels, label)
  1377  		return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
  1378  	})
  1379  	defer restore()
  1380  
  1381  	resealCalls := 0
  1382  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
  1383  		resealCalls++
  1384  		c.Assert(params, NotNil)
  1385  		c.Assert(params.ModelParams, HasLen, 1)
  1386  		switch resealCalls {
  1387  		case 1:
  1388  			c.Check(params.KeyFiles, DeepEquals, []string{
  1389  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
  1390  			})
  1391  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
  1392  				fmt.Sprintf("snapd_recovery_mode=recover snapd_recovery_system=%s static cmdline", systemLabel),
  1393  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
  1394  			})
  1395  			return nil
  1396  		case 2:
  1397  			c.Check(params.KeyFiles, DeepEquals, []string{
  1398  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
  1399  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
  1400  			})
  1401  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
  1402  				fmt.Sprintf("snapd_recovery_mode=recover snapd_recovery_system=%s static cmdline", systemLabel),
  1403  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
  1404  			})
  1405  			return tc.resealRecoveryKeyErr
  1406  		case 3:
  1407  			// run key boot chain is unchanged, so only recovery key boot chain is resealed
  1408  			if tc.resealRecoveryKeyErr == nil {
  1409  				c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
  1410  				return fmt.Errorf("unexpected call")
  1411  			}
  1412  			c.Check(params.KeyFiles, DeepEquals, []string{
  1413  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
  1414  			})
  1415  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
  1416  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
  1417  			})
  1418  			return nil
  1419  		case 4:
  1420  			if tc.resealRecoveryKeyErr == nil {
  1421  				c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
  1422  				return fmt.Errorf("unexpected call")
  1423  			}
  1424  			c.Check(params.KeyFiles, DeepEquals, []string{
  1425  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
  1426  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
  1427  			})
  1428  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
  1429  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
  1430  			})
  1431  			return tc.resealRecoveryKeyDuringCleanupErr
  1432  		default:
  1433  			c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
  1434  			return fmt.Errorf("unexpected call")
  1435  		}
  1436  	})
  1437  	defer restore()
  1438  
  1439  	err := boot.PromoteTriedRecoverySystem(s.uc20dev, systemLabel, tc.triedSystems)
  1440  	if tc.expectedErr == "" {
  1441  		c.Assert(err, IsNil)
  1442  	} else {
  1443  		c.Assert(err, ErrorMatches, tc.expectedErr)
  1444  	}
  1445  	c.Check(readSeedSeenLabels, DeepEquals, tc.readSeedSystems)
  1446  	c.Check(resealCalls, Equals, tc.resealCalls)
  1447  
  1448  	modeenvRead, err := boot.ReadModeenv("")
  1449  	c.Assert(err, IsNil)
  1450  	c.Check(modeenvRead.GoodRecoverySystems, DeepEquals, tc.expectedGoodSystemsList)
  1451  	c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, tc.expectedCurrentSystemsList)
  1452  }
  1453  
  1454  func (s *systemsSuite) TestPromoteTriedRecoverySystemHappy(c *C) {
  1455  	s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{
  1456  		triedSystems: []string{"1234"},
  1457  
  1458  		resealCalls: 2,
  1459  
  1460  		readSeedSystems: []string{
  1461  			// run key
  1462  			"20200825", "1234",
  1463  			// recovery keys
  1464  			"20200825", "1234",
  1465  		},
  1466  
  1467  		expectedCurrentSystemsList: []string{"20200825", "1234"},
  1468  		expectedGoodSystemsList:    []string{"20200825", "1234"},
  1469  	})
  1470  }
  1471  
  1472  func (s *systemsSuite) TestPromoteTriedRecoverySystemInCurrent(c *C) {
  1473  	s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{
  1474  		triedSystems:            []string{"1234"},
  1475  		systemLabelAddToCurrent: true,
  1476  		resealCalls:             2,
  1477  
  1478  		readSeedSystems: []string{
  1479  			// run key
  1480  			"20200825", "1234",
  1481  			// recovery keys
  1482  			"20200825", "1234",
  1483  		},
  1484  		expectedCurrentSystemsList: []string{"20200825", "1234"},
  1485  		expectedGoodSystemsList:    []string{"20200825", "1234"},
  1486  	})
  1487  }
  1488  
  1489  func (s *systemsSuite) TestPromoteTriedRecoverySystemPresentEverywhere(c *C) {
  1490  	s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{
  1491  		triedSystems:            []string{"1234"},
  1492  		systemLabelAddToCurrent: true,
  1493  		systemLabelAddToGood:    true,
  1494  
  1495  		resealCalls: 2,
  1496  
  1497  		readSeedSystems: []string{
  1498  			// run key
  1499  			"20200825", "1234",
  1500  			// recovery keys
  1501  			"20200825", "1234",
  1502  		},
  1503  		expectedCurrentSystemsList: []string{"20200825", "1234"},
  1504  		expectedGoodSystemsList:    []string{"20200825", "1234"},
  1505  	})
  1506  }
  1507  
  1508  func (s *systemsSuite) TestPromoteTriedRecoverySystemResealFails(c *C) {
  1509  	s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{
  1510  		triedSystems:         []string{"1234"},
  1511  		resealRecoveryKeyErr: fmt.Errorf("recovery key reseal mock failure"),
  1512  		// no failure during cleanup
  1513  		resealRecoveryKeyDuringCleanupErr: nil,
  1514  
  1515  		resealCalls: 4,
  1516  
  1517  		expectedErr: `cannot reseal the fallback encryption keys: recovery key reseal mock failure`,
  1518  
  1519  		readSeedSystems: []string{
  1520  			// run key
  1521  			"20200825", "1234",
  1522  			// recovery keys
  1523  			"20200825", "1234",
  1524  			// cleanup run key reseal (the seed system is still in
  1525  			// current-recovery-systems)
  1526  			"20200825",
  1527  			// cleanup recovery keys
  1528  			"20200825",
  1529  		},
  1530  		expectedCurrentSystemsList: []string{"20200825"},
  1531  		expectedGoodSystemsList:    []string{"20200825"},
  1532  	})
  1533  }
  1534  
  1535  func (s *systemsSuite) TestPromoteTriedRecoverySystemResealUndoFails(c *C) {
  1536  	s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{
  1537  		triedSystems:                      []string{"1234"},
  1538  		resealRecoveryKeyErr:              fmt.Errorf("recovery key reseal mock failure"),
  1539  		resealRecoveryKeyDuringCleanupErr: fmt.Errorf("recovery key reseal mock fail in cleanup"),
  1540  
  1541  		resealCalls: 4,
  1542  
  1543  		expectedErr: `cannot reseal the fallback encryption keys: recovery key reseal mock failure \(cleanup failed: cannot reseal the fallback encryption keys: recovery key reseal mock fail in cleanup\)`,
  1544  
  1545  		readSeedSystems: []string{
  1546  			// run key
  1547  			"20200825", "1234",
  1548  			// recovery keys
  1549  			"20200825", "1234",
  1550  			// cleanup run key reseal (the seed system is still in
  1551  			// current-recovery-systems)
  1552  			// cleanup run key
  1553  			"20200825",
  1554  			// cleanup recovery keys
  1555  			"20200825",
  1556  		},
  1557  		expectedCurrentSystemsList: []string{"20200825"},
  1558  		expectedGoodSystemsList:    []string{"20200825"},
  1559  	})
  1560  }
  1561  
  1562  func (s *systemsSuite) TestPromoteTriedRecoverySystemNotTried(c *C) {
  1563  	s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{
  1564  		triedSystems: []string{"not-here"},
  1565  
  1566  		expectedErr: `system has not been successfully tried`,
  1567  
  1568  		expectedCurrentSystemsList: []string{"20200825"},
  1569  		expectedGoodSystemsList:    []string{"20200825"},
  1570  	})
  1571  
  1572  	// also works if tried systems list is nil
  1573  	s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{
  1574  		triedSystems: nil,
  1575  
  1576  		expectedErr: `system has not been successfully tried`,
  1577  
  1578  		expectedCurrentSystemsList: []string{"20200825"},
  1579  		expectedGoodSystemsList:    []string{"20200825"},
  1580  	})
  1581  }
  1582  
  1583  type recoverySystemDropTestCase struct {
  1584  	systemLabelAddToCurrent bool
  1585  	systemLabelAddToGood    bool
  1586  
  1587  	resealRecoveryKeyErr error
  1588  	resealCalls          int
  1589  	expectedErr          string
  1590  
  1591  	expectedCurrentSystemsList []string
  1592  	expectedGoodSystemsList    []string
  1593  }
  1594  
  1595  func (s *systemsSuite) testDropRecoverySystem(c *C, systemLabel string, tc recoverySystemDropTestCase) {
  1596  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
  1597  	mockAssetsCache(c, s.rootdir, "trusted", []string{
  1598  		"asset-asset-hash-1",
  1599  	})
  1600  
  1601  	bootloader.Force(mtbl)
  1602  	defer bootloader.Force(nil)
  1603  
  1604  	// system is encrypted
  1605  	s.stampSealedKeys(c, s.rootdir)
  1606  
  1607  	model := s.uc20dev.Model()
  1608  
  1609  	modeenv := &boot.Modeenv{
  1610  		Mode: "run",
  1611  		// keep this comment to make old gofmt happy
  1612  		CurrentRecoverySystems: []string{"20200825"},
  1613  		GoodRecoverySystems:    []string{"20200825"},
  1614  		CurrentKernels:         []string{},
  1615  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1616  			"asset": []string{"asset-hash-1"},
  1617  		},
  1618  
  1619  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1620  			"asset": []string{"asset-hash-1"},
  1621  		},
  1622  
  1623  		Model:          model.Model(),
  1624  		BrandID:        model.BrandID(),
  1625  		Grade:          string(model.Grade()),
  1626  		ModelSignKeyID: model.SignKeyID(),
  1627  	}
  1628  	if tc.systemLabelAddToCurrent {
  1629  		modeenv.CurrentRecoverySystems = append(modeenv.CurrentRecoverySystems, systemLabel)
  1630  	}
  1631  	if tc.systemLabelAddToGood {
  1632  		modeenv.GoodRecoverySystems = append(modeenv.GoodRecoverySystems, systemLabel)
  1633  	}
  1634  
  1635  	c.Assert(modeenv.WriteTo(""), IsNil)
  1636  
  1637  	var readSeedSeenLabels []string
  1638  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
  1639  		// the mock bootloader can only mock a single recovery boot
  1640  		// chain, so pretend both seeds use the same kernel, but keep track of the labels
  1641  		readSeedSeenLabels = append(readSeedSeenLabels, label)
  1642  		return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
  1643  	})
  1644  	defer restore()
  1645  
  1646  	resealCalls := 0
  1647  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
  1648  		resealCalls++
  1649  		c.Assert(params, NotNil)
  1650  		c.Assert(params.ModelParams, HasLen, 1)
  1651  		switch resealCalls {
  1652  		case 1:
  1653  			c.Check(params.KeyFiles, DeepEquals, []string{
  1654  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
  1655  			})
  1656  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
  1657  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
  1658  			})
  1659  			return nil
  1660  		case 2:
  1661  			c.Check(params.KeyFiles, DeepEquals, []string{
  1662  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
  1663  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
  1664  			})
  1665  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
  1666  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
  1667  			})
  1668  			return tc.resealRecoveryKeyErr
  1669  		default:
  1670  			c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
  1671  			return fmt.Errorf("unexpected call")
  1672  		}
  1673  	})
  1674  	defer restore()
  1675  
  1676  	err := boot.DropRecoverySystem(s.uc20dev, systemLabel)
  1677  	if tc.expectedErr == "" {
  1678  		c.Assert(err, IsNil)
  1679  	} else {
  1680  		c.Assert(err, ErrorMatches, tc.expectedErr)
  1681  	}
  1682  	c.Check(readSeedSeenLabels, DeepEquals, []string{"20200825", "20200825"})
  1683  	c.Check(resealCalls, Equals, tc.resealCalls)
  1684  
  1685  	modeenvRead, err := boot.ReadModeenv("")
  1686  	c.Assert(err, IsNil)
  1687  	// current is unchanged
  1688  	c.Check(modeenvRead.GoodRecoverySystems, DeepEquals, tc.expectedCurrentSystemsList)
  1689  	c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, tc.expectedGoodSystemsList)
  1690  }
  1691  
  1692  func (s *systemsSuite) TestDropRecoverySystemHappy(c *C) {
  1693  	s.testDropRecoverySystem(c, "1234", recoverySystemDropTestCase{
  1694  		systemLabelAddToCurrent: true,
  1695  		systemLabelAddToGood:    true,
  1696  		resealCalls:             2,
  1697  
  1698  		expectedGoodSystemsList:    []string{"20200825"},
  1699  		expectedCurrentSystemsList: []string{"20200825"},
  1700  	})
  1701  }
  1702  
  1703  func (s *systemsSuite) TestDropRecoverySystemAlreadyGoneFromBoth(c *C) {
  1704  	s.testDropRecoverySystem(c, "1234", recoverySystemDropTestCase{
  1705  		systemLabelAddToCurrent: false,
  1706  		systemLabelAddToGood:    false,
  1707  		resealCalls:             2,
  1708  
  1709  		expectedGoodSystemsList:    []string{"20200825"},
  1710  		expectedCurrentSystemsList: []string{"20200825"},
  1711  	})
  1712  }
  1713  
  1714  func (s *systemsSuite) TestDropRecoverySystemAlreadyGoneOne(c *C) {
  1715  	s.testDropRecoverySystem(c, "1234", recoverySystemDropTestCase{
  1716  		systemLabelAddToCurrent: true,
  1717  		systemLabelAddToGood:    false,
  1718  		resealCalls:             2,
  1719  
  1720  		expectedGoodSystemsList:    []string{"20200825"},
  1721  		expectedCurrentSystemsList: []string{"20200825"},
  1722  	})
  1723  }
  1724  
  1725  func (s *systemsSuite) TestDropRecoverySystemResealErr(c *C) {
  1726  	s.testDropRecoverySystem(c, "1234", recoverySystemDropTestCase{
  1727  		systemLabelAddToCurrent: true,
  1728  		systemLabelAddToGood:    false,
  1729  		resealCalls:             2,
  1730  		resealRecoveryKeyErr:    fmt.Errorf("mocked error"),
  1731  		expectedErr:             `cannot reseal the fallback encryption keys: mocked error`,
  1732  
  1733  		expectedGoodSystemsList:    []string{"20200825"},
  1734  		expectedCurrentSystemsList: []string{"20200825"},
  1735  	})
  1736  }
  1737  
  1738  type initramfsMarkTryRecoverySystemSuite struct {
  1739  	baseSystemsSuite
  1740  
  1741  	bl *bootloadertest.MockBootloader
  1742  }
  1743  
  1744  var _ = Suite(&initramfsMarkTryRecoverySystemSuite{})
  1745  
  1746  func (s *initramfsMarkTryRecoverySystemSuite) SetUpTest(c *C) {
  1747  	s.baseSystemsSuite.SetUpTest(c)
  1748  
  1749  	s.bl = bootloadertest.Mock("bootloader", s.bootdir)
  1750  	bootloader.Force(s.bl)
  1751  	s.AddCleanup(func() { bootloader.Force(nil) })
  1752  }
  1753  
  1754  func (s *initramfsMarkTryRecoverySystemSuite) testMarkRecoverySystemForRun(c *C, outcome boot.TryRecoverySystemOutcome, expectingStatus string) {
  1755  	err := s.bl.SetBootVars(map[string]string{
  1756  		"recovery_system_status": "try",
  1757  		"try_recovery_system":    "1234",
  1758  	})
  1759  	c.Assert(err, IsNil)
  1760  	err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(outcome)
  1761  	c.Assert(err, IsNil)
  1762  
  1763  	expectedVars := map[string]string{
  1764  		"snapd_recovery_mode":   "run",
  1765  		"snapd_recovery_system": "",
  1766  
  1767  		"recovery_system_status": expectingStatus,
  1768  		"try_recovery_system":    "1234",
  1769  	}
  1770  
  1771  	vars, err := s.bl.GetBootVars("snapd_recovery_mode", "snapd_recovery_system",
  1772  		"recovery_system_status", "try_recovery_system")
  1773  	c.Assert(err, IsNil)
  1774  	c.Check(vars, DeepEquals, expectedVars)
  1775  
  1776  	err = s.bl.SetBootVars(map[string]string{
  1777  		// the status is overwritten, even if it's completely bogus
  1778  		"recovery_system_status": "foobar",
  1779  		"try_recovery_system":    "1234",
  1780  	})
  1781  	c.Assert(err, IsNil)
  1782  
  1783  	err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(outcome)
  1784  	c.Assert(err, IsNil)
  1785  
  1786  	vars, err = s.bl.GetBootVars("snapd_recovery_mode", "snapd_recovery_system",
  1787  		"recovery_system_status", "try_recovery_system")
  1788  	c.Assert(err, IsNil)
  1789  	c.Check(vars, DeepEquals, expectedVars)
  1790  }
  1791  
  1792  func (s *initramfsMarkTryRecoverySystemSuite) TestMarkTryRecoverySystemSuccess(c *C) {
  1793  	s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeSuccess, "tried")
  1794  }
  1795  
  1796  func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemFailure(c *C) {
  1797  	s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeFailure, "try")
  1798  }
  1799  
  1800  func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemBogus(c *C) {
  1801  	s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeInconsistent, "")
  1802  }
  1803  
  1804  func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemErr(c *C) {
  1805  	s.bl.SetErr = fmt.Errorf("set fails")
  1806  	err := boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeSuccess)
  1807  	c.Assert(err, ErrorMatches, "set fails")
  1808  	err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeFailure)
  1809  	c.Assert(err, ErrorMatches, "set fails")
  1810  	err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeInconsistent)
  1811  	c.Assert(err, ErrorMatches, "set fails")
  1812  }
  1813  
  1814  func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemUnset(c *C) {
  1815  	err := s.bl.SetBootVars(map[string]string{
  1816  		"recovery_system_status": "try",
  1817  		// system is unset
  1818  		"try_recovery_system": "",
  1819  	})
  1820  	c.Assert(err, IsNil)
  1821  	isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
  1822  	c.Assert(err, ErrorMatches, `try recovery system is unset but status is "try"`)
  1823  	c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true)
  1824  	c.Check(isTry, Equals, false)
  1825  }
  1826  
  1827  func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemBogus(c *C) {
  1828  	err := s.bl.SetBootVars(map[string]string{
  1829  		"recovery_system_status": "foobar",
  1830  		"try_recovery_system":    "1234",
  1831  	})
  1832  	c.Assert(err, IsNil)
  1833  	isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
  1834  	c.Assert(err, ErrorMatches, `unexpected recovery system status "foobar"`)
  1835  	c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true)
  1836  	c.Check(isTry, Equals, false)
  1837  
  1838  	// errors out even if try recovery system label is unset
  1839  	err = s.bl.SetBootVars(map[string]string{
  1840  		"recovery_system_status": "no-label",
  1841  		"try_recovery_system":    "",
  1842  	})
  1843  	c.Assert(err, IsNil)
  1844  	isTry, err = boot.InitramfsIsTryingRecoverySystem("1234")
  1845  	c.Assert(err, ErrorMatches, `unexpected recovery system status "no-label"`)
  1846  	c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true)
  1847  	c.Check(isTry, Equals, false)
  1848  }
  1849  
  1850  func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemNoTryingStatus(c *C) {
  1851  	err := s.bl.SetBootVars(map[string]string{
  1852  		"recovery_system_status": "",
  1853  		"try_recovery_system":    "",
  1854  	})
  1855  	c.Assert(err, IsNil)
  1856  	isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
  1857  	c.Assert(err, IsNil)
  1858  	c.Check(isTry, Equals, false)
  1859  
  1860  	err = s.bl.SetBootVars(map[string]string{
  1861  		// status is checked first
  1862  		"recovery_system_status": "",
  1863  		"try_recovery_system":    "1234",
  1864  	})
  1865  	c.Assert(err, IsNil)
  1866  	isTry, err = boot.InitramfsIsTryingRecoverySystem("1234")
  1867  	c.Assert(err, IsNil)
  1868  	c.Check(isTry, Equals, false)
  1869  }
  1870  
  1871  func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemSameSystem(c *C) {
  1872  	// the usual scenario
  1873  	err := s.bl.SetBootVars(map[string]string{
  1874  		"recovery_system_status": "try",
  1875  		"try_recovery_system":    "1234",
  1876  	})
  1877  	c.Assert(err, IsNil)
  1878  	isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
  1879  	c.Assert(err, IsNil)
  1880  	c.Check(isTry, Equals, true)
  1881  
  1882  	// pretend the system has already been tried
  1883  	err = s.bl.SetBootVars(map[string]string{
  1884  		"recovery_system_status": "tried",
  1885  		"try_recovery_system":    "1234",
  1886  	})
  1887  	c.Assert(err, IsNil)
  1888  	isTry, err = boot.InitramfsIsTryingRecoverySystem("1234")
  1889  	c.Assert(err, IsNil)
  1890  	c.Check(isTry, Equals, true)
  1891  }
  1892  
  1893  func (s *initramfsMarkTryRecoverySystemSuite) TestRecoverySystemSuccessDifferent(c *C) {
  1894  	// other system
  1895  	err := s.bl.SetBootVars(map[string]string{
  1896  		"recovery_system_status": "try",
  1897  		"try_recovery_system":    "9999",
  1898  	})
  1899  	c.Assert(err, IsNil)
  1900  	isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
  1901  	c.Assert(err, IsNil)
  1902  	c.Check(isTry, Equals, false)
  1903  
  1904  	// same when the other system has already been tried
  1905  	err = s.bl.SetBootVars(map[string]string{
  1906  		"recovery_system_status": "tried",
  1907  		"try_recovery_system":    "9999",
  1908  	})
  1909  	c.Assert(err, IsNil)
  1910  	isTry, err = boot.InitramfsIsTryingRecoverySystem("1234")
  1911  	c.Assert(err, IsNil)
  1912  	c.Check(isTry, Equals, false)
  1913  }