gitee.com/mysnapcore/mysnapd@v0.1.0/boot/bootchain.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020-2022 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
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"fmt"
    26  	"os"
    27  	"path/filepath"
    28  	"sort"
    29  
    30  	"gitee.com/mysnapcore/mysnapd/asserts"
    31  	"gitee.com/mysnapcore/mysnapd/bootloader"
    32  	"gitee.com/mysnapcore/mysnapd/dirs"
    33  	"gitee.com/mysnapcore/mysnapd/osutil"
    34  	"gitee.com/mysnapcore/mysnapd/secboot"
    35  )
    36  
    37  // TODO:UC20 add a doc comment when this is stabilized
    38  type bootChain struct {
    39  	BrandID        string             `json:"brand-id"`
    40  	Model          string             `json:"model"`
    41  	Classic        bool               `json:"classic,omitempty"`
    42  	Grade          asserts.ModelGrade `json:"grade"`
    43  	ModelSignKeyID string             `json:"model-sign-key-id"`
    44  	AssetChain     []bootAsset        `json:"asset-chain"`
    45  	Kernel         string             `json:"kernel"`
    46  	// KernelRevision is the revision of the kernel snap. It is empty if
    47  	// kernel is unasserted, in which case always reseal.
    48  	KernelRevision string   `json:"kernel-revision"`
    49  	KernelCmdlines []string `json:"kernel-cmdlines"`
    50  
    51  	kernelBootFile bootloader.BootFile
    52  }
    53  
    54  func (b *bootChain) modelForSealing() *modelForSealing {
    55  	return &modelForSealing{
    56  		brandID:        b.BrandID,
    57  		model:          b.Model,
    58  		classic:        b.Classic,
    59  		grade:          b.Grade,
    60  		modelSignKeyID: b.ModelSignKeyID,
    61  	}
    62  }
    63  
    64  // TODO:UC20 add a doc comment when this is stabilized
    65  type bootAsset struct {
    66  	Role   bootloader.Role `json:"role"`
    67  	Name   string          `json:"name"`
    68  	Hashes []string        `json:"hashes"`
    69  }
    70  
    71  func bootAssetLess(b, other *bootAsset) bool {
    72  	byRole := b.Role < other.Role
    73  	byName := b.Name < other.Name
    74  	// sort order: role -> name -> hash list (len -> lexical)
    75  	if b.Role != other.Role {
    76  		return byRole
    77  	}
    78  	if b.Name != other.Name {
    79  		return byName
    80  	}
    81  	return stringListsLess(b.Hashes, other.Hashes)
    82  }
    83  
    84  func stringListsEqual(sl1, sl2 []string) bool {
    85  	if len(sl1) != len(sl2) {
    86  		return false
    87  	}
    88  	for i := range sl1 {
    89  		if sl1[i] != sl2[i] {
    90  			return false
    91  		}
    92  	}
    93  	return true
    94  }
    95  
    96  func stringListsLess(sl1, sl2 []string) bool {
    97  	if len(sl1) != len(sl2) {
    98  		return len(sl1) < len(sl2)
    99  	}
   100  	for idx := range sl1 {
   101  		if sl1[idx] < sl2[idx] {
   102  			return true
   103  		}
   104  	}
   105  	return false
   106  }
   107  
   108  func toPredictableBootAsset(b *bootAsset) *bootAsset {
   109  	if b == nil {
   110  		return nil
   111  	}
   112  	newB := *b
   113  	if b.Hashes != nil {
   114  		newB.Hashes = make([]string, len(b.Hashes))
   115  		copy(newB.Hashes, b.Hashes)
   116  		sort.Strings(newB.Hashes)
   117  	}
   118  	return &newB
   119  }
   120  
   121  func toPredictableBootChain(b *bootChain) *bootChain {
   122  	if b == nil {
   123  		return nil
   124  	}
   125  	newB := *b
   126  	if b.AssetChain != nil {
   127  		newB.AssetChain = make([]bootAsset, len(b.AssetChain))
   128  		for i := range b.AssetChain {
   129  			newB.AssetChain[i] = *toPredictableBootAsset(&b.AssetChain[i])
   130  		}
   131  	}
   132  	if b.KernelCmdlines != nil {
   133  		newB.KernelCmdlines = make([]string, len(b.KernelCmdlines))
   134  		copy(newB.KernelCmdlines, b.KernelCmdlines)
   135  		sort.Strings(newB.KernelCmdlines)
   136  	}
   137  	return &newB
   138  }
   139  
   140  func predictableBootAssetsEqual(b1, b2 []bootAsset) bool {
   141  	b1JSON, err := json.Marshal(b1)
   142  	if err != nil {
   143  		return false
   144  	}
   145  	b2JSON, err := json.Marshal(b2)
   146  	if err != nil {
   147  		return false
   148  	}
   149  	return bytes.Equal(b1JSON, b2JSON)
   150  }
   151  
   152  func predictableBootAssetsLess(b1, b2 []bootAsset) bool {
   153  	if len(b1) != len(b2) {
   154  		return len(b1) < len(b2)
   155  	}
   156  	for i := range b1 {
   157  		if bootAssetLess(&b1[i], &b2[i]) {
   158  			return true
   159  		}
   160  	}
   161  	return false
   162  }
   163  
   164  type byBootChainOrder []bootChain
   165  
   166  func (b byBootChainOrder) Len() int      { return len(b) }
   167  func (b byBootChainOrder) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
   168  func (b byBootChainOrder) Less(i, j int) bool {
   169  	// sort by model info
   170  	if b[i].BrandID != b[j].BrandID {
   171  		return b[i].BrandID < b[j].BrandID
   172  	}
   173  	if b[i].Model != b[j].Model {
   174  		return b[i].Model < b[j].Model
   175  	}
   176  	if b[i].Grade != b[j].Grade {
   177  		return b[i].Grade < b[j].Grade
   178  	}
   179  	if b[i].ModelSignKeyID != b[j].ModelSignKeyID {
   180  		return b[i].ModelSignKeyID < b[j].ModelSignKeyID
   181  	}
   182  	// then boot assets
   183  	if !predictableBootAssetsEqual(b[i].AssetChain, b[j].AssetChain) {
   184  		return predictableBootAssetsLess(b[i].AssetChain, b[j].AssetChain)
   185  	}
   186  	// then kernel
   187  	if b[i].Kernel != b[j].Kernel {
   188  		return b[i].Kernel < b[j].Kernel
   189  	}
   190  	if b[i].KernelRevision != b[j].KernelRevision {
   191  		return b[i].KernelRevision < b[j].KernelRevision
   192  	}
   193  	// and last kernel command lines
   194  	if !stringListsEqual(b[i].KernelCmdlines, b[j].KernelCmdlines) {
   195  		return stringListsLess(b[i].KernelCmdlines, b[j].KernelCmdlines)
   196  	}
   197  	return false
   198  }
   199  
   200  type predictableBootChains []bootChain
   201  
   202  // hasUnrevisionedKernels returns true if any of the chains have an
   203  // unrevisioned kernel. Revisions will not be set for unasserted
   204  // kernels.
   205  func (pbc predictableBootChains) hasUnrevisionedKernels() bool {
   206  	for i := range pbc {
   207  		if pbc[i].KernelRevision == "" {
   208  			return true
   209  		}
   210  	}
   211  	return false
   212  }
   213  
   214  func toPredictableBootChains(chains []bootChain) predictableBootChains {
   215  	if chains == nil {
   216  		return nil
   217  	}
   218  	predictableChains := make([]bootChain, len(chains))
   219  	for i := range chains {
   220  		predictableChains[i] = *toPredictableBootChain(&chains[i])
   221  	}
   222  	sort.Sort(byBootChainOrder(predictableChains))
   223  	return predictableChains
   224  }
   225  
   226  type bootChainEquivalence int
   227  
   228  const (
   229  	bootChainEquivalent   bootChainEquivalence = 0
   230  	bootChainDifferent    bootChainEquivalence = 1
   231  	bootChainUnrevisioned bootChainEquivalence = -1
   232  )
   233  
   234  // predictableBootChainsEqualForReseal returns bootChainEquivalent
   235  // when boot chains are equivalent for reseal. If the boot chains
   236  // are clearly different it returns bootChainDifferent.
   237  // If it would return bootChainEquivalent but the chains contain
   238  // unrevisioned kernels it will return bootChainUnrevisioned.
   239  func predictableBootChainsEqualForReseal(pb1, pb2 predictableBootChains) bootChainEquivalence {
   240  	pb1JSON, err := json.Marshal(pb1)
   241  	if err != nil {
   242  		return bootChainDifferent
   243  	}
   244  	pb2JSON, err := json.Marshal(pb2)
   245  	if err != nil {
   246  		return bootChainDifferent
   247  	}
   248  	if bytes.Equal(pb1JSON, pb2JSON) {
   249  		if pb1.hasUnrevisionedKernels() {
   250  			return bootChainUnrevisioned
   251  		}
   252  		return bootChainEquivalent
   253  	}
   254  	return bootChainDifferent
   255  }
   256  
   257  // bootAssetsToLoadChains generates a list of load chains covering given boot
   258  // assets sequence. At the end of each chain, adds an entry for the kernel boot
   259  // file.
   260  func bootAssetsToLoadChains(assets []bootAsset, kernelBootFile bootloader.BootFile, roleToBlName map[bootloader.Role]string) ([]*secboot.LoadChain, error) {
   261  	// kernel is added after all the assets
   262  	addKernelBootFile := len(assets) == 0
   263  	if addKernelBootFile {
   264  		return []*secboot.LoadChain{secboot.NewLoadChain(kernelBootFile)}, nil
   265  	}
   266  
   267  	thisAsset := assets[0]
   268  	blName := roleToBlName[thisAsset.Role]
   269  	if blName == "" {
   270  		return nil, fmt.Errorf("internal error: no bootloader name for boot asset role %q", thisAsset.Role)
   271  	}
   272  	var chains []*secboot.LoadChain
   273  	for _, hash := range thisAsset.Hashes {
   274  		var bf bootloader.BootFile
   275  		var next []*secboot.LoadChain
   276  		var err error
   277  
   278  		p := filepath.Join(
   279  			dirs.SnapBootAssetsDir,
   280  			trustedAssetCacheRelPath(blName, thisAsset.Name, hash))
   281  		if !osutil.FileExists(p) {
   282  			return nil, fmt.Errorf("file %s not found in boot assets cache", p)
   283  		}
   284  		bf = bootloader.NewBootFile(
   285  			"", // asset comes from the filesystem, not a snap
   286  			p,
   287  			thisAsset.Role,
   288  		)
   289  		next, err = bootAssetsToLoadChains(assets[1:], kernelBootFile, roleToBlName)
   290  		if err != nil {
   291  			return nil, err
   292  		}
   293  		chains = append(chains, secboot.NewLoadChain(bf, next...))
   294  	}
   295  	return chains, nil
   296  }
   297  
   298  // predictableBootChainsWrapperForStorage wraps the boot chains so
   299  // that we do not store the arrays directly as JSON and we can add
   300  // other information
   301  type predictableBootChainsWrapperForStorage struct {
   302  	ResealCount int                   `json:"reseal-count"`
   303  	BootChains  predictableBootChains `json:"boot-chains"`
   304  }
   305  
   306  func readBootChains(path string) (pbc predictableBootChains, resealCount int, err error) {
   307  	inf, err := os.Open(path)
   308  	if err != nil {
   309  		if os.IsNotExist(err) {
   310  			return nil, 0, nil
   311  		}
   312  		return nil, 0, fmt.Errorf("cannot open existing boot chains data file: %v", err)
   313  	}
   314  	defer inf.Close()
   315  	var wrapped predictableBootChainsWrapperForStorage
   316  	if err := json.NewDecoder(inf).Decode(&wrapped); err != nil {
   317  		return nil, 0, fmt.Errorf("cannot read boot chains data: %v", err)
   318  	}
   319  	return wrapped.BootChains, wrapped.ResealCount, nil
   320  }
   321  
   322  func writeBootChains(pbc predictableBootChains, path string, resealCount int) error {
   323  	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
   324  		return fmt.Errorf("cannot create device fde state directory: %v", err)
   325  	}
   326  	outf, err := osutil.NewAtomicFile(path, 0600, 0, osutil.NoChown, osutil.NoChown)
   327  	if err != nil {
   328  		return fmt.Errorf("cannot create a temporary boot chains file: %v", err)
   329  	}
   330  	// becomes noop when the file is committed
   331  	defer outf.Cancel()
   332  
   333  	wrapped := predictableBootChainsWrapperForStorage{
   334  		ResealCount: resealCount,
   335  		BootChains:  pbc,
   336  	}
   337  	if err := json.NewEncoder(outf).Encode(wrapped); err != nil {
   338  		return fmt.Errorf("cannot write boot chains data: %v", err)
   339  	}
   340  	return outf.Commit()
   341  }