github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/boot/model_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/asserts/assertstest"
    31  	"github.com/snapcore/snapd/boot"
    32  	"github.com/snapcore/snapd/boot/boottest"
    33  	"github.com/snapcore/snapd/bootloader"
    34  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    35  	"github.com/snapcore/snapd/dirs"
    36  	"github.com/snapcore/snapd/secboot"
    37  	"github.com/snapcore/snapd/seed"
    38  	"github.com/snapcore/snapd/snap"
    39  	"github.com/snapcore/snapd/testutil"
    40  	"github.com/snapcore/snapd/timings"
    41  )
    42  
    43  type modelSuite struct {
    44  	baseBootenvSuite
    45  
    46  	oldUc20dev boot.Device
    47  	newUc20dev boot.Device
    48  
    49  	runKernelBf      bootloader.BootFile
    50  	recoveryKernelBf bootloader.BootFile
    51  
    52  	keyID string
    53  
    54  	readSystemEssentialCalls int
    55  }
    56  
    57  var _ = Suite(&modelSuite{})
    58  
    59  var (
    60  	brandPrivKey, _ = assertstest.GenerateKey(752)
    61  )
    62  
    63  func makeEncodableModel(signingAccounts *assertstest.SigningAccounts, overrides map[string]interface{}) *asserts.Model {
    64  	headers := map[string]interface{}{
    65  		"model":        "my-model-uc20",
    66  		"display-name": "My Model",
    67  		"architecture": "amd64",
    68  		"base":         "core20",
    69  		"grade":        "dangerous",
    70  		"snaps": []interface{}{
    71  			map[string]interface{}{
    72  				"name": "pc-kernel",
    73  				"id":   "pckernelidididididididididididid",
    74  				"type": "kernel",
    75  			},
    76  			map[string]interface{}{
    77  				"name": "pc",
    78  				"id":   "pcididididididididididididididid",
    79  				"type": "gadget",
    80  			},
    81  		},
    82  	}
    83  	for k, v := range overrides {
    84  		headers[k] = v
    85  	}
    86  	return signingAccounts.Model("canonical", headers["model"].(string), headers)
    87  }
    88  
    89  func (s *modelSuite) SetUpTest(c *C) {
    90  	s.baseBootenvSuite.SetUpTest(c)
    91  
    92  	store := assertstest.NewStoreStack("canonical", nil)
    93  	brands := assertstest.NewSigningAccounts(store)
    94  	brands.Register("my-brand", brandPrivKey, nil)
    95  	s.keyID = brands.Signing("canonical").KeyID
    96  
    97  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { return nil })
    98  	s.AddCleanup(restore)
    99  	s.oldUc20dev = boottest.MockUC20Device("", makeEncodableModel(brands, nil))
   100  	s.newUc20dev = boottest.MockUC20Device("", makeEncodableModel(brands, map[string]interface{}{
   101  		"model": "my-new-model-uc20",
   102  		"grade": "secured",
   103  	}))
   104  
   105  	model := s.oldUc20dev.Model()
   106  
   107  	modeenv := &boot.Modeenv{
   108  		Mode: "run",
   109  		// system 1234 corresponds to the new model
   110  		CurrentRecoverySystems: []string{"20200825", "1234"},
   111  		GoodRecoverySystems:    []string{"20200825", "1234"},
   112  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   113  			"asset": []string{"asset-hash-1"},
   114  		},
   115  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   116  			"asset": []string{"asset-hash-1"},
   117  		},
   118  		CurrentKernels: []string{"pc-kernel_500.snap"},
   119  		CurrentKernelCommandLines: boot.BootCommandLines{
   120  			"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   121  		},
   122  
   123  		Model:          model.Model(),
   124  		BrandID:        model.BrandID(),
   125  		Grade:          string(model.Grade()),
   126  		ModelSignKeyID: model.SignKeyID(),
   127  	}
   128  	c.Assert(modeenv.WriteTo(""), IsNil)
   129  
   130  	mockAssetsCache(c, s.rootdir, "trusted", []string{
   131  		"asset-asset-hash-1",
   132  	})
   133  
   134  	mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets()
   135  	mtbl.TrustedAssetsList = []string{"asset-1"}
   136  	mtbl.StaticCommandLine = "static cmdline"
   137  	mtbl.BootChainList = []bootloader.BootFile{
   138  		bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
   139  		s.runKernelBf,
   140  	}
   141  	mtbl.RecoveryBootChainList = []bootloader.BootFile{
   142  		bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
   143  		s.recoveryKernelBf,
   144  	}
   145  	bootloader.Force(mtbl)
   146  
   147  	s.AddCleanup(func() { bootloader.Force(nil) })
   148  
   149  	// run kernel
   150  	s.runKernelBf = bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_500.snap",
   151  		"kernel.efi", bootloader.RoleRunMode)
   152  	// seed (recovery) kernel
   153  	s.recoveryKernelBf = bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap",
   154  		"kernel.efi", bootloader.RoleRecovery)
   155  
   156  	c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device"), 0755), IsNil)
   157  
   158  	s.readSystemEssentialCalls = 0
   159  	restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   160  		s.readSystemEssentialCalls++
   161  		kernelRev := 1
   162  		systemModel := s.oldUc20dev.Model()
   163  		if label == "1234" {
   164  			// recovery system for new model
   165  			kernelRev = 999
   166  			systemModel = s.newUc20dev.Model()
   167  		}
   168  		return systemModel, []*seed.Snap{mockKernelSeedSnap(c, snap.R(kernelRev)), mockGadgetSeedSnap(c, nil)}, nil
   169  	})
   170  	s.AddCleanup(restore)
   171  }
   172  
   173  func (s *modelSuite) TestWriteModelToUbuntuBoot(c *C) {
   174  	err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model())
   175  	c.Assert(err, IsNil)
   176  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   177  		"model: my-model-uc20\n")
   178  
   179  	// overwrite the file
   180  	err = boot.WriteModelToUbuntuBoot(s.newUc20dev.Model())
   181  	c.Assert(err, IsNil)
   182  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   183  		"model: my-new-model-uc20\n")
   184  
   185  	err = os.RemoveAll(filepath.Join(boot.InitramfsUbuntuBootDir))
   186  	c.Assert(err, IsNil)
   187  	// fails when trying to write
   188  	err = boot.WriteModelToUbuntuBoot(s.newUc20dev.Model())
   189  	c.Assert(err, ErrorMatches, `open .*/run/mnt/ubuntu-boot/device/model\..*: no such file or directory`)
   190  }
   191  
   192  func (s *modelSuite) TestDeviceChangeHappy(c *C) {
   193  	// system is encrypted
   194  	s.stampSealedKeys(c, s.rootdir)
   195  
   196  	// set up the old model file
   197  	err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model())
   198  	c.Assert(err, IsNil)
   199  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   200  		"model: my-model-uc20\n")
   201  
   202  	resealKeysCalls := 0
   203  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   204  		resealKeysCalls++
   205  		m, err := boot.ReadModeenv("")
   206  		c.Assert(err, IsNil)
   207  		currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   208  		tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   209  		switch resealKeysCalls {
   210  		case 1:
   211  			// run key
   212  			c.Assert(params.KeyFiles, DeepEquals, []string{
   213  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   214  			})
   215  		case 2: // recovery keys
   216  			c.Assert(params.KeyFiles, DeepEquals, []string{
   217  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   218  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   219  			})
   220  		case 3:
   221  			// run key
   222  			c.Assert(params.KeyFiles, DeepEquals, []string{
   223  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   224  			})
   225  		case 4: // recovery keys
   226  			c.Assert(params.KeyFiles, DeepEquals, []string{
   227  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   228  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   229  			})
   230  		default:
   231  			c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls)
   232  		}
   233  
   234  		switch resealKeysCalls {
   235  		case 1, 2:
   236  			// keys are first resealed for both models
   237  			c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
   238  			c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   239  			// boot/device/model is still the old file
   240  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   241  				"model: my-model-uc20\n")
   242  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   243  				"grade: dangerous\n")
   244  		case 3, 4:
   245  			// and finally just for the new model
   246  			c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   247  			c.Assert(tryForSealing, Equals, "/,,")
   248  			// boot/device/model is the new model by this time
   249  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   250  				"model: my-new-model-uc20\n")
   251  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   252  				"grade: secured\n")
   253  		}
   254  		return nil
   255  	})
   256  	defer restore()
   257  
   258  	err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev)
   259  	c.Assert(err, IsNil)
   260  	c.Assert(resealKeysCalls, Equals, 4)
   261  
   262  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   263  		"model: my-new-model-uc20\n")
   264  
   265  	m, err := boot.ReadModeenv("")
   266  	c.Assert(err, IsNil)
   267  	currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   268  	tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   269  	c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   270  	// try model has been cleared
   271  	c.Assert(tryForSealing, Equals, "/,,")
   272  }
   273  
   274  func (s *modelSuite) TestDeviceChangeUnhappyFirstReseal(c *C) {
   275  	// system is encrypted
   276  	s.stampSealedKeys(c, s.rootdir)
   277  
   278  	// set up the old model file
   279  	err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model())
   280  	c.Assert(err, IsNil)
   281  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   282  		"model: my-model-uc20\n")
   283  
   284  	resealKeysCalls := 0
   285  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   286  		resealKeysCalls++
   287  		m, err := boot.ReadModeenv("")
   288  		c.Assert(err, IsNil)
   289  		currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   290  		tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   291  		switch resealKeysCalls {
   292  		case 1:
   293  			// run key
   294  			c.Assert(params.KeyFiles, DeepEquals, []string{
   295  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   296  			})
   297  		default:
   298  			c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls)
   299  		}
   300  
   301  		switch resealKeysCalls {
   302  		case 1:
   303  			// keys are first resealed for both models
   304  			c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
   305  			c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   306  			// boot/device/model is still the old file
   307  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   308  				"model: my-model-uc20\n")
   309  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   310  				"grade: dangerous\n")
   311  		}
   312  		return fmt.Errorf("fail on first try")
   313  	})
   314  	defer restore()
   315  
   316  	err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev)
   317  	c.Assert(err, ErrorMatches, "cannot reseal the encryption key: fail on first try")
   318  	c.Assert(resealKeysCalls, Equals, 1)
   319  	// still the old model file
   320  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   321  		"model: my-model-uc20\n")
   322  
   323  	m, err := boot.ReadModeenv("")
   324  	c.Assert(err, IsNil)
   325  	currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   326  	tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   327  	c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
   328  	// try model has been cleared
   329  	c.Assert(tryForSealing, Equals, "/,,")
   330  }
   331  
   332  func (s *modelSuite) TestDeviceChangeUnhappyFirstSwapModelFile(c *C) {
   333  	// system is encrypted
   334  	s.stampSealedKeys(c, s.rootdir)
   335  
   336  	// set up the old model file
   337  	err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model())
   338  	c.Assert(err, IsNil)
   339  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   340  		"model: my-model-uc20\n")
   341  
   342  	resealKeysCalls := 0
   343  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   344  		resealKeysCalls++
   345  		m, err := boot.ReadModeenv("")
   346  		c.Assert(err, IsNil)
   347  		currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   348  		tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   349  		switch resealKeysCalls {
   350  		case 1:
   351  			// run key
   352  			c.Assert(params.KeyFiles, DeepEquals, []string{
   353  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   354  			})
   355  		case 2:
   356  			// recovery keys
   357  			c.Assert(params.KeyFiles, DeepEquals, []string{
   358  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   359  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   360  			})
   361  		default:
   362  			c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls)
   363  		}
   364  
   365  		switch resealKeysCalls {
   366  		case 1, 2:
   367  			// keys are first resealed for both models
   368  			c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
   369  			c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   370  			// boot/device/model is still the old file
   371  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   372  				"model: my-model-uc20\n")
   373  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   374  				"grade: dangerous\n")
   375  		}
   376  
   377  		if resealKeysCalls == 2 {
   378  			// break writing of the model file
   379  			c.Assert(os.RemoveAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device")), IsNil)
   380  		}
   381  		return nil
   382  	})
   383  	defer restore()
   384  
   385  	err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev)
   386  	c.Assert(err, ErrorMatches, `cannot write new model file: open .*/run/mnt/ubuntu-boot/device/model\..*: no such file or directory`)
   387  	c.Assert(resealKeysCalls, Equals, 2)
   388  
   389  	m, err := boot.ReadModeenv("")
   390  	c.Assert(err, IsNil)
   391  	currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   392  	tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   393  	c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
   394  	// try model has been cleared
   395  	c.Assert(tryForSealing, Equals, "/,,")
   396  }
   397  
   398  func (s *modelSuite) TestDeviceChangeUnhappySecondReseal(c *C) {
   399  	// system is encrypted
   400  	s.stampSealedKeys(c, s.rootdir)
   401  
   402  	// set up the old model file
   403  	err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model())
   404  	c.Assert(err, IsNil)
   405  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   406  		"model: my-model-uc20\n")
   407  
   408  	resealKeysCalls := 0
   409  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   410  		resealKeysCalls++
   411  		m, err := boot.ReadModeenv("")
   412  		c.Assert(err, IsNil)
   413  		currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   414  		tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   415  		// which keys?
   416  		switch resealKeysCalls {
   417  		case 1, 3:
   418  			// run key
   419  			c.Assert(params.KeyFiles, DeepEquals, []string{
   420  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   421  			})
   422  		case 2:
   423  			// recovery keys
   424  			c.Assert(params.KeyFiles, DeepEquals, []string{
   425  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   426  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   427  			})
   428  		default:
   429  			c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls)
   430  		}
   431  		// what's in params?
   432  		switch resealKeysCalls {
   433  		case 1:
   434  			c.Assert(params.ModelParams, HasLen, 2)
   435  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   436  			c.Assert(params.ModelParams[1].Model.Model(), Equals, "my-new-model-uc20")
   437  		case 2:
   438  			// recovery key resealed for current model only
   439  			c.Assert(params.ModelParams, HasLen, 1)
   440  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   441  		case 3, 4:
   442  			// try model has become current
   443  			c.Assert(params.ModelParams, HasLen, 1)
   444  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-new-model-uc20")
   445  		}
   446  		// what's in modeenv?
   447  		switch resealKeysCalls {
   448  		case 1, 2:
   449  			// keys are first resealed for both models
   450  			c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
   451  			c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   452  			// boot/device/model is still the old file
   453  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   454  				"model: my-model-uc20\n")
   455  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   456  				"grade: dangerous\n")
   457  		case 3:
   458  			// and finally just for the new model
   459  			c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   460  			c.Assert(tryForSealing, Equals, "/,,")
   461  			// boot/device/model is the new model by this time
   462  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   463  				"model: my-new-model-uc20\n")
   464  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   465  				"grade: secured\n")
   466  		}
   467  
   468  		if resealKeysCalls == 3 {
   469  			return fmt.Errorf("fail on second try")
   470  		}
   471  
   472  		return nil
   473  	})
   474  	defer restore()
   475  
   476  	err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev)
   477  	c.Assert(err, ErrorMatches, `cannot reseal the encryption key: fail on second try`)
   478  	c.Assert(resealKeysCalls, Equals, 3)
   479  	// old model file was restored
   480  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   481  		"model: my-model-uc20\n")
   482  
   483  	m, err := boot.ReadModeenv("")
   484  	c.Assert(err, IsNil)
   485  	currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   486  	tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   487  	c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
   488  	// try model has been cleared
   489  	c.Assert(tryForSealing, Equals, "/,,")
   490  }
   491  
   492  func (s *modelSuite) TestDeviceChangeRebootBeforeNewModel(c *C) {
   493  	// system is encrypted
   494  	s.stampSealedKeys(c, s.rootdir)
   495  
   496  	// set up the old model file
   497  	err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model())
   498  	c.Assert(err, IsNil)
   499  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   500  		"model: my-model-uc20\n")
   501  
   502  	resealKeysCalls := 0
   503  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   504  		resealKeysCalls++
   505  		c.Logf("reseal key call: %v", resealKeysCalls)
   506  		m, err := boot.ReadModeenv("")
   507  		c.Assert(err, IsNil)
   508  		currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   509  		tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   510  		// timeline & calls:
   511  		// 1 - pre reboot, run & recovery keys, try model set
   512  		// 2 - pre reboot, recovery keys, try model set, unexpected reboot is triggered
   513  		// (reboot)
   514  		// no call for run key, boot chains haven't changes since call 1
   515  		// 3 - recovery key, try model set
   516  		// 4, 5 - post reboot, run & recovery keys, after rewriting model file, try model cleared
   517  
   518  		// which keys?
   519  		switch resealKeysCalls {
   520  		case 1, 4:
   521  			// run key
   522  			c.Assert(params.KeyFiles, DeepEquals, []string{
   523  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   524  			})
   525  		case 2, 3, 5:
   526  			// recovery keys
   527  			c.Assert(params.KeyFiles, DeepEquals, []string{
   528  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   529  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   530  			})
   531  		default:
   532  			c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls)
   533  		}
   534  		// what's in params?
   535  		switch resealKeysCalls {
   536  		case 1:
   537  			c.Assert(params.ModelParams, HasLen, 2)
   538  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   539  			c.Assert(params.ModelParams[1].Model.Model(), Equals, "my-new-model-uc20")
   540  		case 2:
   541  			// attempted reseal of recovery key before clearing try model
   542  			c.Assert(params.ModelParams, HasLen, 1)
   543  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   544  		case 3:
   545  			// recovery keys are resealed only for current system
   546  			c.Assert(params.ModelParams, HasLen, 1)
   547  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   548  		case 4, 5:
   549  			// try model has become current
   550  			c.Assert(params.ModelParams, HasLen, 1)
   551  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-new-model-uc20")
   552  		}
   553  		// what's in modeenv?
   554  		switch resealKeysCalls {
   555  		case 1, 2, 3:
   556  			// keys are first resealed for both models, which are restored to the modeenv
   557  			c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
   558  			c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   559  			// boot/device/model is still the old file
   560  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   561  				"model: my-model-uc20\n")
   562  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   563  				"grade: dangerous\n")
   564  		case 4, 5:
   565  			// and finally just for the new model
   566  			c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   567  			c.Assert(tryForSealing, Equals, "/,,")
   568  			// boot/device/model is the new model by this time
   569  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   570  				"model: my-new-model-uc20\n")
   571  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   572  				"grade: secured\n")
   573  		}
   574  
   575  		if resealKeysCalls == 2 {
   576  			panic(fmt.Sprintf("mock reboot after first complete reseal"))
   577  		}
   578  
   579  		return nil
   580  	})
   581  	defer restore()
   582  
   583  	c.Assert(func() { boot.DeviceChange(s.oldUc20dev, s.newUc20dev) }, PanicMatches,
   584  		`mock reboot after first complete reseal`)
   585  	c.Assert(resealKeysCalls, Equals, 2)
   586  	// still old model in place
   587  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   588  		"model: my-model-uc20\n")
   589  
   590  	m, err := boot.ReadModeenv("")
   591  	c.Assert(err, IsNil)
   592  	currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   593  	tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   594  	c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
   595  	// try model is already set
   596  	c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   597  
   598  	// let's try again
   599  	err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev)
   600  	c.Assert(err, IsNil)
   601  	c.Assert(resealKeysCalls, Equals, 5)
   602  	// got new model now
   603  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   604  		"model: my-new-model-uc20\n")
   605  
   606  	m, err = boot.ReadModeenv("")
   607  	c.Assert(err, IsNil)
   608  	currForSealing = boot.ModelUniqueID(m.ModelForSealing())
   609  	tryForSealing = boot.ModelUniqueID(m.TryModelForSealing())
   610  	// new model is current
   611  	c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   612  	// try model has been cleared
   613  	c.Assert(tryForSealing, Equals, "/,,")
   614  
   615  }
   616  
   617  func (s *modelSuite) TestDeviceChangeRebootAfterNewModelFileWrite(c *C) {
   618  	// system is encrypted
   619  	s.stampSealedKeys(c, s.rootdir)
   620  
   621  	// set up the old model file
   622  	err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model())
   623  	c.Assert(err, IsNil)
   624  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   625  		"model: my-model-uc20\n")
   626  
   627  	resealKeysCalls := 0
   628  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   629  		resealKeysCalls++
   630  		c.Logf("reseal key call: %v", resealKeysCalls)
   631  		m, err := boot.ReadModeenv("")
   632  		c.Assert(err, IsNil)
   633  		currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   634  		tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   635  		// timeline & calls:
   636  		// 1, 2 - pre reboot, run & recovery keys, try model set
   637  		// 3 - run key, after model file has been modified, try model cleared, unexpected
   638  		//     reboot is triggered
   639  		// (reboot)
   640  		// no reseal - boot chains are identical to what was in calls 1 & 2 which were successful
   641  		// 4, 5 - post reboot, run & recovery keys, after rewriting model file, try model cleared
   642  
   643  		// which keys?
   644  		switch resealKeysCalls {
   645  		case 1, 3, 4:
   646  			// run key
   647  			c.Assert(params.KeyFiles, DeepEquals, []string{
   648  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   649  			})
   650  		case 2, 5:
   651  			// recovery keys
   652  			c.Assert(params.KeyFiles, DeepEquals, []string{
   653  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   654  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   655  			})
   656  		default:
   657  			c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls)
   658  		}
   659  		// what's in params?
   660  		switch resealKeysCalls {
   661  		case 1:
   662  			c.Assert(params.ModelParams, HasLen, 2)
   663  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   664  			c.Assert(params.ModelParams[1].Model.Model(), Equals, "my-new-model-uc20")
   665  		case 2:
   666  			// recovery key resealed for current model only
   667  			c.Assert(params.ModelParams, HasLen, 1)
   668  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   669  		case 3:
   670  			// attempted reseal with of run key after clearing try model
   671  			c.Assert(params.ModelParams, HasLen, 1)
   672  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-new-model-uc20")
   673  		case 4, 5:
   674  			// try model has become current
   675  			c.Assert(params.ModelParams, HasLen, 1)
   676  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-new-model-uc20")
   677  		}
   678  		// what's in modeenv?
   679  		switch resealKeysCalls {
   680  		case 1, 2:
   681  			// keys are first resealed for both models
   682  			c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
   683  			c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   684  			// boot/device/model is still the old file
   685  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   686  				"model: my-model-uc20\n")
   687  		case 3, 4, 5:
   688  			// and finally just for the new model
   689  			c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   690  			c.Assert(tryForSealing, Equals, "/,,")
   691  			// boot/device/model is the new model by this time
   692  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   693  				"model: my-new-model-uc20\n")
   694  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   695  				"grade: secured\n")
   696  		}
   697  
   698  		if resealKeysCalls == 3 {
   699  			panic(fmt.Sprintf("mock reboot before second complete reseal"))
   700  		}
   701  
   702  		return nil
   703  	})
   704  	defer restore()
   705  
   706  	c.Assert(func() { boot.DeviceChange(s.oldUc20dev, s.newUc20dev) }, PanicMatches,
   707  		`mock reboot before second complete reseal`)
   708  	c.Assert(resealKeysCalls, Equals, 3)
   709  	// model file has already been replaced
   710  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   711  		"model: my-new-model-uc20\n")
   712  
   713  	m, err := boot.ReadModeenv("")
   714  	c.Assert(err, IsNil)
   715  	currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   716  	tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   717  	// as well as modeenv
   718  	c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   719  	c.Assert(tryForSealing, Equals, "/,,")
   720  
   721  	// let's try again (post reboot)
   722  	err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev)
   723  	c.Assert(err, IsNil)
   724  	c.Assert(resealKeysCalls, Equals, 5)
   725  	// got new model now
   726  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   727  		"model: my-new-model-uc20\n")
   728  
   729  	m, err = boot.ReadModeenv("")
   730  	c.Assert(err, IsNil)
   731  	currForSealing = boot.ModelUniqueID(m.ModelForSealing())
   732  	tryForSealing = boot.ModelUniqueID(m.TryModelForSealing())
   733  	// new model is current
   734  	c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   735  	// try model has been cleared
   736  	c.Assert(tryForSealing, Equals, "/,,")
   737  
   738  }
   739  
   740  func (s *modelSuite) TestDeviceChangeRebootPostSameModel(c *C) {
   741  	// system is encrypted
   742  	s.stampSealedKeys(c, s.rootdir)
   743  
   744  	// set up the old model file
   745  	err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model())
   746  	c.Assert(err, IsNil)
   747  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   748  		"model: my-model-uc20\n")
   749  
   750  	resealKeysCalls := 0
   751  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   752  		resealKeysCalls++
   753  		c.Logf("reseal key call: %v", resealKeysCalls)
   754  		m, err := boot.ReadModeenv("")
   755  		c.Assert(err, IsNil)
   756  		currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   757  		tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   758  		// timeline & calls:
   759  		// 1, 2 - pre reboot, run & recovery keys, try model set
   760  		// 3 - run key, after model file has been modified, try model cleared
   761  		// 4 - recovery key, model file has been modified, try model cleared, unexpected
   762  		//     reboot is triggered
   763  		// (reboot)
   764  		// 5, 6 - run & recovery, try model set, new model also restored
   765  		//        as 'old' model, params are grouped by model
   766  		// 7 - run only (recovery boot chains have not changed since)
   767  
   768  		// which keys?
   769  		switch resealKeysCalls {
   770  		case 1, 3, 5, 7:
   771  			// run key
   772  			c.Assert(params.KeyFiles, DeepEquals, []string{
   773  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   774  			})
   775  		case 2, 4, 6:
   776  			// recovery keys
   777  			c.Assert(params.KeyFiles, DeepEquals, []string{
   778  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   779  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   780  			})
   781  		default:
   782  			c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls)
   783  		}
   784  		// what's in params?
   785  		switch resealKeysCalls {
   786  		case 1:
   787  			c.Assert(params.ModelParams, HasLen, 2)
   788  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   789  			c.Assert(params.ModelParams[1].Model.Model(), Equals, "my-new-model-uc20")
   790  		case 2:
   791  			// recovery key resealed for current model only
   792  			c.Assert(params.ModelParams, HasLen, 1)
   793  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   794  		case 3, 4:
   795  			// try model has become current
   796  			c.Assert(params.ModelParams, HasLen, 1)
   797  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-new-model-uc20")
   798  		case 5, 6, 7:
   799  			//
   800  			c.Assert(params.ModelParams, HasLen, 1)
   801  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-new-model-uc20")
   802  		}
   803  		// what's in modeenv?
   804  		switch resealKeysCalls {
   805  		case 1, 2:
   806  			// keys are first resealed for both models
   807  			c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
   808  			c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   809  			// boot/device/model is still the old file
   810  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   811  				"model: my-model-uc20\n")
   812  		case 3, 4, 7:
   813  			// and finally just for the new model
   814  			c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   815  			c.Assert(tryForSealing, Equals, "/,,")
   816  			// boot/device/model is the new model by this time
   817  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   818  				"model: my-new-model-uc20\n")
   819  		case 5, 6:
   820  			// new model passed as old one
   821  			c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   822  			c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   823  			// boot/device/model is still the old file
   824  			c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   825  				"model: my-new-model-uc20\n")
   826  		}
   827  
   828  		if resealKeysCalls == 4 {
   829  			panic(fmt.Sprintf("mock reboot before second complete reseal"))
   830  		}
   831  		return nil
   832  	})
   833  	defer restore()
   834  
   835  	// as if called by device manager in task handler
   836  	c.Assert(func() { boot.DeviceChange(s.oldUc20dev, s.newUc20dev) }, PanicMatches,
   837  		`mock reboot before second complete reseal`)
   838  	c.Assert(resealKeysCalls, Equals, 4)
   839  	// model file has already been replaced
   840  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   841  		"model: my-new-model-uc20\n")
   842  
   843  	// as if called by device manager, after the model has been changed, but
   844  	// the set-model task isn't marked as done
   845  	err = boot.DeviceChange(s.newUc20dev, s.newUc20dev)
   846  	c.Assert(err, IsNil)
   847  	c.Assert(resealKeysCalls, Equals, 7)
   848  
   849  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   850  		"model: my-new-model-uc20\n")
   851  
   852  	m, err := boot.ReadModeenv("")
   853  	c.Assert(err, IsNil)
   854  	currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   855  	tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   856  	c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   857  	// try model has been cleared
   858  	c.Assert(tryForSealing, Equals, "/,,")
   859  }
   860  
   861  type unhappyMockedWriteModelToBootTestCase struct {
   862  	breakModeenvAfterFirstWrite bool
   863  	modelRestoreFail            bool
   864  	expectedErr                 string
   865  }
   866  
   867  func (s *modelSuite) testDeviceChangeUnhappyMockedWriteModelToBoot(c *C, tc unhappyMockedWriteModelToBootTestCase) {
   868  	// system is encrypted
   869  	s.stampSealedKeys(c, s.rootdir)
   870  
   871  	// set up the old model file
   872  	err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model())
   873  	c.Assert(err, IsNil)
   874  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   875  		"model: my-model-uc20\n")
   876  
   877  	modeenvDir := filepath.Dir(dirs.SnapModeenvFileUnder(dirs.GlobalRootDir))
   878  	defer os.Chmod(modeenvDir, 0755)
   879  
   880  	writeModelToBootCalls := 0
   881  	resealKeysCalls := 0
   882  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   883  		resealKeysCalls++
   884  		m, err := boot.ReadModeenv("")
   885  		c.Assert(err, IsNil)
   886  		currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   887  		tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   888  		switch resealKeysCalls {
   889  		case 1:
   890  			// run key
   891  			c.Assert(params.KeyFiles, DeepEquals, []string{
   892  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   893  			})
   894  		case 2:
   895  			// recovery keys
   896  			c.Assert(params.KeyFiles, DeepEquals, []string{
   897  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   898  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   899  			})
   900  		default:
   901  			c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls)
   902  		}
   903  
   904  		switch resealKeysCalls {
   905  		case 1:
   906  			// keys are first resealed for both models
   907  			c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
   908  			c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
   909  			// no model has been written to ubuntu-boot yet
   910  			c.Assert(writeModelToBootCalls, Equals, 0)
   911  		}
   912  		return nil
   913  	})
   914  	defer restore()
   915  
   916  	restore = boot.MockWriteModelToUbuntuBoot(func(model *asserts.Model) error {
   917  		writeModelToBootCalls++
   918  		c.Assert(model, NotNil)
   919  		switch writeModelToBootCalls {
   920  		case 1:
   921  			// a call to write the new model
   922  			c.Check(model.Model(), Equals, "my-new-model-uc20")
   923  			// only 2 calls to reseal until now
   924  			c.Check(resealKeysCalls, Equals, 2)
   925  			if tc.breakModeenvAfterFirstWrite {
   926  				c.Assert(os.Chmod(modeenvDir, 0000), IsNil)
   927  				return nil
   928  			}
   929  		case 2:
   930  			// a call to restore the old model
   931  			c.Check(model.Model(), Equals, "my-model-uc20")
   932  			if !tc.breakModeenvAfterFirstWrite {
   933  				c.Errorf("unexpected additional call to writeModelToBoot (call # %d)", writeModelToBootCalls)
   934  			}
   935  			if !tc.modelRestoreFail {
   936  				return nil
   937  			}
   938  		default:
   939  			c.Errorf("unexpected additional call to writeModelToBoot (call # %d)", writeModelToBootCalls)
   940  		}
   941  		return fmt.Errorf("mocked fail in write model to boot")
   942  	})
   943  	defer restore()
   944  
   945  	err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev)
   946  	c.Assert(err, ErrorMatches, tc.expectedErr)
   947  	c.Assert(resealKeysCalls, Equals, 2)
   948  	if tc.breakModeenvAfterFirstWrite {
   949  		// write to boot failed on the second call
   950  		c.Assert(writeModelToBootCalls, Equals, 2)
   951  	} else {
   952  		c.Assert(writeModelToBootCalls, Equals, 1)
   953  	}
   954  	// still the old model file, all writes were intercepted
   955  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
   956  		"model: my-model-uc20\n")
   957  
   958  	if !tc.breakModeenvAfterFirstWrite {
   959  		m, err := boot.ReadModeenv("")
   960  		c.Assert(err, IsNil)
   961  		currForSealing := boot.ModelUniqueID(m.ModelForSealing())
   962  		tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
   963  		c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
   964  		// try model has been cleared
   965  		c.Assert(tryForSealing, Equals, "/,,")
   966  	}
   967  }
   968  
   969  func (s *modelSuite) TestDeviceChangeUnhappyMockedWriteModelToBootBeforeModelSwap(c *C) {
   970  	s.testDeviceChangeUnhappyMockedWriteModelToBoot(c, unhappyMockedWriteModelToBootTestCase{
   971  		expectedErr: "cannot write new model file: mocked fail in write model to boot",
   972  	})
   973  }
   974  
   975  func (s *modelSuite) TestDeviceChangeUnhappyMockedWriteModelToBootAfterModelSwapFailingRestore(c *C) {
   976  	// writing modeenv after placing new model file on disk fails, and so
   977  	// does restoring of the old model
   978  	if os.Getuid() == 0 {
   979  		// the test is manipulating file permissions, which doesn't
   980  		// affect root
   981  		c.Skip("test cannot be executed by root")
   982  	}
   983  	s.testDeviceChangeUnhappyMockedWriteModelToBoot(c, unhappyMockedWriteModelToBootTestCase{
   984  		breakModeenvAfterFirstWrite: true,
   985  		modelRestoreFail:            true,
   986  
   987  		expectedErr: `open .*/var/lib/snapd/modeenv\..*: permission denied \(restoring model failed: mocked fail in write model to boot\)`,
   988  	})
   989  }
   990  
   991  func (s *modelSuite) TestDeviceChangeUnhappyMockedWriteModelToBootAfterModelSwapHappyRestore(c *C) {
   992  	// writing modeenv after placing new model file on disk fails, but
   993  	// restore is successful
   994  	if os.Getuid() == 0 {
   995  		// the test is manipulating file permissions, which doesn't
   996  		// affect root
   997  		c.Skip("test cannot be executed by root")
   998  	}
   999  	s.testDeviceChangeUnhappyMockedWriteModelToBoot(c, unhappyMockedWriteModelToBootTestCase{
  1000  		breakModeenvAfterFirstWrite: true,
  1001  		modelRestoreFail:            false,
  1002  
  1003  		expectedErr: `open .*/var/lib/snapd/modeenv\..*: permission denied$`,
  1004  	})
  1005  }
  1006  
  1007  func (s *modelSuite) TestDeviceChangeUnhappyFailReseaWithSwappedModelMockedWriteToBoot(c *C) {
  1008  	// system is encrypted
  1009  	s.stampSealedKeys(c, s.rootdir)
  1010  
  1011  	// set up the old model file
  1012  	err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model())
  1013  	c.Assert(err, IsNil)
  1014  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
  1015  		"model: my-model-uc20\n")
  1016  
  1017  	writeModelToBootCalls := 0
  1018  	resealKeysCalls := 0
  1019  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
  1020  		resealKeysCalls++
  1021  		if resealKeysCalls == 3 {
  1022  			// we are resealing the run key, the old model has been
  1023  			// replaced by the new one in modeenv
  1024  			c.Assert(params.KeyFiles, DeepEquals, []string{
  1025  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
  1026  			})
  1027  			m, err := boot.ReadModeenv("")
  1028  			c.Assert(err, IsNil)
  1029  			currForSealing := boot.ModelUniqueID(m.ModelForSealing())
  1030  			tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
  1031  			// keys are first resealed for both models
  1032  			c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
  1033  			c.Assert(tryForSealing, Equals, "/,,")
  1034  			// an new model has already been written
  1035  			c.Assert(writeModelToBootCalls, Equals, 1)
  1036  			return fmt.Errorf("mock reseal failure")
  1037  		}
  1038  
  1039  		return nil
  1040  	})
  1041  	defer restore()
  1042  
  1043  	restore = boot.MockWriteModelToUbuntuBoot(func(model *asserts.Model) error {
  1044  		writeModelToBootCalls++
  1045  		switch writeModelToBootCalls {
  1046  		case 1:
  1047  			c.Assert(model, NotNil)
  1048  			c.Check(model.Model(), Equals, "my-new-model-uc20")
  1049  			// only 2 calls to reseal until now
  1050  			c.Check(resealKeysCalls, Equals, 2)
  1051  		case 2:
  1052  			// handling of reseal with new model restores the old one on the disk
  1053  			c.Check(model.Model(), Equals, "my-model-uc20")
  1054  			m, err := boot.ReadModeenv("")
  1055  			c.Assert(err, IsNil)
  1056  			// and both models are present in the modeenv
  1057  			currForSealing := boot.ModelUniqueID(m.ModelForSealing())
  1058  			tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
  1059  			// keys are first resealed for both models
  1060  			c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
  1061  			c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID)
  1062  
  1063  		default:
  1064  			c.Errorf("unexpected additional call to writeModelToBoot (call # %d)", writeModelToBootCalls)
  1065  		}
  1066  		return nil
  1067  	})
  1068  	defer restore()
  1069  
  1070  	err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev)
  1071  	c.Assert(err, ErrorMatches, `cannot reseal the encryption key: mock reseal failure`)
  1072  	c.Assert(resealKeysCalls, Equals, 3)
  1073  	c.Assert(writeModelToBootCalls, Equals, 2)
  1074  	// still the old model file, all writes were intercepted
  1075  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains,
  1076  		"model: my-model-uc20\n")
  1077  
  1078  	// finally the try model has been dropped from modeenv
  1079  	m, err := boot.ReadModeenv("")
  1080  	c.Assert(err, IsNil)
  1081  	currForSealing := boot.ModelUniqueID(m.ModelForSealing())
  1082  	tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
  1083  	c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID)
  1084  	// try model has been cleared
  1085  	c.Assert(tryForSealing, Equals, "/,,")
  1086  }
  1087  
  1088  func (s *modelSuite) TestDeviceChangeRebootRestoreModelKeyChangeMockedWriteModel(c *C) {
  1089  	// system is encrypted
  1090  	s.stampSealedKeys(c, s.rootdir)
  1091  
  1092  	oldKeyID := "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"
  1093  	newKeyID := "ZZZ_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"
  1094  	// model can be mocked freely as we will not encode it as we mocked a
  1095  	// function that writes out the model too
  1096  	s.oldUc20dev = boottest.MockUC20Device("", boottest.MakeMockUC20Model(map[string]interface{}{
  1097  		"model":             "my-model-uc20",
  1098  		"brand-id":          "my-brand",
  1099  		"grade":             "dangerous",
  1100  		"sign-key-sha3-384": oldKeyID,
  1101  	}))
  1102  
  1103  	s.newUc20dev = boottest.MockUC20Device("", boottest.MakeMockUC20Model(map[string]interface{}{
  1104  		"model":             "my-model-uc20",
  1105  		"brand-id":          "my-brand",
  1106  		"grade":             "dangerous",
  1107  		"sign-key-sha3-384": newKeyID,
  1108  	}))
  1109  
  1110  	resealKeysCalls := 0
  1111  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
  1112  		resealKeysCalls++
  1113  		c.Logf("reseal key call: %v", resealKeysCalls)
  1114  		m, err := boot.ReadModeenv("")
  1115  		c.Assert(err, IsNil)
  1116  		currForSealing := boot.ModelUniqueID(m.ModelForSealing())
  1117  		tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
  1118  		// timeline & calls:
  1119  		// 1, 2 - pre reboot, run & recovery keys, try model set
  1120  		// 3 - run key, after model file has been modified, try model cleared
  1121  		// 4 - recovery key, model file has been modified, try model cleared,
  1122  		//     unexpected reboot is triggered
  1123  		// (reboot)
  1124  		// 5 - run with old model & key (since we resealed run key in
  1125  		//     call 3, and recovery has not changed), old model restored in modeenv
  1126  		// 6 - run with new model and key, old current has been dropped
  1127  		// 7 - recovery with new model only
  1128  
  1129  		// which keys?
  1130  		switch resealKeysCalls {
  1131  		case 1, 3, 5, 6:
  1132  			// run key
  1133  			c.Assert(params.KeyFiles, DeepEquals, []string{
  1134  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
  1135  			})
  1136  		case 2, 4, 7:
  1137  			// recovery keys
  1138  			c.Assert(params.KeyFiles, DeepEquals, []string{
  1139  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
  1140  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
  1141  			})
  1142  		default:
  1143  			c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls)
  1144  		}
  1145  		// what's in params?
  1146  		switch resealKeysCalls {
  1147  		case 1, 5:
  1148  			c.Assert(params.ModelParams, HasLen, 2)
  1149  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
  1150  			c.Assert(params.ModelParams[0].Model.SignKeyID(), Equals, oldKeyID)
  1151  			c.Assert(params.ModelParams[1].Model.Model(), Equals, "my-model-uc20")
  1152  			c.Assert(params.ModelParams[1].Model.SignKeyID(), Equals, newKeyID)
  1153  		case 2:
  1154  			// recovery key resealed for current model only
  1155  			c.Assert(params.ModelParams, HasLen, 1)
  1156  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
  1157  			c.Assert(params.ModelParams[0].Model.SignKeyID(), Equals, oldKeyID)
  1158  		case 3, 4, 6, 7:
  1159  			// try model has become current
  1160  			c.Assert(params.ModelParams, HasLen, 1)
  1161  			c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
  1162  			c.Assert(params.ModelParams[0].Model.SignKeyID(), Equals, newKeyID)
  1163  		}
  1164  		// what's in modeenv?
  1165  		switch resealKeysCalls {
  1166  		case 1, 2, 5:
  1167  			// keys are first resealed for both models
  1168  			c.Assert(currForSealing, Equals, "my-brand/my-model-uc20,dangerous,"+oldKeyID)
  1169  			c.Assert(tryForSealing, Equals, "my-brand/my-model-uc20,dangerous,"+newKeyID)
  1170  		case 3, 4, 6, 7:
  1171  			// and finally just for the new model
  1172  			c.Assert(currForSealing, Equals, "my-brand/my-model-uc20,dangerous,"+newKeyID)
  1173  			c.Assert(tryForSealing, Equals, "/,,")
  1174  		}
  1175  
  1176  		if resealKeysCalls == 4 {
  1177  			panic(fmt.Sprintf("mock reboot before second complete reseal"))
  1178  		}
  1179  		return nil
  1180  	})
  1181  	defer restore()
  1182  
  1183  	writeModelToBootCalls := 0
  1184  	restore = boot.MockWriteModelToUbuntuBoot(func(model *asserts.Model) error {
  1185  		writeModelToBootCalls++
  1186  		c.Logf("write model to boot call: %v", writeModelToBootCalls)
  1187  		switch writeModelToBootCalls {
  1188  		case 1:
  1189  			c.Assert(model, NotNil)
  1190  			c.Check(model.Model(), Equals, "my-model-uc20")
  1191  			// only 2 calls to reseal until now
  1192  			c.Check(resealKeysCalls, Equals, 2)
  1193  		case 2:
  1194  			// handling of reseal with new model restores the old one on the disk
  1195  			c.Check(model.Model(), Equals, "my-model-uc20")
  1196  			m, err := boot.ReadModeenv("")
  1197  			c.Assert(err, IsNil)
  1198  			// and both models are present in the modeenv
  1199  			currForSealing := boot.ModelUniqueID(m.ModelForSealing())
  1200  			tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
  1201  			// keys are first resealed for both models
  1202  			c.Assert(currForSealing, Equals, "my-brand/my-model-uc20,dangerous,"+oldKeyID)
  1203  			c.Assert(tryForSealing, Equals, "my-brand/my-model-uc20,dangerous,"+newKeyID)
  1204  
  1205  		default:
  1206  			c.Errorf("unexpected additional call to writeModelToBoot (call # %d)", writeModelToBootCalls)
  1207  		}
  1208  		return nil
  1209  	})
  1210  	defer restore()
  1211  
  1212  	// as if called by device manager in task handler
  1213  	c.Assert(func() { boot.DeviceChange(s.oldUc20dev, s.newUc20dev) }, PanicMatches,
  1214  		`mock reboot before second complete reseal`)
  1215  	c.Assert(resealKeysCalls, Equals, 4)
  1216  	c.Assert(writeModelToBootCalls, Equals, 1)
  1217  
  1218  	err := boot.DeviceChange(s.oldUc20dev, s.newUc20dev)
  1219  	c.Assert(err, IsNil)
  1220  	c.Assert(resealKeysCalls, Equals, 7)
  1221  	c.Assert(writeModelToBootCalls, Equals, 2)
  1222  
  1223  	m, err := boot.ReadModeenv("")
  1224  	c.Assert(err, IsNil)
  1225  	currForSealing := boot.ModelUniqueID(m.ModelForSealing())
  1226  	tryForSealing := boot.ModelUniqueID(m.TryModelForSealing())
  1227  	c.Assert(currForSealing, Equals, "my-brand/my-model-uc20,dangerous,"+newKeyID)
  1228  	// try model has been cleared
  1229  	c.Assert(tryForSealing, Equals, "/,,")
  1230  }