github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/boot/assets.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 "crypto" 24 "encoding/hex" 25 "errors" 26 "fmt" 27 "io" 28 "os" 29 "path/filepath" 30 31 _ "golang.org/x/crypto/sha3" 32 33 "github.com/snapcore/snapd/asserts" 34 "github.com/snapcore/snapd/bootloader" 35 "github.com/snapcore/snapd/dirs" 36 "github.com/snapcore/snapd/gadget" 37 "github.com/snapcore/snapd/logger" 38 "github.com/snapcore/snapd/osutil" 39 "github.com/snapcore/snapd/secboot" 40 "github.com/snapcore/snapd/strutil" 41 ) 42 43 type trustedAssetsCache struct { 44 cacheDir string 45 hash crypto.Hash 46 } 47 48 func newTrustedAssetsCache(cacheDir string) *trustedAssetsCache { 49 return &trustedAssetsCache{cacheDir: cacheDir, hash: crypto.SHA3_384} 50 } 51 52 func (c *trustedAssetsCache) tempAssetRelPath(blName, assetName string) string { 53 return filepath.Join(blName, assetName+".temp") 54 } 55 56 func (c *trustedAssetsCache) pathInCache(part string) string { 57 return filepath.Join(c.cacheDir, part) 58 } 59 60 func trustedAssetCacheRelPath(blName, assetName, assetHash string) string { 61 return filepath.Join(blName, fmt.Sprintf("%s-%s", assetName, assetHash)) 62 } 63 64 // fileHash calculates the hash of an arbitrary file using the same hash method 65 // as the cache. 66 func (c *trustedAssetsCache) fileHash(name string) (string, error) { 67 digest, _, err := osutil.FileDigest(name, c.hash) 68 if err != nil { 69 return "", err 70 } 71 return hex.EncodeToString(digest), nil 72 } 73 74 // Add entry for a new named asset owned by a particular bootloader, with the 75 // binary content of the located at a given path. The cache ensures that only 76 // one entry for given tuple of (bootloader name, asset name, content-hash) 77 // exists in the cache. 78 func (c *trustedAssetsCache) Add(assetPath, blName, assetName string) (*trackedAsset, error) { 79 if err := os.MkdirAll(c.pathInCache(blName), 0755); err != nil { 80 return nil, fmt.Errorf("cannot create cache directory: %v", err) 81 } 82 83 // input 84 inf, err := os.Open(assetPath) 85 if err != nil { 86 return nil, fmt.Errorf("cannot open asset file: %v", err) 87 } 88 defer inf.Close() 89 // temporary output 90 tempPath := c.pathInCache(c.tempAssetRelPath(blName, assetName)) 91 outf, err := osutil.NewAtomicFile(tempPath, 0644, 0, osutil.NoChown, osutil.NoChown) 92 if err != nil { 93 return nil, fmt.Errorf("cannot create temporary cache file: %v", err) 94 } 95 defer outf.Cancel() 96 97 // copy and hash at the same time 98 h := c.hash.New() 99 tr := io.TeeReader(inf, h) 100 if _, err := io.Copy(outf, tr); err != nil { 101 return nil, fmt.Errorf("cannot copy trusted asset to cache: %v", err) 102 } 103 hashStr := hex.EncodeToString(h.Sum(nil)) 104 cacheKey := trustedAssetCacheRelPath(blName, assetName, hashStr) 105 106 ta := &trackedAsset{ 107 blName: blName, 108 name: assetName, 109 hash: hashStr, 110 } 111 112 targetName := c.pathInCache(cacheKey) 113 if osutil.FileExists(targetName) { 114 // asset is already cached 115 return ta, nil 116 } 117 // commit under a new name 118 if err := outf.CommitAs(targetName); err != nil { 119 return nil, fmt.Errorf("cannot commit file to assets cache: %v", err) 120 } 121 return ta, nil 122 } 123 124 func (c *trustedAssetsCache) Remove(blName, assetName, hashStr string) error { 125 cacheKey := trustedAssetCacheRelPath(blName, assetName, hashStr) 126 if err := os.Remove(c.pathInCache(cacheKey)); err != nil && !os.IsNotExist(err) { 127 return err 128 } 129 return nil 130 } 131 132 // CopyBootAssetsCacheToRoot copies the boot assets cache to a corresponding 133 // location under a new root directory. 134 func CopyBootAssetsCacheToRoot(dstRoot string) error { 135 if !osutil.IsDirectory(dirs.SnapBootAssetsDir) { 136 // nothing to copy 137 return nil 138 } 139 140 newCacheRoot := dirs.SnapBootAssetsDirUnder(dstRoot) 141 if err := os.MkdirAll(newCacheRoot, 0755); err != nil { 142 return fmt.Errorf("cannot create cache directory under new root: %v", err) 143 } 144 err := filepath.Walk(dirs.SnapBootAssetsDir, func(path string, info os.FileInfo, err error) error { 145 if err != nil { 146 return err 147 } 148 relPath, err := filepath.Rel(dirs.SnapBootAssetsDir, path) 149 if err != nil { 150 return err 151 } 152 if info.IsDir() { 153 if err := os.MkdirAll(filepath.Join(newCacheRoot, relPath), info.Mode()); err != nil { 154 return fmt.Errorf("cannot recreate cache directory %q: %v", relPath, err) 155 } 156 return nil 157 } 158 if !info.Mode().IsRegular() { 159 return fmt.Errorf("unsupported non-file entry %q mode %v", relPath, info.Mode()) 160 } 161 if err := osutil.CopyFile(path, filepath.Join(newCacheRoot, relPath), osutil.CopyFlagPreserveAll); err != nil { 162 return fmt.Errorf("cannot copy boot asset cache file %q: %v", relPath, err) 163 } 164 return nil 165 }) 166 return err 167 } 168 169 // ErrObserverNotApplicable indicates that observer is not applicable for use 170 // with the model. 171 var ErrObserverNotApplicable = errors.New("observer not applicable") 172 173 // TrustedAssetsInstallObserverForModel returns a new trusted assets observer 174 // for use during installation of the run mode system, provided the device model 175 // supports secure boot. Otherwise, nil and ErrObserverNotApplicable is 176 // returned. 177 func TrustedAssetsInstallObserverForModel(model *asserts.Model, gadgetDir string) (*TrustedAssetsInstallObserver, error) { 178 if model.Grade() == asserts.ModelGradeUnset { 179 // no need to observe updates when assets are not managed 180 return nil, ErrObserverNotApplicable 181 } 182 if gadgetDir == "" { 183 return nil, fmt.Errorf("internal error: gadget dir not provided") 184 } 185 186 return &TrustedAssetsInstallObserver{ 187 model: model, 188 cache: newTrustedAssetsCache(dirs.SnapBootAssetsDir), 189 gadgetDir: gadgetDir, 190 }, nil 191 } 192 193 type trackedAsset struct { 194 blName, name, hash string 195 } 196 197 func isAssetAlreadyTracked(bam bootAssetsMap, newAsset *trackedAsset) bool { 198 return isAssetHashTrackedInMap(bam, newAsset.name, newAsset.hash) 199 } 200 201 func isAssetHashTrackedInMap(bam bootAssetsMap, assetName, assetHash string) bool { 202 if bam == nil { 203 return false 204 } 205 hashes, ok := bam[assetName] 206 if !ok { 207 return false 208 } 209 return strutil.ListContains(hashes, assetHash) 210 } 211 212 // TrustedAssetsInstallObserver tracks the installation of trusted boot assets. 213 type TrustedAssetsInstallObserver struct { 214 model *asserts.Model 215 gadgetDir string 216 cache *trustedAssetsCache 217 blName string 218 trustedAssets []string 219 trackedAssets bootAssetsMap 220 trackedRecoveryAssets bootAssetsMap 221 encryptionKey secboot.EncryptionKey 222 } 223 224 // Observe observes the operation related to the content of a given gadget 225 // structure. In particular, the TrustedAssetsInstallObserver tracks writing of 226 // managed boot assets, such as the bootloader binary which is measured as part 227 // of the secure boot. 228 // 229 // Implements gadget.ContentObserver. 230 func (o *TrustedAssetsInstallObserver) Observe(op gadget.ContentOperation, affectedStruct *gadget.LaidOutStructure, root, relativeTarget string, data *gadget.ContentChange) (bool, error) { 231 if affectedStruct.Role != gadget.SystemBoot { 232 // only care about system-boot 233 return true, nil 234 } 235 236 if o.blName == "" { 237 // we have no information about the bootloader yet 238 bl, err := bootloader.ForGadget(o.gadgetDir, root, &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true}) 239 if err != nil { 240 return false, fmt.Errorf("cannot find bootloader: %v", err) 241 } 242 o.blName = bl.Name() 243 tbl, ok := bl.(bootloader.TrustedAssetsBootloader) 244 if !ok { 245 return true, nil 246 } 247 trustedAssets, err := tbl.TrustedAssets() 248 if err != nil { 249 return false, fmt.Errorf("cannot list %q bootloader trusted assets: %v", bl.Name(), err) 250 } 251 o.trustedAssets = trustedAssets 252 } 253 if len(o.trustedAssets) == 0 || !strutil.ListContains(o.trustedAssets, relativeTarget) { 254 // not one of the trusted assets 255 return true, nil 256 } 257 ta, err := o.cache.Add(data.After, o.blName, filepath.Base(relativeTarget)) 258 if err != nil { 259 return false, err 260 } 261 // during installation, modeenv is written out later, at this point we 262 // only care that the same file may appear multiple times in gadget 263 // structure content, so make sure we are not tracking it yet 264 if !isAssetAlreadyTracked(o.trackedAssets, ta) { 265 if o.trackedAssets == nil { 266 o.trackedAssets = bootAssetsMap{} 267 } 268 if len(o.trackedAssets[ta.name]) > 0 { 269 return false, fmt.Errorf("cannot reuse asset name %q", ta.name) 270 } 271 o.trackedAssets[ta.name] = append(o.trackedAssets[ta.name], ta.hash) 272 } 273 return true, nil 274 } 275 276 // ObserveExistingTrustedRecoveryAssets observes existing trusted assets of a 277 // recovery bootloader located inside a given root directory. 278 func (o *TrustedAssetsInstallObserver) ObserveExistingTrustedRecoveryAssets(recoveryRootDir string) error { 279 bl, err := bootloader.Find(recoveryRootDir, &bootloader.Options{ 280 Role: bootloader.RoleRecovery, 281 }) 282 if err != nil { 283 return fmt.Errorf("cannot identify recovery system bootloader: %v", err) 284 } 285 tbl, ok := bl.(bootloader.TrustedAssetsBootloader) 286 if !ok { 287 // not a trusted assets bootloader 288 return nil 289 } 290 trustedAssets, err := tbl.TrustedAssets() 291 if err != nil { 292 return fmt.Errorf("cannot list %q recovery bootloader trusted assets: %v", bl.Name(), err) 293 } 294 for _, trustedAsset := range trustedAssets { 295 ta, err := o.cache.Add(filepath.Join(recoveryRootDir, trustedAsset), bl.Name(), filepath.Base(trustedAsset)) 296 if err != nil { 297 return err 298 } 299 if !isAssetAlreadyTracked(o.trackedRecoveryAssets, ta) { 300 if o.trackedRecoveryAssets == nil { 301 o.trackedRecoveryAssets = bootAssetsMap{} 302 } 303 if len(o.trackedRecoveryAssets[ta.name]) > 0 { 304 return fmt.Errorf("cannot reuse recovery asset name %q", ta.name) 305 } 306 o.trackedRecoveryAssets[ta.name] = append(o.trackedRecoveryAssets[ta.name], ta.hash) 307 } 308 } 309 return nil 310 } 311 312 func (o *TrustedAssetsInstallObserver) currentTrustedBootAssetsMap() bootAssetsMap { 313 return o.trackedAssets 314 } 315 316 func (o *TrustedAssetsInstallObserver) currentTrustedRecoveryBootAssetsMap() bootAssetsMap { 317 return o.trackedRecoveryAssets 318 } 319 320 func (o *TrustedAssetsInstallObserver) ChosenEncryptionKey(key secboot.EncryptionKey) { 321 o.encryptionKey = key 322 } 323 324 // TrustedAssetsUpdateObserverForModel returns a new trusted assets observer for 325 // tracking changes to the measured boot assets during gadget updates, provided 326 // the device model supports secure boot. Otherwise, nil and ErrObserverNotApplicable is 327 // returned. 328 func TrustedAssetsUpdateObserverForModel(model *asserts.Model) (*TrustedAssetsUpdateObserver, error) { 329 if model.Grade() == asserts.ModelGradeUnset { 330 // no need to observe updates when assets are not managed 331 return nil, ErrObserverNotApplicable 332 } 333 334 return &TrustedAssetsUpdateObserver{ 335 cache: newTrustedAssetsCache(dirs.SnapBootAssetsDir), 336 }, nil 337 } 338 339 // TrustedAssetsUpdateObserver tracks the updates of trusted boot assets and 340 // attempts to reseal when needed. 341 type TrustedAssetsUpdateObserver struct { 342 cache *trustedAssetsCache 343 344 bootBootloader bootloader.Bootloader 345 bootTrustedAssets []string 346 changedAssets []*trackedAsset 347 348 seedBootloader bootloader.Bootloader 349 seedTrustedAssets []string 350 seedChangedAssets []*trackedAsset 351 352 modeenv *Modeenv 353 } 354 355 func findMaybeTrustedAssetsBootloader(root string, opts *bootloader.Options) (foundBl bootloader.Bootloader, trustedAssets []string, err error) { 356 foundBl, err = bootloader.Find(root, opts) 357 if err != nil { 358 return nil, nil, fmt.Errorf("cannot find bootloader: %v", err) 359 } 360 tbl, ok := foundBl.(bootloader.TrustedAssetsBootloader) 361 if !ok { 362 return foundBl, nil, nil 363 } 364 trustedAssets, err = tbl.TrustedAssets() 365 if err != nil { 366 return nil, nil, fmt.Errorf("cannot list %q bootloader trusted assets: %v", foundBl.Name(), err) 367 } 368 return foundBl, trustedAssets, nil 369 } 370 371 // Observe observes the operation related to the update or rollback of the 372 // content of a given gadget structure. In particular, the 373 // TrustedAssetsUpdateObserver tracks updates of managed boot assets, such as 374 // the bootloader binary which is measured as part of the secure boot. 375 // 376 // Implements gadget.ContentUpdateObserver. 377 func (o *TrustedAssetsUpdateObserver) Observe(op gadget.ContentOperation, affectedStruct *gadget.LaidOutStructure, root, relativeTarget string, data *gadget.ContentChange) (bool, error) { 378 var whichBootloader bootloader.Bootloader 379 var whichAssets []string 380 var err error 381 var isRecovery bool 382 383 switch affectedStruct.Role { 384 case gadget.SystemBoot: 385 if o.bootBootloader == nil { 386 o.bootBootloader, o.bootTrustedAssets, err = findMaybeTrustedAssetsBootloader(root, &bootloader.Options{ 387 Role: bootloader.RoleRunMode, 388 NoSlashBoot: true, 389 }) 390 if err != nil { 391 return false, err 392 } 393 } 394 whichBootloader = o.bootBootloader 395 whichAssets = o.bootTrustedAssets 396 case gadget.SystemSeed: 397 if o.seedBootloader == nil { 398 o.seedBootloader, o.seedTrustedAssets, err = findMaybeTrustedAssetsBootloader(root, &bootloader.Options{ 399 Role: bootloader.RoleRecovery, 400 }) 401 if err != nil { 402 return false, err 403 } 404 } 405 whichBootloader = o.seedBootloader 406 whichAssets = o.seedTrustedAssets 407 isRecovery = true 408 default: 409 // only system-seed and system-boot are of interest 410 return true, nil 411 } 412 if len(whichAssets) == 0 || !strutil.ListContains(whichAssets, relativeTarget) { 413 // not one of the trusted assets 414 return true, nil 415 } 416 if o.modeenv == nil { 417 // we've hit a trusted asset, so a modeenv is needed now too 418 o.modeenv, err = ReadModeenv("") 419 if err != nil { 420 return false, fmt.Errorf("cannot load modeenv: %v", err) 421 } 422 } 423 switch op { 424 case gadget.ContentUpdate: 425 return o.observeUpdate(whichBootloader, isRecovery, root, relativeTarget, data) 426 case gadget.ContentRollback: 427 return o.observeRollback(whichBootloader, isRecovery, root, relativeTarget, data) 428 default: 429 // we only care about update and rollback actions 430 return false, nil 431 } 432 } 433 434 func (o *TrustedAssetsUpdateObserver) observeUpdate(bl bootloader.Bootloader, recovery bool, root, relativeTarget string, change *gadget.ContentChange) (bool, error) { 435 modeenvBefore, err := o.modeenv.Copy() 436 if err != nil { 437 return false, fmt.Errorf("cannot copy modeenv: %v", err) 438 } 439 440 // we may be running after a mid-update reboot, where a successful boot 441 // would have trimmed the tracked assets hash lists to contain only the 442 // asset we booted with 443 444 var taBefore *trackedAsset 445 if change.Before != "" { 446 // make sure that the original copy is present in the cache if 447 // it existed 448 taBefore, err = o.cache.Add(change.Before, bl.Name(), filepath.Base(relativeTarget)) 449 if err != nil { 450 return false, err 451 } 452 } 453 454 ta, err := o.cache.Add(change.After, bl.Name(), filepath.Base(relativeTarget)) 455 if err != nil { 456 return false, err 457 } 458 459 trustedAssets := &o.modeenv.CurrentTrustedBootAssets 460 changedAssets := &o.changedAssets 461 if recovery { 462 trustedAssets = &o.modeenv.CurrentTrustedRecoveryBootAssets 463 changedAssets = &o.seedChangedAssets 464 } 465 // keep track of the change for cancellation purpose 466 *changedAssets = append(*changedAssets, ta) 467 468 if *trustedAssets == nil { 469 *trustedAssets = bootAssetsMap{} 470 } 471 472 if taBefore != nil && !isAssetAlreadyTracked(*trustedAssets, taBefore) { 473 // make sure that the boot asset that was was in the filesystem 474 // before the update, is properly tracked until either a 475 // successful boot or the update is canceled 476 // the original asset hash is listed first 477 (*trustedAssets)[taBefore.name] = append([]string{taBefore.hash}, (*trustedAssets)[taBefore.name]...) 478 } 479 480 if !isAssetAlreadyTracked(*trustedAssets, ta) { 481 if len((*trustedAssets)[ta.name]) > 1 { 482 // we expect at most 2 different blobs for a given asset 483 // name, the current one and one that will be installed 484 // during an update; more entries indicates that the 485 // same asset name is used multiple times with different 486 // content 487 return false, fmt.Errorf("cannot reuse asset name %q", ta.name) 488 } 489 (*trustedAssets)[ta.name] = append((*trustedAssets)[ta.name], ta.hash) 490 } 491 492 if o.modeenv.deepEqual(modeenvBefore) { 493 return true, nil 494 } 495 if err := o.modeenv.Write(); err != nil { 496 return false, fmt.Errorf("cannot write modeeenv: %v", err) 497 } 498 return true, nil 499 } 500 501 func (o *TrustedAssetsUpdateObserver) observeRollback(bl bootloader.Bootloader, recovery bool, root, relativeTarget string, data *gadget.ContentChange) (bool, error) { 502 trustedAssets := &o.modeenv.CurrentTrustedBootAssets 503 otherTrustedAssets := o.modeenv.CurrentTrustedRecoveryBootAssets 504 if recovery { 505 trustedAssets = &o.modeenv.CurrentTrustedRecoveryBootAssets 506 otherTrustedAssets = o.modeenv.CurrentTrustedBootAssets 507 } 508 509 assetName := filepath.Base(relativeTarget) 510 hashList, ok := (*trustedAssets)[assetName] 511 if !ok || len(hashList) == 0 { 512 // asset not tracked in modeenv 513 return true, nil 514 } 515 516 // new assets are appended to the list 517 expectedOldHash := hashList[0] 518 // sanity check, make sure that the current file is what we expect 519 newlyAdded := false 520 ondiskHash, err := o.cache.fileHash(filepath.Join(root, relativeTarget)) 521 if err != nil { 522 // file may not exist if it was added by the update, that's ok 523 if !os.IsNotExist(err) { 524 return false, fmt.Errorf("cannot calculate the digest of current asset: %v", err) 525 } 526 newlyAdded = true 527 if len(hashList) > 1 { 528 // we have more than 1 hash of the asset, so we expected 529 // a previous revision to be restored, but got nothing 530 // instead 531 return false, fmt.Errorf("tracked asset %q is unexpectedly missing from disk", 532 assetName) 533 } 534 } else { 535 if ondiskHash != expectedOldHash { 536 // this is unexpected, a different file exists on disk? 537 return false, fmt.Errorf("unexpected content of existing asset %q", relativeTarget) 538 } 539 } 540 541 newHash := "" 542 if len(hashList) == 1 { 543 if newlyAdded { 544 newHash = hashList[0] 545 } 546 } else { 547 newHash = hashList[1] 548 } 549 if newHash != "" && !isAssetHashTrackedInMap(otherTrustedAssets, assetName, newHash) { 550 // asset revision is not used used elsewhere, we can remove it from the cache 551 if err := o.cache.Remove(bl.Name(), assetName, newHash); err != nil { 552 // XXX: should this be a log instead? 553 return false, fmt.Errorf("cannot remove unused boot asset %v:%v: %v", assetName, newHash, err) 554 } 555 } 556 557 // update modeenv content 558 if !newlyAdded { 559 (*trustedAssets)[assetName] = hashList[:1] 560 } else { 561 delete(*trustedAssets, assetName) 562 } 563 564 if err := o.modeenv.Write(); err != nil { 565 return false, fmt.Errorf("cannot write modeeenv: %v", err) 566 } 567 568 return false, nil 569 } 570 571 // BeforeWrite is called when the update process has been staged for execution. 572 func (o *TrustedAssetsUpdateObserver) BeforeWrite() error { 573 // TODO:UC20: 574 // - reseal with a given state of modeenv 575 return nil 576 } 577 578 func (o *TrustedAssetsUpdateObserver) canceledUpdate(recovery bool) { 579 trustedAssets := &o.modeenv.CurrentTrustedBootAssets 580 otherTrustedAssets := o.modeenv.CurrentTrustedRecoveryBootAssets 581 changedAssets := o.changedAssets 582 if recovery { 583 trustedAssets = &o.modeenv.CurrentTrustedRecoveryBootAssets 584 otherTrustedAssets = o.modeenv.CurrentTrustedBootAssets 585 changedAssets = o.seedChangedAssets 586 } 587 588 if len(*trustedAssets) == 0 { 589 return 590 } 591 592 for _, changed := range changedAssets { 593 hashList, ok := (*trustedAssets)[changed.name] 594 if !ok || len(hashList) == 0 { 595 // not tracked already, nothing to do 596 continue 597 } 598 if len(hashList) == 1 { 599 currentAssetHash := hashList[0] 600 if currentAssetHash != changed.hash { 601 // assets list has already been trimmed, nothing 602 // to do 603 continue 604 } else { 605 // asset was newly added 606 delete(*trustedAssets, changed.name) 607 } 608 } else { 609 // asset updates were appended to the list 610 (*trustedAssets)[changed.name] = hashList[:1] 611 } 612 if !isAssetHashTrackedInMap(otherTrustedAssets, changed.name, changed.hash) { 613 // asset revision is not used used elsewhere, we can remove it from the cache 614 if err := o.cache.Remove(changed.blName, changed.name, changed.hash); err != nil { 615 logger.Noticef("cannot remove unused boot asset %v:%v: %v", changed.name, changed.hash, err) 616 } 617 } 618 } 619 } 620 621 // Canceled is called when the update has been canceled, or if changes 622 // were written and the update has been reverted. 623 func (o *TrustedAssetsUpdateObserver) Canceled() error { 624 if o.modeenv == nil { 625 // modeenv wasn't even loaded yet, meaning none of the boot 626 // assets was updated 627 return nil 628 } 629 for _, isRecovery := range []bool{false, true} { 630 o.canceledUpdate(isRecovery) 631 } 632 633 if err := o.modeenv.Write(); err != nil { 634 return fmt.Errorf("cannot write modeeenv: %v", err) 635 } 636 637 // TODO:UC20: 638 // - reseal with a given state of modeenv 639 return nil 640 } 641 642 func observeSuccessfulBootAssetsForBootloader(m *Modeenv, root string, opts *bootloader.Options) (drop []*trackedAsset, err error) { 643 trustedAssetsMap := &m.CurrentTrustedBootAssets 644 otherTrustedAssetsMap := m.CurrentTrustedRecoveryBootAssets 645 whichBootloader := "run mode" 646 if opts != nil && opts.Role == bootloader.RoleRecovery { 647 trustedAssetsMap = &m.CurrentTrustedRecoveryBootAssets 648 otherTrustedAssetsMap = m.CurrentTrustedBootAssets 649 whichBootloader = "recovery" 650 } 651 652 if len(*trustedAssetsMap) == 0 { 653 // bootloader may have trusted assets, but we are not tracking 654 // any for the boot process 655 return nil, nil 656 } 657 658 // let's find the bootloader first 659 bl, trustedAssets, err := findMaybeTrustedAssetsBootloader(root, opts) 660 if err != nil { 661 return nil, err 662 } 663 if len(trustedAssets) == 0 { 664 // not a trusted assets bootloader, nothing to do 665 return nil, nil 666 } 667 668 cache := newTrustedAssetsCache(dirs.SnapBootAssetsDir) 669 for _, trustedAsset := range trustedAssets { 670 assetName := filepath.Base(trustedAsset) 671 672 // find the hash of the file on disk 673 assetHash, err := cache.fileHash(filepath.Join(root, trustedAsset)) 674 if err != nil && !os.IsNotExist(err) { 675 return nil, fmt.Errorf("cannot calculate the digest of existing trusted asset: %v", err) 676 } 677 if assetHash == "" { 678 // no trusted asset on disk, but we booted nonetheless, 679 // at least log something 680 logger.Noticef("system booted without %v bootloader trusted asset %q", whichBootloader, trustedAsset) 681 // given that asset names cannot be reused, clear the 682 // boot assets map for the current bootloader 683 delete(*trustedAssetsMap, assetName) 684 continue 685 } 686 687 // this is what we booted with 688 bootedWith := []string{assetHash} 689 // one of these was expected during boot 690 hashList := (*trustedAssetsMap)[assetName] 691 692 assetFound := false 693 // find out if anything needs to be dropped 694 for _, hash := range hashList { 695 if hash == assetHash { 696 assetFound = true 697 continue 698 } 699 if !isAssetHashTrackedInMap(otherTrustedAssetsMap, assetName, hash) { 700 // asset can be dropped 701 drop = append(drop, &trackedAsset{ 702 blName: bl.Name(), 703 name: assetName, 704 hash: hash, 705 }) 706 } 707 } 708 709 if !assetFound { 710 // unexpected, we have booted with an asset whose hash 711 // is not listed among the ones we expect 712 713 // TODO:UC20: try to restore the asset from cache 714 return nil, fmt.Errorf("system booted with unexpected %v bootloader asset %q hash %v", whichBootloader, trustedAsset, assetHash) 715 } 716 717 // update the list of what we booted with 718 (*trustedAssetsMap)[assetName] = bootedWith 719 720 } 721 return drop, nil 722 } 723 724 // observeSuccessfulBootAssets observes the state of the trusted boot assets 725 // after a successful boot. Returns a modified modeenv reflecting a new state, 726 // and a list of assets that can be dropped from the cache. 727 func observeSuccessfulBootAssets(m *Modeenv) (newM *Modeenv, drop []*trackedAsset, err error) { 728 newM, err = m.Copy() 729 if err != nil { 730 return nil, nil, err 731 } 732 733 for _, bl := range []struct { 734 root string 735 opts *bootloader.Options 736 }{ 737 { 738 // ubuntu-boot bootloader 739 root: InitramfsUbuntuBootDir, 740 opts: &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true}, 741 }, { 742 // ubuntu-seed bootloader 743 root: InitramfsUbuntuSeedDir, 744 opts: &bootloader.Options{Role: bootloader.RoleRecovery, NoSlashBoot: true}, 745 }, 746 } { 747 dropForBootloader, err := observeSuccessfulBootAssetsForBootloader(newM, bl.root, bl.opts) 748 if err != nil { 749 return nil, nil, err 750 } 751 drop = append(drop, dropForBootloader...) 752 } 753 return newM, drop, nil 754 }