github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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/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 partion 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 pupulated 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 slow (first row, second row)
   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_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 partions
   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  	Kye_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  	// osutil.FileExists(path + "bak")
   214  	return &Env{
   215  		path:    path,
   216  		pathbak: path + "bak",
   217  		env: SnapBootSelect_v1{
   218  			Signature: SNAP_BOOTSELECT_SIGNATURE,
   219  			Version:   SNAP_BOOTSELECT_VERSION,
   220  		},
   221  	}
   222  }
   223  
   224  func (l *Env) Get(key string) string {
   225  	switch key {
   226  	case "snap_mode":
   227  		return cToGoString(l.env.Snap_mode[:])
   228  	case "snap_kernel":
   229  		return cToGoString(l.env.Snap_kernel[:])
   230  	case "snap_try_kernel":
   231  		return cToGoString(l.env.Snap_try_kernel[:])
   232  	case "snap_core":
   233  		return cToGoString(l.env.Snap_core[:])
   234  	case "snap_try_core":
   235  		return cToGoString(l.env.Snap_try_core[:])
   236  	case "snap_gadget":
   237  		return cToGoString(l.env.Snap_gadget[:])
   238  	case "snap_try_gadget":
   239  		return cToGoString(l.env.Snap_try_gadget[:])
   240  	case "reboot_reason":
   241  		return cToGoString(l.env.Reboot_reason[:])
   242  	case "bootimg_file_name":
   243  		return cToGoString(l.env.Bootimg_file_name[:])
   244  	}
   245  	return ""
   246  }
   247  
   248  func (l *Env) Set(key, value string) {
   249  	switch key {
   250  	case "snap_mode":
   251  		copyString(l.env.Snap_mode[:], value)
   252  	case "snap_kernel":
   253  		copyString(l.env.Snap_kernel[:], value)
   254  	case "snap_try_kernel":
   255  		copyString(l.env.Snap_try_kernel[:], value)
   256  	case "snap_core":
   257  		copyString(l.env.Snap_core[:], value)
   258  	case "snap_try_core":
   259  		copyString(l.env.Snap_try_core[:], value)
   260  	case "snap_gadget":
   261  		copyString(l.env.Snap_gadget[:], value)
   262  	case "snap_try_gadget":
   263  		copyString(l.env.Snap_try_gadget[:], value)
   264  	case "reboot_reason":
   265  		copyString(l.env.Reboot_reason[:], value)
   266  	case "bootimg_file_name":
   267  		copyString(l.env.Bootimg_file_name[:], value)
   268  	}
   269  }
   270  
   271  // ConfigureBootPartitions set boot partitions label names
   272  // this function should not be used at run time!
   273  // it should be used only at image build time,
   274  // if partition labels are not pre-filled by gadget built
   275  func (l *Env) ConfigureBootPartitions(boot_1, boot_2 string) {
   276  	copyString(l.env.Bootimg_matrix[0][MATRIX_ROW_PARTITION][:], boot_1)
   277  	copyString(l.env.Bootimg_matrix[1][MATRIX_ROW_PARTITION][:], boot_2)
   278  }
   279  
   280  // ConfigureBootimgName set boot image file name
   281  // boot image file name is used at kernel extraction time
   282  // this function should not be used at run time!
   283  // it should be used only at image build time
   284  // if default boot.img is not set by gadget built
   285  func (l *Env) ConfigureBootimgName(bootimgName string) {
   286  	copyString(l.env.Bootimg_file_name[:], bootimgName)
   287  }
   288  
   289  func (l *Env) Load() error {
   290  	err := l.LoadEnv(l.path)
   291  	if err != nil {
   292  		return l.LoadEnv(l.pathbak)
   293  	}
   294  	return nil
   295  }
   296  
   297  func (l *Env) LoadEnv(path string) error {
   298  	f, err := os.Open(path)
   299  	if err != nil {
   300  		return fmt.Errorf("cannot open LK env file: %v", err)
   301  	}
   302  
   303  	defer f.Close()
   304  	if err := binary.Read(f, binary.LittleEndian, &l.env); err != nil {
   305  		return fmt.Errorf("cannot read LK env from file: %v", err)
   306  	}
   307  
   308  	// calculate crc32 to validate structure
   309  	w := bytes.NewBuffer(nil)
   310  	ss := binary.Size(l.env)
   311  	w.Grow(ss)
   312  	if err := binary.Write(w, binary.LittleEndian, &l.env); err != nil {
   313  		return fmt.Errorf("cannot write LK env to buffer for validation: %v", err)
   314  	}
   315  	if l.env.Version != SNAP_BOOTSELECT_VERSION || l.env.Signature != SNAP_BOOTSELECT_SIGNATURE {
   316  		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)
   317  	}
   318  
   319  	crc := crc32.ChecksumIEEE(w.Bytes()[:ss-4]) // size of crc32 itself at the end of the structure
   320  	if crc != l.env.Crc32 {
   321  		return fmt.Errorf("cannot validate environment checksum %s, got 0x%X expected 0x%X\n", path, crc, l.env.Crc32)
   322  	}
   323  	logger.Debugf("Load: validated crc32 (0x%X)", l.env.Crc32)
   324  	return nil
   325  }
   326  
   327  func (l *Env) Save() error {
   328  	logger.Debugf("Save")
   329  	w := bytes.NewBuffer(nil)
   330  	ss := binary.Size(l.env)
   331  	w.Grow(ss)
   332  	if err := binary.Write(w, binary.LittleEndian, &l.env); err != nil {
   333  		return fmt.Errorf("cannot write LK env to buffer for saving: %v", err)
   334  	}
   335  	// calculate crc32
   336  	l.env.Crc32 = crc32.ChecksumIEEE(w.Bytes()[:ss-4])
   337  	logger.Debugf("Save: calculated crc32 (0x%X)", l.env.Crc32)
   338  	w.Truncate(ss - 4)
   339  	binary.Write(w, binary.LittleEndian, &l.env.Crc32)
   340  
   341  	err := l.SaveEnv(l.path, w)
   342  	if err != nil {
   343  		logger.Debugf("Save: failed to save main environment")
   344  	}
   345  	// if there is backup environment file save to it as well
   346  	if osutil.FileExists(l.pathbak) {
   347  		if err := l.SaveEnv(l.pathbak, w); err != nil {
   348  			logger.Debugf("Save: failed to save backup environment %v", err)
   349  		}
   350  	}
   351  	return err
   352  }
   353  
   354  func (l *Env) SaveEnv(path string, buf *bytes.Buffer) error {
   355  	f, err := os.OpenFile(path, os.O_WRONLY, 0660)
   356  	if err != nil {
   357  		return fmt.Errorf("cannot open LK env file for env storing: %v", err)
   358  	}
   359  	defer f.Close()
   360  
   361  	if _, err := f.Write(buf.Bytes()); err != nil {
   362  		return fmt.Errorf("cannot write LK env buf to LK env file: %v", err)
   363  	}
   364  	if err := f.Sync(); err != nil {
   365  		return fmt.Errorf("cannot sync LK env file: %v", err)
   366  	}
   367  	return nil
   368  }
   369  
   370  // FindFreeBootPartition find free boot partition to be used for new kernel revision
   371  // - consider kernel snap blob name, if kernel name matches
   372  //   already installed revision, return coresponding partition name
   373  // - protect partition used by kernel_snap, consider other as free
   374  // - consider only boot partitions with defined partition name
   375  func (l *Env) FindFreeBootPartition(kernel string) (string, error) {
   376  	for x := range l.env.Bootimg_matrix {
   377  		bp := cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:])
   378  		if bp != "" {
   379  			k := cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:])
   380  			if k != cToGoString(l.env.Snap_kernel[:]) || k == kernel || k == "" {
   381  				return cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]), nil
   382  			}
   383  		}
   384  	}
   385  	return "", fmt.Errorf("cannot find free partition for boot image")
   386  }
   387  
   388  // SetBootPartition set kernel revision name to passed boot partition
   389  func (l *Env) SetBootPartition(bootpart, kernel string) error {
   390  	for x := range l.env.Bootimg_matrix {
   391  		if bootpart == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]) {
   392  			copyString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:], kernel)
   393  			return nil
   394  		}
   395  	}
   396  	return fmt.Errorf("cannot find defined [%s] boot image partition", bootpart)
   397  }
   398  
   399  func (l *Env) GetBootPartition(kernel string) (string, error) {
   400  	for x := range l.env.Bootimg_matrix {
   401  		if kernel == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:]) {
   402  			return cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]), nil
   403  		}
   404  	}
   405  	return "", fmt.Errorf("cannot find kernel %q in boot image partitions", kernel)
   406  }
   407  
   408  // FreeBootPartition free passed kernel revision from any boot partition
   409  // ignore if there is no boot partition with given kernel revision
   410  func (l *Env) FreeBootPartition(kernel string) (bool, error) {
   411  	for x := range l.env.Bootimg_matrix {
   412  		if "" != cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]) {
   413  			if kernel == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:]) {
   414  				l.env.Bootimg_matrix[x][1][MATRIX_ROW_PARTITION] = 0
   415  				return true, nil
   416  			}
   417  		}
   418  	}
   419  	return false, fmt.Errorf("cannot find defined [%s] boot image partition", kernel)
   420  }
   421  
   422  // GetBootImageName return expected boot image file name in kernel snap
   423  func (l *Env) GetBootImageName() string {
   424  	if "" != cToGoString(l.env.Bootimg_file_name[:]) {
   425  		return cToGoString(l.env.Bootimg_file_name[:])
   426  	}
   427  	return BOOTIMG_DEFAULT_NAME
   428  }