github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/bootloader/lkenv/lkenv.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
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/binary"
    25  	"fmt"
    26  	"hash/crc32"
    27  	"os"
    28  
    29  	"github.com/snapcore/snapd/logger"
    30  	"github.com/snapcore/snapd/osutil"
    31  )
    32  
    33  const SNAP_BOOTSELECT_VERSION = 0x00010001
    34  
    35  // const SNAP_BOOTSELECT_SIGNATURE ('S' | ('B' << 8) | ('s' << 16) | ('e' << 24))
    36  const SNAP_BOOTSELECT_SIGNATURE = 0x53 | 0x42<<8 | 0x73<<16 | 0x65<<24
    37  const SNAP_NAME_MAX_LEN = 256
    38  
    39  /* number of available boot partitions */
    40  const SNAP_BOOTIMG_PART_NUM = 2
    41  
    42  /* Default boot image file name to be used from kernel snap */
    43  const BOOTIMG_DEFAULT_NAME = "boot.img"
    44  
    45  // for accessing the 	Bootimg_matrix
    46  const (
    47  	MATRIX_ROW_PARTITION = 0
    48  	MATRIX_ROW_KERNEL    = 1
    49  )
    50  
    51  /**
    52   * Following structure has to be kept in sync with c structure defined by
    53   * include/lk/snappy-boot_v1.h
    54   * c headerfile is used by bootloader, this ensures sync of the environment
    55   * between snapd and bootloader
    56  
    57   * when this structure needs to be updated,
    58   * new version should be introduced instead together with c header file,
    59   * which is to be adopted by bootloader
    60   *
    61   * !!! Support for old version has to be maintained, as it is not guaranteed
    62   * all existing bootloader would adopt new version!
    63   */
    64  type SnapBootSelect_v1 struct {
    65  	/* Contains value BOOTSELECT_SIGNATURE defined above */
    66  	Signature uint32
    67  	/* snappy boot select version */
    68  	Version uint32
    69  
    70  	/* snap_mode, one of: 'empty', "try", "trying" */
    71  	Snap_mode [SNAP_NAME_MAX_LEN]byte
    72  	/* current core snap revision */
    73  	Snap_core [SNAP_NAME_MAX_LEN]byte
    74  	/* try core snap revision */
    75  	Snap_try_core [SNAP_NAME_MAX_LEN]byte
    76  	/* current kernel snap revision */
    77  	Snap_kernel [SNAP_NAME_MAX_LEN]byte
    78  	/* current kernel snap revision */
    79  	Snap_try_kernel [SNAP_NAME_MAX_LEN]byte
    80  
    81  	/* gadget_mode, one of: 'empty', "try", "trying" */
    82  	Gadget_mode [SNAP_NAME_MAX_LEN]byte
    83  	/* GADGET assets: current gadget assets revision */
    84  	Snap_gadget [SNAP_NAME_MAX_LEN]byte
    85  	/* GADGET assets: try gadget assets revision */
    86  	Snap_try_gadget [SNAP_NAME_MAX_LEN]byte
    87  
    88  	/**
    89  	 * Reboot reason
    90  	 * optional parameter to signal bootloader alternative reboot reasons
    91  	 * e.g. recovery/factory-reset/boot asset update
    92  	 */
    93  	Reboot_reason [SNAP_NAME_MAX_LEN]byte
    94  
    95  	/**
    96  	 * Matrix for mapping of boot img partition to installed kernel snap revision
    97  	 *
    98  	 * First column represents boot image partition label (e.g. boot_a,boot_b )
    99  	 *   value are static and should be populated at gadget built time
   100  	 *   or latest at image build time. Values are not further altered at run time.
   101  	 * Second column represents name currently installed kernel snap
   102  	 *   e.g. pi2-kernel_123.snap
   103  	 * initial value representing initial kernel snap revision
   104  	 *   is populated at image build time by snapd
   105  	 *
   106  	 * There are two rows in the matrix, representing current and previous kernel revision
   107  	 * following describes how this matrix should be modified at different stages:
   108  	 *  - at image build time:
   109  	 *    - extracted kernel snap revision name should be filled
   110  	 *      into free slot (first row, second column)
   111  	 *  - snapd:
   112  	 *    - when new kernel snap revision is being installed, snapd cycles through
   113  	 *      matrix to find unused 'boot slot' to be used for new kernel snap revision
   114  	 *      from free slot, first column represents partition label to which kernel
   115  	 *      snap boot image should be extracted. Second column is then populated with
   116  	 *      kernel snap revision name.
   117  	 *    - snap_mode, snap_try_kernel, snap_try_core behaves same way as with u-boot
   118  	 *  - bootloader:
   119  	 *    - bootloader reads snap_mode to determine if snap_kernel or snap_try_kernel is used
   120  	 *      to get kernel snap revision name
   121  	 *      kernel snap revision is then used to search matrix to determine
   122  	 *      partition label to be used for current boot
   123  	 *    - bootloader NEVER alters this matrix values
   124  	 *
   125  	 * [ <bootimg 1 part label> ] [ <kernel snap revision installed in this boot partition> ]
   126  	 * [ <bootimg 2 part label> ] [ <kernel snap revision installed in this boot partition> ]
   127  	 */
   128  	Bootimg_matrix [SNAP_BOOTIMG_PART_NUM][2][SNAP_NAME_MAX_LEN]byte
   129  
   130  	/**
   131  	 * name of the boot image from kernel snap to be used for extraction
   132  	 * when not defined or empty, default boot.img will be used
   133  	 */
   134  	Bootimg_file_name [SNAP_NAME_MAX_LEN]byte
   135  
   136  	/**
   137  	 * gadget assets: Matrix for mapping of gadget asset partitions
   138  	 * Optional boot asset tracking, based on bootloader support
   139  	 * Some boot chains support A/B boot assets for increased robustness
   140  	 * example being A/B TrustExecutionEnvironment
   141  	 * This matrix can be used to track current and try boot assets for
   142  	 * robust updates
   143  	 * Use of Gadget_asset_matrix matches use of Bootimg_matrix
   144  	 *
   145  	 * [ <boot assets 1 part label> ] [ <currently installed assets revision in this partition> ]
   146  	 * [ <boot assets 2 part label> ] [ <currently installed assets revision in this partition> ]
   147  	 */
   148  	Gadget_asset_matrix [SNAP_BOOTIMG_PART_NUM][2][SNAP_NAME_MAX_LEN]byte
   149  
   150  	/* unused placeholders for additional parameters in the future */
   151  	Unused_key_01 [SNAP_NAME_MAX_LEN]byte
   152  	Unused_key_02 [SNAP_NAME_MAX_LEN]byte
   153  	Unused_key_03 [SNAP_NAME_MAX_LEN]byte
   154  	Unused_key_04 [SNAP_NAME_MAX_LEN]byte
   155  	Unused_key_05 [SNAP_NAME_MAX_LEN]byte
   156  	Unused_key_06 [SNAP_NAME_MAX_LEN]byte
   157  	Unused_key_07 [SNAP_NAME_MAX_LEN]byte
   158  	Unused_key_08 [SNAP_NAME_MAX_LEN]byte
   159  	Unused_key_09 [SNAP_NAME_MAX_LEN]byte
   160  	Unused_key_10 [SNAP_NAME_MAX_LEN]byte
   161  	Unused_key_11 [SNAP_NAME_MAX_LEN]byte
   162  	Unused_key_12 [SNAP_NAME_MAX_LEN]byte
   163  	Unused_key_13 [SNAP_NAME_MAX_LEN]byte
   164  	Unused_key_14 [SNAP_NAME_MAX_LEN]byte
   165  	Unused_key_15 [SNAP_NAME_MAX_LEN]byte
   166  	Unused_key_16 [SNAP_NAME_MAX_LEN]byte
   167  	Unused_key_17 [SNAP_NAME_MAX_LEN]byte
   168  	Unused_key_18 [SNAP_NAME_MAX_LEN]byte
   169  	Unused_key_19 [SNAP_NAME_MAX_LEN]byte
   170  	Unused_key_20 [SNAP_NAME_MAX_LEN]byte
   171  
   172  	/* unused array of 10 key value pairs */
   173  	Key_value_pairs [10][2][SNAP_NAME_MAX_LEN]byte
   174  
   175  	/* crc32 value for structure */
   176  	Crc32 uint32
   177  }
   178  
   179  // Env contains the data of the uboot environment
   180  // path can be file or partition device node
   181  type Env struct {
   182  	path    string
   183  	pathbak string
   184  	env     SnapBootSelect_v1
   185  }
   186  
   187  // cToGoString convert string in passed byte array into string type
   188  // if string in byte array is not terminated, empty string is returned
   189  func cToGoString(c []byte) string {
   190  	if end := bytes.IndexByte(c, 0); end >= 0 {
   191  		return string(c[:end])
   192  	}
   193  	// no trailing \0 - return ""
   194  	return ""
   195  }
   196  
   197  // copyString copy passed string into byte array
   198  // make sure string is terminated
   199  // if string does not fit into byte array, it will be concatenated
   200  func copyString(b []byte, s string) {
   201  	sl := len(s)
   202  	bs := len(b)
   203  	if bs > sl {
   204  		copy(b[:], s)
   205  		b[sl] = 0
   206  	} else {
   207  		copy(b[:bs-1], s)
   208  		b[bs-1] = 0
   209  	}
   210  }
   211  
   212  func NewEnv(path string) *Env {
   213  	return &Env{
   214  		path:    path,
   215  		pathbak: path + "bak",
   216  		env: SnapBootSelect_v1{
   217  			Signature: SNAP_BOOTSELECT_SIGNATURE,
   218  			Version:   SNAP_BOOTSELECT_VERSION,
   219  		},
   220  	}
   221  }
   222  
   223  func (l *Env) Get(key string) string {
   224  	switch key {
   225  	case "snap_mode":
   226  		return cToGoString(l.env.Snap_mode[:])
   227  	case "snap_kernel":
   228  		return cToGoString(l.env.Snap_kernel[:])
   229  	case "snap_try_kernel":
   230  		return cToGoString(l.env.Snap_try_kernel[:])
   231  	case "snap_core":
   232  		return cToGoString(l.env.Snap_core[:])
   233  	case "snap_try_core":
   234  		return cToGoString(l.env.Snap_try_core[:])
   235  	case "snap_gadget":
   236  		return cToGoString(l.env.Snap_gadget[:])
   237  	case "snap_try_gadget":
   238  		return cToGoString(l.env.Snap_try_gadget[:])
   239  	case "reboot_reason":
   240  		return cToGoString(l.env.Reboot_reason[:])
   241  	case "bootimg_file_name":
   242  		return cToGoString(l.env.Bootimg_file_name[:])
   243  	}
   244  	return ""
   245  }
   246  
   247  func (l *Env) Set(key, value string) {
   248  	switch key {
   249  	case "snap_mode":
   250  		copyString(l.env.Snap_mode[:], value)
   251  	case "snap_kernel":
   252  		copyString(l.env.Snap_kernel[:], value)
   253  	case "snap_try_kernel":
   254  		copyString(l.env.Snap_try_kernel[:], value)
   255  	case "snap_core":
   256  		copyString(l.env.Snap_core[:], value)
   257  	case "snap_try_core":
   258  		copyString(l.env.Snap_try_core[:], value)
   259  	case "snap_gadget":
   260  		copyString(l.env.Snap_gadget[:], value)
   261  	case "snap_try_gadget":
   262  		copyString(l.env.Snap_try_gadget[:], value)
   263  	case "reboot_reason":
   264  		copyString(l.env.Reboot_reason[:], value)
   265  	case "bootimg_file_name":
   266  		copyString(l.env.Bootimg_file_name[:], value)
   267  	}
   268  }
   269  
   270  // ConfigureBootPartitions set boot partitions label names
   271  // this function should not be used at run time!
   272  // it should be used only at image build time,
   273  // if partition labels are not pre-filled by gadget built
   274  func (l *Env) ConfigureBootPartitions(boot_1, boot_2 string) {
   275  	copyString(l.env.Bootimg_matrix[0][MATRIX_ROW_PARTITION][:], boot_1)
   276  	copyString(l.env.Bootimg_matrix[1][MATRIX_ROW_PARTITION][:], boot_2)
   277  }
   278  
   279  // ConfigureBootimgName set boot image file name
   280  // boot image file name is used at kernel extraction time
   281  // this function should not be used at run time!
   282  // it should be used only at image build time
   283  // if default boot.img is not set by gadget built
   284  func (l *Env) ConfigureBootimgName(bootimgName string) {
   285  	copyString(l.env.Bootimg_file_name[:], bootimgName)
   286  }
   287  
   288  func (l *Env) Load() error {
   289  	err := l.LoadEnv(l.path)
   290  	if err != nil {
   291  		return l.LoadEnv(l.pathbak)
   292  	}
   293  	return nil
   294  }
   295  
   296  func (l *Env) LoadEnv(path string) error {
   297  	f, err := os.Open(path)
   298  	if err != nil {
   299  		return fmt.Errorf("cannot open LK env file: %v", err)
   300  	}
   301  
   302  	defer f.Close()
   303  	if err := binary.Read(f, binary.LittleEndian, &l.env); err != nil {
   304  		return fmt.Errorf("cannot read LK env from file: %v", err)
   305  	}
   306  
   307  	// calculate crc32 to validate structure
   308  	w := bytes.NewBuffer(nil)
   309  	ss := binary.Size(l.env)
   310  	w.Grow(ss)
   311  	if err := binary.Write(w, binary.LittleEndian, &l.env); err != nil {
   312  		return fmt.Errorf("cannot write LK env to buffer for validation: %v", err)
   313  	}
   314  	if l.env.Version != SNAP_BOOTSELECT_VERSION || l.env.Signature != SNAP_BOOTSELECT_SIGNATURE {
   315  		return fmt.Errorf("cannot validate version/signature for %s, got 0x%X expected 0x%X, got 0x%X expected 0x%X\n", path, l.env.Version, SNAP_BOOTSELECT_VERSION, l.env.Signature, SNAP_BOOTSELECT_SIGNATURE)
   316  	}
   317  
   318  	crc := crc32.ChecksumIEEE(w.Bytes()[:ss-4]) // size of crc32 itself at the end of the structure
   319  	if crc != l.env.Crc32 {
   320  		return fmt.Errorf("cannot validate environment checksum %s, got 0x%X expected 0x%X\n", path, crc, l.env.Crc32)
   321  	}
   322  	logger.Debugf("Load: validated crc32 (0x%X)", l.env.Crc32)
   323  	return nil
   324  }
   325  
   326  func (l *Env) Save() error {
   327  	logger.Debugf("Save")
   328  	w := bytes.NewBuffer(nil)
   329  	ss := binary.Size(l.env)
   330  	w.Grow(ss)
   331  	if err := binary.Write(w, binary.LittleEndian, &l.env); err != nil {
   332  		return fmt.Errorf("cannot write LK env to buffer for saving: %v", err)
   333  	}
   334  	// calculate crc32
   335  	l.env.Crc32 = crc32.ChecksumIEEE(w.Bytes()[:ss-4])
   336  	logger.Debugf("Save: calculated crc32 (0x%X)", l.env.Crc32)
   337  	w.Truncate(ss - 4)
   338  	binary.Write(w, binary.LittleEndian, &l.env.Crc32)
   339  
   340  	err := l.saveEnv(l.path, w)
   341  	if err != nil {
   342  		logger.Debugf("Save: failed to save main environment")
   343  	}
   344  	// if there is backup environment file save to it as well
   345  	if osutil.FileExists(l.pathbak) {
   346  		if err := l.saveEnv(l.pathbak, w); err != nil {
   347  			logger.Debugf("Save: failed to save backup environment %v", err)
   348  		}
   349  	}
   350  	return err
   351  }
   352  
   353  func (l *Env) saveEnv(path string, buf *bytes.Buffer) error {
   354  	f, err := os.OpenFile(path, os.O_WRONLY, 0660)
   355  	if err != nil {
   356  		return fmt.Errorf("cannot open LK env file for env storing: %v", err)
   357  	}
   358  	defer f.Close()
   359  
   360  	if _, err := f.Write(buf.Bytes()); err != nil {
   361  		return fmt.Errorf("cannot write LK env buf to LK env file: %v", err)
   362  	}
   363  	if err := f.Sync(); err != nil {
   364  		return fmt.Errorf("cannot sync LK env file: %v", err)
   365  	}
   366  	return nil
   367  }
   368  
   369  // FindFreeBootPartition find free boot partition to be used for new kernel revision
   370  // - consider kernel snap blob name, if kernel name matches
   371  //   already installed revision, return coresponding partition name
   372  // - protect partition used by kernel_snap, consider other as free
   373  // - consider only boot partitions with defined partition name
   374  func (l *Env) FindFreeBootPartition(kernel string) (string, error) {
   375  	for x := range l.env.Bootimg_matrix {
   376  		bp := cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:])
   377  		if bp != "" {
   378  			k := cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:])
   379  			if k != cToGoString(l.env.Snap_kernel[:]) || k == kernel || k == "" {
   380  				return cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]), nil
   381  			}
   382  		}
   383  	}
   384  	return "", fmt.Errorf("cannot find free partition for boot image")
   385  }
   386  
   387  // SetBootPartition sets the kernel revision reference in the provided boot
   388  // partition reference to the provided kernel revision. It returns a non-nil err
   389  // if the provided boot partition reference was not found.
   390  func (l *Env) SetBootPartition(bootpart, kernel string) error {
   391  	for x := range l.env.Bootimg_matrix {
   392  		if bootpart == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]) {
   393  			copyString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:], kernel)
   394  			return nil
   395  		}
   396  	}
   397  	return fmt.Errorf("cannot find defined [%s] boot image partition", bootpart)
   398  }
   399  
   400  // GetBootPartition returns the first found boot partition that contains a
   401  // reference to the given kernel revision. If the revision was not found, a
   402  // non-nil error is returned.
   403  func (l *Env) GetBootPartition(kernel string) (string, error) {
   404  	for x := range l.env.Bootimg_matrix {
   405  		if kernel == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:]) {
   406  			return cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]), nil
   407  		}
   408  	}
   409  	return "", fmt.Errorf("cannot find kernel %q in boot image partitions", kernel)
   410  }
   411  
   412  // RemoveKernelRevisionFromBootPartition removes from the boot image matrix the
   413  // first found boot partition that contains a reference to the given kernel
   414  // revision. If the referenced kernel revision was not found, a non-nil err is
   415  // returned, otherwise the reference is removed and nil is returned.
   416  // Note that to persist this change the env must be saved afterwards with Save.
   417  func (l *Env) RemoveKernelRevisionFromBootPartition(kernel string) error {
   418  	for x := range l.env.Bootimg_matrix {
   419  		if "" != cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]) {
   420  			if kernel == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:]) {
   421  				l.env.Bootimg_matrix[x][1][MATRIX_ROW_PARTITION] = 0
   422  				return nil
   423  			}
   424  		}
   425  	}
   426  	return fmt.Errorf("cannot find defined [%s] boot image partition", kernel)
   427  }
   428  
   429  // GetBootImageName return expected boot image file name in kernel snap
   430  func (l *Env) GetBootImageName() string {
   431  	if "" != cToGoString(l.env.Bootimg_file_name[:]) {
   432  		return cToGoString(l.env.Bootimg_file_name[:])
   433  	}
   434  	return BOOTIMG_DEFAULT_NAME
   435  }