github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/bootloader/lkenv/lkenv_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 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 lkenv_test
    21  
    22  import (
    23  	"bytes"
    24  	"compress/gzip"
    25  	"encoding/binary"
    26  	"fmt"
    27  	"hash/crc32"
    28  	"io"
    29  	"io/ioutil"
    30  	"os"
    31  	"path/filepath"
    32  	"testing"
    33  
    34  	"golang.org/x/xerrors"
    35  	. "gopkg.in/check.v1"
    36  
    37  	"github.com/snapcore/snapd/boot"
    38  	"github.com/snapcore/snapd/bootloader/lkenv"
    39  	"github.com/snapcore/snapd/logger"
    40  	"github.com/snapcore/snapd/testutil"
    41  )
    42  
    43  // Hook up check.v1 into the "go test" runner
    44  func Test(t *testing.T) { TestingT(t) }
    45  
    46  type lkenvTestSuite struct {
    47  	envPath    string
    48  	envPathbak string
    49  }
    50  
    51  var _ = Suite(&lkenvTestSuite{})
    52  
    53  var (
    54  	lkversions = []lkenv.Version{
    55  		lkenv.V1,
    56  		lkenv.V2Run,
    57  		lkenv.V2Recovery,
    58  	}
    59  )
    60  
    61  func (l *lkenvTestSuite) SetUpTest(c *C) {
    62  	l.envPath = filepath.Join(c.MkDir(), "snapbootsel.bin")
    63  	l.envPathbak = l.envPath + "bak"
    64  }
    65  
    66  // unpack test data packed with gzip
    67  func unpackTestData(data []byte) (resData []byte, err error) {
    68  	b := bytes.NewBuffer(data)
    69  	var r io.Reader
    70  	r, err = gzip.NewReader(b)
    71  	if err != nil {
    72  		return
    73  	}
    74  	var env bytes.Buffer
    75  	_, err = env.ReadFrom(r)
    76  	if err != nil {
    77  		return
    78  	}
    79  	return env.Bytes(), nil
    80  }
    81  
    82  func (l *lkenvTestSuite) TestCtoGoString(c *C) {
    83  	for _, t := range []struct {
    84  		input    []byte
    85  		expected string
    86  	}{
    87  		{[]byte{0, 0, 0, 0, 0}, ""},
    88  		{[]byte{'a', 0, 0, 0, 0}, "a"},
    89  		{[]byte{'a', 'b', 0, 0, 0}, "ab"},
    90  		{[]byte{'a', 'b', 'c', 0, 0}, "abc"},
    91  		{[]byte{'a', 'b', 'c', 'd', 0}, "abcd"},
    92  		// no trailing \0 - assume corrupted "" ?
    93  		{[]byte{'a', 'b', 'c', 'd', 'e'}, ""},
    94  		// first \0 is the cutof
    95  		{[]byte{'a', 'b', 0, 'z', 0}, "ab"},
    96  	} {
    97  		c.Check(lkenv.CToGoString(t.input), Equals, t.expected)
    98  	}
    99  }
   100  
   101  func (l *lkenvTestSuite) TestCopyStringHappy(c *C) {
   102  	for _, t := range []struct {
   103  		input    string
   104  		expected []byte
   105  	}{
   106  		// input up to the size of the buffer works
   107  		{"", []byte{0, 0, 0, 0, 0}},
   108  		{"a", []byte{'a', 0, 0, 0, 0}},
   109  		{"ab", []byte{'a', 'b', 0, 0, 0}},
   110  		{"abc", []byte{'a', 'b', 'c', 0, 0}},
   111  		{"abcd", []byte{'a', 'b', 'c', 'd', 0}},
   112  		// only what fit is copied
   113  		{"abcde", []byte{'a', 'b', 'c', 'd', 0}},
   114  		{"abcdef", []byte{'a', 'b', 'c', 'd', 0}},
   115  		// strange embedded stuff works
   116  		{"ab\000z", []byte{'a', 'b', 0, 'z', 0}},
   117  	} {
   118  		b := make([]byte, 5)
   119  		lkenv.CopyString(b, t.input)
   120  		c.Check(b, DeepEquals, t.expected)
   121  	}
   122  }
   123  
   124  func (l *lkenvTestSuite) TestCopyStringNoPanic(c *C) {
   125  	// too long, string should get concatenate
   126  	b := make([]byte, 5)
   127  	defer lkenv.CopyString(b, "12345")
   128  	c.Assert(recover(), IsNil)
   129  	defer lkenv.CopyString(b, "123456")
   130  	c.Assert(recover(), IsNil)
   131  }
   132  
   133  func (l *lkenvTestSuite) TestGetBootImageName(c *C) {
   134  	for _, version := range lkversions {
   135  		for _, setValue := range []bool{true, false} {
   136  			env := lkenv.NewEnv(l.envPath, "", version)
   137  			c.Check(env, NotNil)
   138  
   139  			if setValue {
   140  				env.Set("bootimg_file_name", "some-boot-image-name")
   141  			}
   142  
   143  			name := env.GetBootImageName()
   144  
   145  			if setValue {
   146  				c.Assert(name, Equals, "some-boot-image-name")
   147  			} else {
   148  				c.Assert(name, Equals, "boot.img")
   149  			}
   150  		}
   151  	}
   152  }
   153  
   154  func (l *lkenvTestSuite) TestSet(c *C) {
   155  	tt := []struct {
   156  		version lkenv.Version
   157  		key     string
   158  		val     string
   159  	}{
   160  		{
   161  			lkenv.V1,
   162  			"snap_mode",
   163  			boot.TryStatus,
   164  		},
   165  		{
   166  			lkenv.V2Run,
   167  			"kernel_status",
   168  			boot.TryingStatus,
   169  		},
   170  		{
   171  			lkenv.V2Recovery,
   172  			"snapd_recovery_mode",
   173  			"recover",
   174  		},
   175  	}
   176  	for _, t := range tt {
   177  		env := lkenv.NewEnv(l.envPath, "", t.version)
   178  		c.Check(env, NotNil)
   179  		env.Set(t.key, t.val)
   180  		c.Check(env.Get(t.key), Equals, t.val)
   181  	}
   182  }
   183  
   184  func (l *lkenvTestSuite) TestSave(c *C) {
   185  	tt := []struct {
   186  		version       lkenv.Version
   187  		keyValuePairs map[string]string
   188  		comment       string
   189  	}{
   190  		{
   191  			lkenv.V1,
   192  			map[string]string{
   193  				"snap_mode":         boot.TryingStatus,
   194  				"snap_kernel":       "kernel-1",
   195  				"snap_try_kernel":   "kernel-2",
   196  				"snap_core":         "core-1",
   197  				"snap_try_core":     "core-2",
   198  				"snap_gadget":       "gadget-1",
   199  				"snap_try_gadget":   "gadget-2",
   200  				"bootimg_file_name": "boot.img",
   201  			},
   202  			"lkenv v1",
   203  		},
   204  		{
   205  			lkenv.V2Run,
   206  			map[string]string{
   207  				"kernel_status":     boot.TryStatus,
   208  				"snap_kernel":       "kernel-1",
   209  				"snap_try_kernel":   "kernel-2",
   210  				"snap_gadget":       "gadget-1",
   211  				"snap_try_gadget":   "gadget-2",
   212  				"bootimg_file_name": "boot.img",
   213  			},
   214  			"lkenv v2 run",
   215  		},
   216  		{
   217  			lkenv.V2Recovery,
   218  			map[string]string{
   219  				"snapd_recovery_mode":   "recover",
   220  				"snapd_recovery_system": "11192020",
   221  				"bootimg_file_name":     "boot.img",
   222  			},
   223  			"lkenv v2 recovery",
   224  		},
   225  	}
   226  	for _, t := range tt {
   227  		for _, makeBackup := range []bool{true, false} {
   228  			var comment CommentInterface
   229  			if makeBackup {
   230  				comment = Commentf("testcase %s with backup", t.comment)
   231  			} else {
   232  				comment = Commentf("testcase %s without backup", t.comment)
   233  			}
   234  
   235  			loggerBuf, restore := logger.MockLogger()
   236  			defer restore()
   237  
   238  			// make unique files per test case
   239  			testFile := filepath.Join(c.MkDir(), "lk.bin")
   240  			testFileBackup := testFile + "bak"
   241  			if makeBackup {
   242  				// create the backup file too
   243  				buf := make([]byte, 4096)
   244  				err := ioutil.WriteFile(testFileBackup, buf, 0644)
   245  				c.Assert(err, IsNil, comment)
   246  			}
   247  
   248  			buf := make([]byte, 4096)
   249  			err := ioutil.WriteFile(testFile, buf, 0644)
   250  			c.Assert(err, IsNil, comment)
   251  
   252  			env := lkenv.NewEnv(testFile, "", t.version)
   253  			c.Check(env, NotNil, comment)
   254  
   255  			for k, v := range t.keyValuePairs {
   256  				env.Set(k, v)
   257  			}
   258  
   259  			err = env.Save()
   260  			c.Assert(err, IsNil, comment)
   261  
   262  			env2 := lkenv.NewEnv(testFile, "", t.version)
   263  			err = env2.Load()
   264  			c.Assert(err, IsNil, comment)
   265  
   266  			for k, v := range t.keyValuePairs {
   267  				c.Check(env2.Get(k), Equals, v, comment)
   268  			}
   269  
   270  			// check the backup too
   271  			if makeBackup {
   272  				env3 := lkenv.NewEnv(testFileBackup, "", t.version)
   273  				err := env3.Load()
   274  				c.Assert(err, IsNil, comment)
   275  
   276  				for k, v := range t.keyValuePairs {
   277  					c.Check(env3.Get(k), Equals, v, comment)
   278  				}
   279  
   280  				// corrupt the main file and then try to load it - we should
   281  				// automatically fallback to the backup file since the backup
   282  				// file will not be corrupt
   283  				buf := make([]byte, 4096)
   284  				f, err := os.OpenFile(testFile, os.O_WRONLY, 0644)
   285  				c.Assert(err, IsNil)
   286  				_, err = io.Copy(f, bytes.NewBuffer(buf))
   287  				c.Assert(err, IsNil, comment)
   288  
   289  				env4 := lkenv.NewEnv(testFile, "", t.version)
   290  				err = env4.Load()
   291  				c.Assert(err, IsNil, comment)
   292  
   293  				for k, v := range t.keyValuePairs {
   294  					c.Check(env4.Get(k), Equals, v, comment)
   295  				}
   296  
   297  				// we should have also had a logged message about being unable
   298  				// to load the main file
   299  				c.Assert(loggerBuf.String(), testutil.Contains, fmt.Sprintf("cannot load primary bootloader environment: cannot validate %s:", testFile))
   300  			}
   301  		}
   302  	}
   303  }
   304  
   305  func (l *lkenvTestSuite) TestLoadValidatesCRC32(c *C) {
   306  	for _, version := range lkversions {
   307  		testFile := filepath.Join(c.MkDir(), "lk.bin")
   308  
   309  		// make an out of band lkenv object and set the wrong signature to be
   310  		// able to export it to a file
   311  		var rawStruct interface{}
   312  		switch version {
   313  		case lkenv.V1:
   314  			rawStruct = lkenv.SnapBootSelect_v1{
   315  				Version:   version.Number(),
   316  				Signature: version.Signature(),
   317  			}
   318  		case lkenv.V2Run:
   319  			rawStruct = lkenv.SnapBootSelect_v2_run{
   320  				Version:   version.Number(),
   321  				Signature: version.Signature(),
   322  			}
   323  		case lkenv.V2Recovery:
   324  			rawStruct = lkenv.SnapBootSelect_v2_recovery{
   325  				Version:   version.Number(),
   326  				Signature: version.Signature(),
   327  			}
   328  		}
   329  
   330  		buf := bytes.NewBuffer(nil)
   331  		ss := binary.Size(rawStruct)
   332  		buf.Grow(ss)
   333  		err := binary.Write(buf, binary.LittleEndian, rawStruct)
   334  		c.Assert(err, IsNil)
   335  
   336  		// calculate the expected checksum but don't put it into the object when
   337  		// we write it out so that the checksum is invalid
   338  		expCrc32 := crc32.ChecksumIEEE(buf.Bytes()[:ss-4])
   339  
   340  		err = ioutil.WriteFile(testFile, buf.Bytes(), 0644)
   341  		c.Assert(err, IsNil)
   342  
   343  		// now try importing the file with LoadEnv()
   344  		env := lkenv.NewEnv(testFile, "", version)
   345  		c.Assert(env, NotNil)
   346  
   347  		err = env.LoadEnv(testFile)
   348  		c.Assert(err, ErrorMatches, fmt.Sprintf("cannot validate %s: expected checksum 0x%X, got 0x%X", testFile, expCrc32, 0))
   349  	}
   350  
   351  }
   352  
   353  func (l *lkenvTestSuite) TestNewBackupFileLocation(c *C) {
   354  	// creating with the second argument as the empty string falls back to
   355  	// the main path + "bak"
   356  	for _, version := range lkversions {
   357  		logbuf, restore := logger.MockLogger()
   358  		defer restore()
   359  
   360  		testFile := filepath.Join(c.MkDir(), "lk.bin")
   361  		c.Assert(testFile, testutil.FileAbsent)
   362  		c.Assert(testFile+"bak", testutil.FileAbsent)
   363  		// make empty files for Save() to overwrite
   364  		err := ioutil.WriteFile(testFile, nil, 0644)
   365  		c.Assert(err, IsNil)
   366  		err = ioutil.WriteFile(testFile+"bak", nil, 0644)
   367  		c.Assert(err, IsNil)
   368  		env := lkenv.NewEnv(testFile, "", version)
   369  		c.Assert(env, NotNil)
   370  		err = env.Save()
   371  		c.Assert(err, IsNil)
   372  
   373  		// make sure both the primary and backup files were written and can be
   374  		// successfully loaded
   375  		env2 := lkenv.NewEnv(testFile, "", version)
   376  		err = env2.Load()
   377  		c.Assert(err, IsNil)
   378  
   379  		env3 := lkenv.NewEnv(testFile+"bak", "", version)
   380  		err = env3.Load()
   381  		c.Assert(err, IsNil)
   382  
   383  		// no messages logged
   384  		c.Assert(logbuf.String(), Equals, "")
   385  	}
   386  
   387  	// now specify a different backup file location
   388  	for _, version := range lkversions {
   389  		logbuf, restore := logger.MockLogger()
   390  		defer restore()
   391  		testFile := filepath.Join(c.MkDir(), "lk.bin")
   392  		testFileBackup := filepath.Join(c.MkDir(), "lkbackup.bin")
   393  		err := ioutil.WriteFile(testFile, nil, 0644)
   394  		c.Assert(err, IsNil)
   395  		err = ioutil.WriteFile(testFileBackup, nil, 0644)
   396  		c.Assert(err, IsNil)
   397  
   398  		env := lkenv.NewEnv(testFile, testFileBackup, version)
   399  		c.Assert(env, NotNil)
   400  		err = env.Save()
   401  		c.Assert(err, IsNil)
   402  
   403  		// make sure both the primary and backup files were written and can be
   404  		// successfully loaded
   405  		env2 := lkenv.NewEnv(testFile, "", version)
   406  		err = env2.Load()
   407  		c.Assert(err, IsNil)
   408  
   409  		env3 := lkenv.NewEnv(testFileBackup, "", version)
   410  		err = env3.Load()
   411  		c.Assert(err, IsNil)
   412  
   413  		// no "bak" files present
   414  		c.Assert(testFile+"bak", testutil.FileAbsent)
   415  		c.Assert(testFileBackup+"bak", testutil.FileAbsent)
   416  
   417  		// no messages logged
   418  		c.Assert(logbuf.String(), Equals, "")
   419  	}
   420  }
   421  
   422  func (l *lkenvTestSuite) TestLoadValidatesVersionSignatureConsistency(c *C) {
   423  
   424  	tt := []struct {
   425  		version          lkenv.Version
   426  		binVersion       uint32
   427  		binSignature     uint32
   428  		validateFailMode string
   429  	}{
   430  		{
   431  			lkenv.V1,
   432  			lkenv.V2Recovery.Number(),
   433  			lkenv.V1.Signature(),
   434  			"version",
   435  		},
   436  		{
   437  			lkenv.V1,
   438  			lkenv.V1.Number(),
   439  			lkenv.V2Recovery.Signature(),
   440  			"signature",
   441  		},
   442  		{
   443  			lkenv.V2Run,
   444  			lkenv.V1.Number(),
   445  			lkenv.V2Run.Signature(),
   446  			"version",
   447  		},
   448  		{
   449  			lkenv.V2Run,
   450  			lkenv.V2Run.Number(),
   451  			lkenv.V2Recovery.Signature(),
   452  			"signature",
   453  		},
   454  		{
   455  			lkenv.V2Recovery,
   456  			lkenv.V1.Number(),
   457  			lkenv.V2Recovery.Signature(),
   458  			"version",
   459  		},
   460  		{
   461  			lkenv.V2Recovery,
   462  			lkenv.V2Recovery.Number(),
   463  			lkenv.V2Run.Signature(),
   464  			"signature",
   465  		},
   466  	}
   467  
   468  	for _, t := range tt {
   469  		testFile := filepath.Join(c.MkDir(), "lk.bin")
   470  
   471  		// make an out of band lkenv object and set the wrong signature to be
   472  		// able to export it to a file
   473  		var rawStruct interface{}
   474  		switch t.version {
   475  		case lkenv.V1:
   476  			rawStruct = lkenv.SnapBootSelect_v1{
   477  				Version:   t.binVersion,
   478  				Signature: t.binSignature,
   479  			}
   480  		case lkenv.V2Run:
   481  			rawStruct = lkenv.SnapBootSelect_v2_run{
   482  				Version:   t.binVersion,
   483  				Signature: t.binSignature,
   484  			}
   485  		case lkenv.V2Recovery:
   486  			rawStruct = lkenv.SnapBootSelect_v2_recovery{
   487  				Version:   t.binVersion,
   488  				Signature: t.binSignature,
   489  			}
   490  		}
   491  
   492  		buf := bytes.NewBuffer(nil)
   493  		ss := binary.Size(rawStruct)
   494  		buf.Grow(ss)
   495  		err := binary.Write(buf, binary.LittleEndian, rawStruct)
   496  		c.Assert(err, IsNil)
   497  
   498  		// calculate crc32
   499  		newCrc32 := crc32.ChecksumIEEE(buf.Bytes()[:ss-4])
   500  		// note for efficiency's sake to avoid re-writing the whole structure,
   501  		// we re-write _just_ the crc32 to w as little-endian
   502  		buf.Truncate(ss - 4)
   503  		binary.Write(buf, binary.LittleEndian, &newCrc32)
   504  
   505  		err = ioutil.WriteFile(testFile, buf.Bytes(), 0644)
   506  		c.Assert(err, IsNil)
   507  
   508  		// now try importing the file with LoadEnv()
   509  		env := lkenv.NewEnv(testFile, "", t.version)
   510  		c.Assert(env, NotNil)
   511  
   512  		var expNum, gotNum uint32
   513  		switch t.validateFailMode {
   514  		case "signature":
   515  			expNum = t.version.Signature()
   516  			gotNum = t.binSignature
   517  		case "version":
   518  			expNum = t.version.Number()
   519  			gotNum = t.binVersion
   520  		}
   521  		expErr := fmt.Sprintf(
   522  			"cannot validate %s: expected %s 0x%X, got 0x%X",
   523  			testFile,
   524  			t.validateFailMode,
   525  			expNum,
   526  			gotNum,
   527  		)
   528  
   529  		err = env.LoadEnv(testFile)
   530  		c.Assert(err, ErrorMatches, expErr)
   531  	}
   532  }
   533  
   534  func (l *lkenvTestSuite) TestLoadPropagatesErrNotExist(c *C) {
   535  	// make sure that if the env file doesn't exist, the error returned from
   536  	// Load() is os.ErrNotExist, even if it isn't exactly that
   537  	env := lkenv.NewEnv("some-nonsense-file-this-doesnt-exist", "", lkenv.V1)
   538  	c.Check(env, NotNil)
   539  
   540  	err := env.Load()
   541  	c.Assert(xerrors.Is(err, os.ErrNotExist), Equals, true, Commentf("err is %+v", err))
   542  	c.Assert(err, ErrorMatches, "cannot open LK env file: open some-nonsense-file-this-doesnt-existbak: no such file or directory")
   543  }
   544  
   545  func (l *lkenvTestSuite) TestLoad(c *C) {
   546  	for _, version := range lkversions {
   547  		for _, makeBackup := range []bool{true, false} {
   548  			loggerBuf, restore := logger.MockLogger()
   549  			defer restore()
   550  			// make unique files per test case
   551  			testFile := filepath.Join(c.MkDir(), "lk.bin")
   552  			testFileBackup := testFile + "bak"
   553  			if makeBackup {
   554  				buf := make([]byte, 100000)
   555  				err := ioutil.WriteFile(testFileBackup, buf, 0644)
   556  				c.Assert(err, IsNil)
   557  			}
   558  
   559  			buf := make([]byte, 100000)
   560  			err := ioutil.WriteFile(testFile, buf, 0644)
   561  			c.Assert(err, IsNil)
   562  
   563  			// create an env for this file and try to load it
   564  			env := lkenv.NewEnv(testFile, "", version)
   565  			c.Check(env, NotNil)
   566  
   567  			err = env.Load()
   568  			// possible error messages could be "cannot open LK env file: ..."
   569  			// or "cannot valid <file>: ..."
   570  			if makeBackup {
   571  				// here we will read the backup file which exists but like the
   572  				// primary file is corrupted
   573  				c.Assert(err, ErrorMatches, fmt.Sprintf("cannot validate %s: expected version 0x%X, got 0x0", testFileBackup, version.Number()))
   574  			} else {
   575  				// here we fail to read the normal file, and automatically try
   576  				// to read the backup, but fail because it doesn't exist
   577  				c.Assert(err, ErrorMatches, fmt.Sprintf("cannot open LK env file: open %s: no such file or directory", testFileBackup))
   578  			}
   579  
   580  			c.Assert(loggerBuf.String(), testutil.Contains, fmt.Sprintf("cannot load primary bootloader environment: cannot validate %s:", testFile))
   581  			c.Assert(loggerBuf.String(), testutil.Contains, "attempting to load backup bootloader environment")
   582  		}
   583  	}
   584  }
   585  
   586  func (l *lkenvTestSuite) TestGetAndSetAndFindBootPartition(c *C) {
   587  	tt := []struct {
   588  		version lkenv.Version
   589  		// use slices instead of a map since we need a consistent ordering
   590  		bootMatrixKeys   []string
   591  		bootMatrixValues []string
   592  		matrixType       string
   593  		comment          string
   594  	}{
   595  		{
   596  			lkenv.V1,
   597  			[]string{
   598  				"boot_a",
   599  				"boot_b",
   600  			},
   601  			[]string{
   602  				"kernel-1",
   603  				"kernel-2",
   604  			},
   605  			"kernel",
   606  			"v1",
   607  		},
   608  		{
   609  			lkenv.V2Run,
   610  			[]string{
   611  				"boot_a",
   612  				"boot_b",
   613  			},
   614  			[]string{
   615  				"kernel-1",
   616  				"kernel-2",
   617  			},
   618  			"kernel",
   619  			"v2 run",
   620  		},
   621  		{
   622  			lkenv.V2Recovery,
   623  			[]string{
   624  				"boot_recovery_1",
   625  			},
   626  			[]string{
   627  				"20201123",
   628  			},
   629  			"recovery-system",
   630  			"v2 recovery 1 slot",
   631  		},
   632  		{
   633  			lkenv.V2Recovery,
   634  			[]string{
   635  				"boot_recovery_1",
   636  				"boot_recovery_2",
   637  			},
   638  			[]string{
   639  				"20201123",
   640  				"20201124",
   641  			},
   642  			"recovery-system",
   643  			"v2 recovery 2 slots",
   644  		},
   645  		{
   646  			lkenv.V2Recovery,
   647  			[]string{
   648  				"boot_recovery_1",
   649  				"boot_recovery_2",
   650  				"boot_recovery_3",
   651  			},
   652  			[]string{
   653  				"20201123",
   654  				"20201124",
   655  				"20201125",
   656  			},
   657  			"recovery-system",
   658  			"v2 recovery 3 slots",
   659  		},
   660  		{
   661  			lkenv.V2Recovery,
   662  			[]string{
   663  				"boot_recovery_1",
   664  				"boot_recovery_2",
   665  				"boot_recovery_3",
   666  				"boot_recovery_4",
   667  				"boot_recovery_5",
   668  				"boot_recovery_6",
   669  				"boot_recovery_7",
   670  				"boot_recovery_8",
   671  				"boot_recovery_9",
   672  				"boot_recovery_10",
   673  			},
   674  			[]string{
   675  				"20201123",
   676  				"20201124",
   677  				"20201125",
   678  				"20201126",
   679  				"20201127",
   680  				"20201128",
   681  				"20201129",
   682  				"20201130",
   683  				"20201131",
   684  				"20201132",
   685  			},
   686  			"recovery-system",
   687  			"v2 recovery max slots",
   688  		},
   689  	}
   690  
   691  	for _, t := range tt {
   692  		comment := Commentf(t.comment)
   693  		// make sure the key and values are the same length for test case
   694  		// consistency check
   695  		c.Assert(t.bootMatrixKeys, HasLen, len(t.bootMatrixValues), comment)
   696  
   697  		buf := make([]byte, 4096)
   698  		err := ioutil.WriteFile(l.envPath, buf, 0644)
   699  		c.Assert(err, IsNil, comment)
   700  
   701  		env := lkenv.NewEnv(l.envPath, "", t.version)
   702  		c.Assert(env, Not(IsNil), comment)
   703  
   704  		var findFunc func(string) (string, error)
   705  		var setFunc func(string, string) error
   706  		var getFunc func(string) (string, error)
   707  		var deleteFunc func(string) error
   708  		switch t.matrixType {
   709  		case "recovery-system":
   710  			findFunc = func(s string) (string, error) { return env.FindFreeRecoverySystemBootPartition(s) }
   711  			setFunc = func(s1, s2 string) error { return env.SetBootPartitionRecoverySystem(s1, s2) }
   712  			getFunc = func(s1 string) (string, error) { return env.GetRecoverySystemBootPartition(s1) }
   713  			deleteFunc = func(s1 string) error { return env.RemoveRecoverySystemFromBootPartition(s1) }
   714  		case "kernel":
   715  			findFunc = func(s string) (string, error) { return env.FindFreeKernelBootPartition(s) }
   716  			setFunc = func(s1, s2 string) error {
   717  				// for assigning the kernel, we need to also set the
   718  				// snap_kernel, since that is used to detect if we should return
   719  				// an unset variable or not
   720  
   721  				err := env.SetBootPartitionKernel(s1, s2)
   722  				c.Assert(err, IsNil, comment)
   723  				if err != nil {
   724  					return err
   725  				}
   726  				if env.Get("snap_kernel") == "" {
   727  					// only set it the first time so that the delete logic test
   728  					// works and we only set the first kernel to be snap_kernel
   729  					env.Set("snap_kernel", s2)
   730  				}
   731  				return nil
   732  			}
   733  			getFunc = func(s1 string) (string, error) { return env.GetKernelBootPartition(s1) }
   734  			deleteFunc = func(s1 string) error { return env.RemoveKernelFromBootPartition(s1) }
   735  		default:
   736  			c.Errorf("unexpected matrix type, test setup broken (%s)", comment)
   737  		}
   738  
   739  		err = env.InitializeBootPartitions(t.bootMatrixKeys...)
   740  		c.Assert(err, IsNil, comment)
   741  
   742  		// before assigning any values to the boot matrix, check that all
   743  		// values we try to assign would go to the first bootPartLabel
   744  		for _, bootPartValue := range t.bootMatrixKeys {
   745  			// we haven't assigned anything yet, so all values should get mapped
   746  			// to the first boot image partition
   747  			bootPartFound, err := findFunc(bootPartValue)
   748  			c.Assert(err, IsNil, comment)
   749  			c.Assert(bootPartFound, Equals, t.bootMatrixKeys[0], comment)
   750  		}
   751  
   752  		// now go and assign them, checking that along the way we are assigning
   753  		// to the next slot
   754  		// iterate over the key list to keep the same order
   755  		for i, bootPart := range t.bootMatrixKeys {
   756  			bootPartValue := t.bootMatrixValues[i]
   757  			// now we will be assigning things, so we should check that the
   758  			// assigned boot image partition matches what we expect
   759  			bootPartFound, err := findFunc(bootPartValue)
   760  			c.Assert(err, IsNil, comment)
   761  			c.Assert(bootPartFound, Equals, bootPart, comment)
   762  
   763  			err = setFunc(bootPart, bootPartValue)
   764  			c.Assert(err, IsNil, comment)
   765  
   766  			// now check that it has the right value
   767  			val, err := getFunc(bootPartValue)
   768  			c.Assert(err, IsNil, comment)
   769  			c.Assert(val, Equals, bootPart, comment)
   770  
   771  			// double-check that finding a free slot for this value returns the
   772  			// existing slot - this logic specifically is important for uc16 and
   773  			// uc18 where during seeding we will end up extracting a kernel to
   774  			// the already extracted slot (since the kernel will already have
   775  			// been extracted during image build time)
   776  			bootPartFound2, err := findFunc(bootPartValue)
   777  			c.Assert(err, IsNil, comment)
   778  			c.Assert(bootPartFound2, Equals, bootPart, comment)
   779  		}
   780  
   781  		// now check that trying to find a free slot for a new recovery system
   782  		// fails because we are full
   783  		if t.matrixType == "recovery-system" {
   784  			thing, err := findFunc("some-random-value")
   785  			c.Check(thing, Equals, "")
   786  			c.Assert(err, ErrorMatches, "cannot find free boot image partition", comment)
   787  		}
   788  
   789  		// test that removing the last one works
   790  		lastIndex := len(t.bootMatrixValues) - 1
   791  		lastValue := t.bootMatrixValues[lastIndex]
   792  		lastKey := t.bootMatrixKeys[lastIndex]
   793  		err = deleteFunc(lastValue)
   794  		c.Assert(err, IsNil, comment)
   795  
   796  		// trying to delete again will fail since it won't exist
   797  		err = deleteFunc(lastValue)
   798  		c.Assert(err, ErrorMatches, fmt.Sprintf("cannot find %q in boot image partitions", lastValue), comment)
   799  
   800  		// trying to find it will return the last slot
   801  		slot, err := findFunc(lastValue)
   802  		c.Assert(err, IsNil, comment)
   803  		c.Assert(slot, Equals, lastKey, comment)
   804  	}
   805  }
   806  
   807  func (l *lkenvTestSuite) TestV1NoRecoverySystemSupport(c *C) {
   808  	env := lkenv.NewEnv(l.envPath, "", lkenv.V1)
   809  	c.Assert(env, NotNil)
   810  
   811  	_, err := env.FindFreeRecoverySystemBootPartition("blah")
   812  	c.Assert(err, ErrorMatches, "internal error: v1 lkenv has no boot image partition recovery system matrix")
   813  
   814  	err = env.SetBootPartitionRecoverySystem("blah", "blah")
   815  	c.Assert(err, ErrorMatches, "internal error: v1 lkenv has no boot image partition recovery system matrix")
   816  
   817  	_, err = env.GetRecoverySystemBootPartition("blah")
   818  	c.Assert(err, ErrorMatches, "internal error: v1 lkenv has no boot image partition recovery system matrix")
   819  
   820  	err = env.RemoveRecoverySystemFromBootPartition("blah")
   821  	c.Assert(err, ErrorMatches, "internal error: v1 lkenv has no boot image partition recovery system matrix")
   822  }
   823  
   824  func (l *lkenvTestSuite) TestV2RunNoRecoverySystemSupport(c *C) {
   825  	env := lkenv.NewEnv(l.envPath, "", lkenv.V2Run)
   826  	c.Assert(env, NotNil)
   827  
   828  	_, err := env.FindFreeRecoverySystemBootPartition("blah")
   829  	c.Assert(err, ErrorMatches, "internal error: v2 run lkenv has no boot image partition recovery system matrix")
   830  
   831  	err = env.SetBootPartitionRecoverySystem("blah", "blah")
   832  	c.Assert(err, ErrorMatches, "internal error: v2 run lkenv has no boot image partition recovery system matrix")
   833  
   834  	_, err = env.GetRecoverySystemBootPartition("blah")
   835  	c.Assert(err, ErrorMatches, "internal error: v2 run lkenv has no boot image partition recovery system matrix")
   836  
   837  	err = env.RemoveRecoverySystemFromBootPartition("blah")
   838  	c.Assert(err, ErrorMatches, "internal error: v2 run lkenv has no boot image partition recovery system matrix")
   839  }
   840  
   841  func (l *lkenvTestSuite) TestV2RecoveryNoKernelSupport(c *C) {
   842  	env := lkenv.NewEnv(l.envPath, "", lkenv.V2Recovery)
   843  	c.Assert(env, NotNil)
   844  
   845  	_, err := env.FindFreeKernelBootPartition("blah")
   846  	c.Assert(err, ErrorMatches, "internal error: v2 recovery lkenv has no boot image partition kernel matrix")
   847  
   848  	err = env.SetBootPartitionKernel("blah", "blah")
   849  	c.Assert(err, ErrorMatches, "internal error: v2 recovery lkenv has no boot image partition kernel matrix")
   850  
   851  	_, err = env.GetKernelBootPartition("blah")
   852  	c.Assert(err, ErrorMatches, "internal error: v2 recovery lkenv has no boot image partition kernel matrix")
   853  
   854  	err = env.RemoveKernelFromBootPartition("blah")
   855  	c.Assert(err, ErrorMatches, "internal error: v2 recovery lkenv has no boot image partition kernel matrix")
   856  }
   857  
   858  func (l *lkenvTestSuite) TestZippedDataSample(c *C) {
   859  	// TODO: add binary data test for v2 structures generated with gadget build
   860  	// tool when it has been updated for v2
   861  
   862  	// test data is generated with gadget build helper tool:
   863  	// $ parts/snap-boot-sel-env/build/lk-boot-env -w test.bin \
   864  	//   --snap-mode="trying" --snap-kernel="kernel-1" --snap-try-kernel="kernel-2" \
   865  	//   --snap-core="core-1" --snap-try-core="core-2" --reboot-reason="" \
   866  	//   --boot-0-part="boot_a" --boot-1-part="boot_b" --boot-0-snap="kernel-1" \
   867  	//   --boot-1-snap="kernel-3" --bootimg-file="boot.img"
   868  	// $ cat test.bin | gzip | xxd -i
   869  	gzipedData := []byte{
   870  		0x1f, 0x8b, 0x08, 0x00, 0x95, 0x88, 0x77, 0x5d, 0x00, 0x03, 0xed, 0xd7,
   871  		0xc1, 0x09, 0xc2, 0x40, 0x10, 0x05, 0xd0, 0xa4, 0x20, 0x05, 0x63, 0x07,
   872  		0x96, 0xa0, 0x05, 0x88, 0x91, 0x25, 0x04, 0x35, 0x0b, 0x6b, 0x2e, 0x1e,
   873  		0xac, 0xcb, 0xf6, 0xc4, 0x90, 0x1e, 0x06, 0xd9, 0xf7, 0x2a, 0xf8, 0xc3,
   874  		0x1f, 0x18, 0xe6, 0x74, 0x78, 0xa6, 0xb6, 0x69, 0x9b, 0xb9, 0xbc, 0xc6,
   875  		0x69, 0x68, 0xaa, 0x75, 0xcd, 0x25, 0x6d, 0x76, 0xd1, 0x29, 0xe2, 0x2c,
   876  		0xf3, 0x77, 0xd1, 0x29, 0xe2, 0xdc, 0x52, 0x99, 0xd2, 0xbd, 0xde, 0x0d,
   877  		0x58, 0xe7, 0xaf, 0x78, 0x03, 0x80, 0x5a, 0xf5, 0x39, 0xcf, 0xe7, 0x4b,
   878  		0x74, 0x8a, 0x38, 0xb5, 0xdf, 0xbf, 0xa5, 0xff, 0x3e, 0x3a, 0x45, 0x9c,
   879  		0xb5, 0xff, 0x7d, 0x74, 0x8e, 0x28, 0xbf, 0xfe, 0xb7, 0xe3, 0xa3, 0xe2,
   880  		0x0f, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   881  		0xf8, 0x17, 0xc7, 0xf7, 0xa7, 0xfb, 0x02, 0x1c, 0xdf, 0x44, 0x21, 0x0c,
   882  		0x3a, 0x00, 0x00}
   883  
   884  	// uncompress test data to sample env file
   885  	rawData, err := unpackTestData(gzipedData)
   886  	c.Assert(err, IsNil)
   887  	err = ioutil.WriteFile(l.envPath, rawData, 0644)
   888  	c.Assert(err, IsNil)
   889  	err = ioutil.WriteFile(l.envPathbak, rawData, 0644)
   890  	c.Assert(err, IsNil)
   891  
   892  	env := lkenv.NewEnv(l.envPath, "", lkenv.V1)
   893  	c.Check(env, NotNil)
   894  	err = env.Load()
   895  	c.Assert(err, IsNil)
   896  	c.Check(env.Get("snap_mode"), Equals, boot.TryingStatus)
   897  	c.Check(env.Get("snap_kernel"), Equals, "kernel-1")
   898  	c.Check(env.Get("snap_try_kernel"), Equals, "kernel-2")
   899  	c.Check(env.Get("snap_core"), Equals, "core-1")
   900  	c.Check(env.Get("snap_try_core"), Equals, "core-2")
   901  	c.Check(env.Get("bootimg_file_name"), Equals, "boot.img")
   902  	c.Check(env.Get("reboot_reason"), Equals, "")
   903  	// first partition should be with label 'boot_a' and 'kernel-1' revision
   904  	p, err := env.GetKernelBootPartition("kernel-1")
   905  	c.Check(p, Equals, "boot_a")
   906  	c.Assert(err, IsNil)
   907  	// test second boot partition is free with label "boot_b"
   908  	p, err = env.FindFreeKernelBootPartition("kernel-2")
   909  	c.Check(p, Equals, "boot_b")
   910  	c.Assert(err, IsNil)
   911  }