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