github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/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 to track trusted and 175 // control managed assets, provided the device model indicates this might be 176 // needed. Otherwise, nil and ErrObserverNotApplicable is returned. 177 func TrustedAssetsInstallObserverForModel(model *asserts.Model, gadgetDir string, useEncryption bool) (*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 // TODO:UC20: clarify use of empty rootdir when getting the lists of 186 // managed and trusted assets 187 runBl, runTrusted, runManaged, err := gadgetMaybeTrustedBootloaderAndAssets(gadgetDir, "", 188 &bootloader.Options{ 189 Role: bootloader.RoleRunMode, 190 NoSlashBoot: true, 191 }) 192 if err != nil { 193 return nil, err 194 } 195 // and the recovery bootloader, seed is mounted during install 196 seedBl, seedTrusted, _, err := gadgetMaybeTrustedBootloaderAndAssets(gadgetDir, InitramfsUbuntuSeedDir, 197 &bootloader.Options{ 198 Role: bootloader.RoleRecovery, 199 }) 200 if err != nil { 201 return nil, err 202 } 203 if !useEncryption { 204 // we do not care about trusted assets when not encrypting data 205 // partition 206 runTrusted = nil 207 seedTrusted = nil 208 } 209 hasManaged := len(runManaged) > 0 210 hasTrusted := len(runTrusted) > 0 || len(seedTrusted) > 0 211 if !hasManaged && !hasTrusted && !useEncryption { 212 // no managed assets, and no trusted assets or we are not 213 // tracking them due to no encryption to data partition 214 return nil, ErrObserverNotApplicable 215 } 216 217 return &TrustedAssetsInstallObserver{ 218 model: model, 219 cache: newTrustedAssetsCache(dirs.SnapBootAssetsDir), 220 gadgetDir: gadgetDir, 221 222 blName: runBl.Name(), 223 managedAssets: runManaged, 224 trustedAssets: runTrusted, 225 226 recoveryBlName: seedBl.Name(), 227 trustedRecoveryAssets: seedTrusted, 228 }, nil 229 } 230 231 type trackedAsset struct { 232 blName, name, hash string 233 } 234 235 func isAssetAlreadyTracked(bam bootAssetsMap, newAsset *trackedAsset) bool { 236 return isAssetHashTrackedInMap(bam, newAsset.name, newAsset.hash) 237 } 238 239 func isAssetHashTrackedInMap(bam bootAssetsMap, assetName, assetHash string) bool { 240 if bam == nil { 241 return false 242 } 243 hashes, ok := bam[assetName] 244 if !ok { 245 return false 246 } 247 return strutil.ListContains(hashes, assetHash) 248 } 249 250 // TrustedAssetsInstallObserver tracks the installation of trusted or managed 251 // boot assets. 252 type TrustedAssetsInstallObserver struct { 253 model *asserts.Model 254 gadgetDir string 255 cache *trustedAssetsCache 256 257 blName string 258 managedAssets []string 259 trustedAssets []string 260 trackedAssets bootAssetsMap 261 262 recoveryBlName string 263 trustedRecoveryAssets []string 264 trackedRecoveryAssets bootAssetsMap 265 266 dataEncryptionKey secboot.EncryptionKey 267 saveEncryptionKey secboot.EncryptionKey 268 } 269 270 // Observe observes the operation related to the content of a given gadget 271 // structure. In particular, the TrustedAssetsInstallObserver tracks writing of 272 // trusted or managed boot assets, such as the bootloader binary which is 273 // measured as part of the secure boot or the bootloader configuration. 274 // 275 // Implements gadget.ContentObserver. 276 func (o *TrustedAssetsInstallObserver) Observe(op gadget.ContentOperation, affectedStruct *gadget.LaidOutStructure, root, relativeTarget string, data *gadget.ContentChange) (gadget.ContentChangeAction, error) { 277 if affectedStruct.Role != gadget.SystemBoot { 278 // only care about system-boot 279 return gadget.ChangeApply, nil 280 } 281 282 if len(o.managedAssets) != 0 && strutil.ListContains(o.managedAssets, relativeTarget) { 283 // this asset is managed by bootloader installation 284 return gadget.ChangeIgnore, nil 285 } 286 if len(o.trustedAssets) == 0 || !strutil.ListContains(o.trustedAssets, relativeTarget) { 287 // not one of the trusted assets 288 return gadget.ChangeApply, nil 289 } 290 ta, err := o.cache.Add(data.After, o.blName, filepath.Base(relativeTarget)) 291 if err != nil { 292 return gadget.ChangeAbort, err 293 } 294 // during installation, modeenv is written out later, at this point we 295 // only care that the same file may appear multiple times in gadget 296 // structure content, so make sure we are not tracking it yet 297 if !isAssetAlreadyTracked(o.trackedAssets, ta) { 298 if o.trackedAssets == nil { 299 o.trackedAssets = bootAssetsMap{} 300 } 301 if len(o.trackedAssets[ta.name]) > 0 { 302 return gadget.ChangeAbort, fmt.Errorf("cannot reuse asset name %q", ta.name) 303 } 304 o.trackedAssets[ta.name] = append(o.trackedAssets[ta.name], ta.hash) 305 } 306 return gadget.ChangeApply, nil 307 } 308 309 // ObserveExistingTrustedRecoveryAssets observes existing trusted assets of a 310 // recovery bootloader located inside a given root directory. 311 func (o *TrustedAssetsInstallObserver) ObserveExistingTrustedRecoveryAssets(recoveryRootDir string) error { 312 if len(o.trustedRecoveryAssets) == 0 { 313 // not a trusted assets bootloader or has no trusted assets 314 return nil 315 } 316 for _, trustedAsset := range o.trustedRecoveryAssets { 317 ta, err := o.cache.Add(filepath.Join(recoveryRootDir, trustedAsset), o.recoveryBlName, filepath.Base(trustedAsset)) 318 if err != nil { 319 return err 320 } 321 if !isAssetAlreadyTracked(o.trackedRecoveryAssets, ta) { 322 if o.trackedRecoveryAssets == nil { 323 o.trackedRecoveryAssets = bootAssetsMap{} 324 } 325 if len(o.trackedRecoveryAssets[ta.name]) > 0 { 326 return fmt.Errorf("cannot reuse recovery asset name %q", ta.name) 327 } 328 o.trackedRecoveryAssets[ta.name] = append(o.trackedRecoveryAssets[ta.name], ta.hash) 329 } 330 } 331 return nil 332 } 333 334 func (o *TrustedAssetsInstallObserver) currentTrustedBootAssetsMap() bootAssetsMap { 335 return o.trackedAssets 336 } 337 338 func (o *TrustedAssetsInstallObserver) currentTrustedRecoveryBootAssetsMap() bootAssetsMap { 339 return o.trackedRecoveryAssets 340 } 341 342 func (o *TrustedAssetsInstallObserver) ChosenEncryptionKeys(key, saveKey secboot.EncryptionKey) { 343 o.dataEncryptionKey = key 344 o.saveEncryptionKey = saveKey 345 } 346 347 // TrustedAssetsUpdateObserverForModel returns a new trusted assets observer for 348 // tracking changes to the trusted boot assets and preserving managed assets, 349 // provided the device model indicates this might be needed. Otherwise, nil and 350 // ErrObserverNotApplicable is returned. 351 func TrustedAssetsUpdateObserverForModel(model *asserts.Model, gadgetDir string) (*TrustedAssetsUpdateObserver, error) { 352 if model.Grade() == asserts.ModelGradeUnset { 353 // no need to observe updates when assets are not managed 354 return nil, ErrObserverNotApplicable 355 } 356 // trusted assets need tracking only when the system is using encryption 357 // for its data partitions 358 trackTrustedAssets := false 359 _, err := sealedKeysMethod(dirs.GlobalRootDir) 360 switch { 361 case err == nil: 362 trackTrustedAssets = true 363 case err == errNoSealedKeys: 364 // nothing to do 365 case err != nil: 366 // all other errors 367 return nil, err 368 } 369 370 // see what we need to observe for the run bootloader 371 runBl, runTrusted, runManaged, err := gadgetMaybeTrustedBootloaderAndAssets(gadgetDir, InitramfsUbuntuBootDir, 372 &bootloader.Options{ 373 Role: bootloader.RoleRunMode, 374 NoSlashBoot: true, 375 }) 376 if err != nil { 377 return nil, err 378 } 379 380 // and the recovery bootloader 381 seedBl, seedTrusted, seedManaged, err := gadgetMaybeTrustedBootloaderAndAssets(gadgetDir, InitramfsUbuntuSeedDir, 382 &bootloader.Options{ 383 Role: bootloader.RoleRecovery, 384 }) 385 if err != nil { 386 return nil, err 387 } 388 389 hasManaged := len(runManaged) > 0 || len(seedManaged) > 0 390 hasTrusted := len(runTrusted) > 0 || len(seedTrusted) > 0 391 if !hasManaged { 392 // no managed assets 393 if !hasTrusted || !trackTrustedAssets { 394 // no trusted assets or we are not tracking them either 395 return nil, ErrObserverNotApplicable 396 } 397 } 398 399 obs := &TrustedAssetsUpdateObserver{ 400 cache: newTrustedAssetsCache(dirs.SnapBootAssetsDir), 401 model: model, 402 403 bootBootloader: runBl, 404 bootManagedAssets: runManaged, 405 406 seedBootloader: seedBl, 407 seedManagedAssets: seedManaged, 408 } 409 if trackTrustedAssets { 410 obs.seedTrustedAssets = seedTrusted 411 obs.bootTrustedAssets = runTrusted 412 } 413 return obs, nil 414 } 415 416 // TrustedAssetsUpdateObserver tracks the updates of trusted boot assets and 417 // attempts to reseal when needed or preserves managed boot assets. 418 type TrustedAssetsUpdateObserver struct { 419 cache *trustedAssetsCache 420 model *asserts.Model 421 422 bootBootloader bootloader.Bootloader 423 bootTrustedAssets []string 424 bootManagedAssets []string 425 changedAssets []*trackedAsset 426 427 seedBootloader bootloader.Bootloader 428 seedTrustedAssets []string 429 seedManagedAssets []string 430 seedChangedAssets []*trackedAsset 431 432 modeenv *Modeenv 433 } 434 435 func trustedAndManagedAssetsOfBootloader(bl bootloader.Bootloader) (trustedAssets, managedAssets []string, err error) { 436 tbl, ok := bl.(bootloader.TrustedAssetsBootloader) 437 if ok { 438 trustedAssets, err = tbl.TrustedAssets() 439 if err != nil { 440 return nil, nil, fmt.Errorf("cannot list %q bootloader trusted assets: %v", bl.Name(), err) 441 } 442 managedAssets = tbl.ManagedAssets() 443 } 444 return trustedAssets, managedAssets, nil 445 } 446 447 func findMaybeTrustedBootloaderAndAssets(rootDir string, opts *bootloader.Options) (foundBl bootloader.Bootloader, trustedAssets []string, err error) { 448 foundBl, err = bootloader.Find(rootDir, opts) 449 if err != nil { 450 return nil, nil, fmt.Errorf("cannot find bootloader: %v", err) 451 } 452 trustedAssets, _, err = trustedAndManagedAssetsOfBootloader(foundBl) 453 return foundBl, trustedAssets, err 454 } 455 456 func gadgetMaybeTrustedBootloaderAndAssets(gadgetDir, rootDir string, opts *bootloader.Options) (foundBl bootloader.Bootloader, trustedAssets, managedAssets []string, err error) { 457 foundBl, err = bootloader.ForGadget(gadgetDir, rootDir, opts) 458 if err != nil { 459 return nil, nil, nil, fmt.Errorf("cannot find bootloader: %v", err) 460 } 461 trustedAssets, managedAssets, err = trustedAndManagedAssetsOfBootloader(foundBl) 462 return foundBl, trustedAssets, managedAssets, err 463 } 464 465 // Observe observes the operation related to the update or rollback of the 466 // content of a given gadget structure. In particular, the 467 // TrustedAssetsUpdateObserver tracks updates of trusted boot assets such as 468 // bootloader binaries, or preserves managed assets such as boot configuration. 469 // 470 // Implements gadget.ContentUpdateObserver. 471 func (o *TrustedAssetsUpdateObserver) Observe(op gadget.ContentOperation, affectedStruct *gadget.LaidOutStructure, root, relativeTarget string, data *gadget.ContentChange) (gadget.ContentChangeAction, error) { 472 var whichBootloader bootloader.Bootloader 473 var whichTrustedAssets []string 474 var whichManagedAssets []string 475 var err error 476 var isRecovery bool 477 478 switch affectedStruct.Role { 479 case gadget.SystemBoot: 480 whichBootloader = o.bootBootloader 481 whichTrustedAssets = o.bootTrustedAssets 482 whichManagedAssets = o.bootManagedAssets 483 case gadget.SystemSeed: 484 whichBootloader = o.seedBootloader 485 whichTrustedAssets = o.seedTrustedAssets 486 whichManagedAssets = o.seedManagedAssets 487 isRecovery = true 488 default: 489 // only system-seed and system-boot are of interest 490 return gadget.ChangeApply, nil 491 } 492 // maybe an asset that we manage? 493 if len(whichManagedAssets) != 0 && strutil.ListContains(whichManagedAssets, relativeTarget) { 494 // this asset is managed directly by the bootloader, preserve it 495 if op != gadget.ContentUpdate { 496 return gadget.ChangeAbort, fmt.Errorf("internal error: managed bootloader asset change for non update operation %v", op) 497 } 498 return gadget.ChangeIgnore, nil 499 } 500 501 if len(whichTrustedAssets) == 0 { 502 // the system is not using encryption for data partitions, so 503 // we're done at this point 504 return gadget.ChangeApply, nil 505 } 506 507 // maybe an asset that is trusted in the boot process? 508 if !strutil.ListContains(whichTrustedAssets, relativeTarget) { 509 // not one of the trusted assets 510 return gadget.ChangeApply, nil 511 } 512 if o.modeenv == nil { 513 // we've hit a trusted asset, so a modeenv is needed now too 514 o.modeenv, err = ReadModeenv("") 515 if err != nil { 516 return gadget.ChangeAbort, fmt.Errorf("cannot load modeenv: %v", err) 517 } 518 } 519 switch op { 520 case gadget.ContentUpdate: 521 return o.observeUpdate(whichBootloader, isRecovery, root, relativeTarget, data) 522 case gadget.ContentRollback: 523 return o.observeRollback(whichBootloader, isRecovery, root, relativeTarget, data) 524 default: 525 // we only care about update and rollback actions 526 return gadget.ChangeApply, nil 527 } 528 } 529 530 func (o *TrustedAssetsUpdateObserver) observeUpdate(bl bootloader.Bootloader, recovery bool, root, relativeTarget string, change *gadget.ContentChange) (gadget.ContentChangeAction, error) { 531 modeenvBefore, err := o.modeenv.Copy() 532 if err != nil { 533 return gadget.ChangeAbort, fmt.Errorf("cannot copy modeenv: %v", err) 534 } 535 536 // we may be running after a mid-update reboot, where a successful boot 537 // would have trimmed the tracked assets hash lists to contain only the 538 // asset we booted with 539 540 var taBefore *trackedAsset 541 if change.Before != "" { 542 // make sure that the original copy is present in the cache if 543 // it existed 544 taBefore, err = o.cache.Add(change.Before, bl.Name(), filepath.Base(relativeTarget)) 545 if err != nil { 546 return gadget.ChangeAbort, err 547 } 548 } 549 550 ta, err := o.cache.Add(change.After, bl.Name(), filepath.Base(relativeTarget)) 551 if err != nil { 552 return gadget.ChangeAbort, err 553 } 554 555 trustedAssets := &o.modeenv.CurrentTrustedBootAssets 556 changedAssets := &o.changedAssets 557 if recovery { 558 trustedAssets = &o.modeenv.CurrentTrustedRecoveryBootAssets 559 changedAssets = &o.seedChangedAssets 560 } 561 // keep track of the change for cancellation purpose 562 *changedAssets = append(*changedAssets, ta) 563 564 if *trustedAssets == nil { 565 *trustedAssets = bootAssetsMap{} 566 } 567 568 if taBefore != nil && !isAssetAlreadyTracked(*trustedAssets, taBefore) { 569 // make sure that the boot asset that was was in the filesystem 570 // before the update, is properly tracked until either a 571 // successful boot or the update is canceled 572 // the original asset hash is listed first 573 (*trustedAssets)[taBefore.name] = append([]string{taBefore.hash}, (*trustedAssets)[taBefore.name]...) 574 } 575 576 if !isAssetAlreadyTracked(*trustedAssets, ta) { 577 if len((*trustedAssets)[ta.name]) > 1 { 578 // we expect at most 2 different blobs for a given asset 579 // name, the current one and one that will be installed 580 // during an update; more entries indicates that the 581 // same asset name is used multiple times with different 582 // content 583 return gadget.ChangeAbort, fmt.Errorf("cannot reuse asset name %q", ta.name) 584 } 585 (*trustedAssets)[ta.name] = append((*trustedAssets)[ta.name], ta.hash) 586 } 587 588 if o.modeenv.deepEqual(modeenvBefore) { 589 return gadget.ChangeApply, nil 590 } 591 if err := o.modeenv.Write(); err != nil { 592 return gadget.ChangeAbort, fmt.Errorf("cannot write modeeenv: %v", err) 593 } 594 return gadget.ChangeApply, nil 595 } 596 597 func (o *TrustedAssetsUpdateObserver) observeRollback(bl bootloader.Bootloader, recovery bool, root, relativeTarget string, data *gadget.ContentChange) (gadget.ContentChangeAction, error) { 598 trustedAssets := &o.modeenv.CurrentTrustedBootAssets 599 otherTrustedAssets := o.modeenv.CurrentTrustedRecoveryBootAssets 600 if recovery { 601 trustedAssets = &o.modeenv.CurrentTrustedRecoveryBootAssets 602 otherTrustedAssets = o.modeenv.CurrentTrustedBootAssets 603 } 604 605 assetName := filepath.Base(relativeTarget) 606 hashList, ok := (*trustedAssets)[assetName] 607 if !ok || len(hashList) == 0 { 608 // asset not tracked in modeenv 609 return gadget.ChangeApply, nil 610 } 611 612 // new assets are appended to the list 613 expectedOldHash := hashList[0] 614 // sanity check, make sure that the current file is what we expect 615 newlyAdded := false 616 ondiskHash, err := o.cache.fileHash(filepath.Join(root, relativeTarget)) 617 if err != nil { 618 // file may not exist if it was added by the update, that's ok 619 if !os.IsNotExist(err) { 620 return gadget.ChangeAbort, fmt.Errorf("cannot calculate the digest of current asset: %v", err) 621 } 622 newlyAdded = true 623 if len(hashList) > 1 { 624 // we have more than 1 hash of the asset, so we expected 625 // a previous revision to be restored, but got nothing 626 // instead 627 return gadget.ChangeAbort, fmt.Errorf("tracked asset %q is unexpectedly missing from disk", 628 assetName) 629 } 630 } else { 631 if ondiskHash != expectedOldHash { 632 // this is unexpected, a different file exists on disk? 633 return gadget.ChangeAbort, fmt.Errorf("unexpected content of existing asset %q", relativeTarget) 634 } 635 } 636 637 newHash := "" 638 if len(hashList) == 1 { 639 if newlyAdded { 640 newHash = hashList[0] 641 } 642 } else { 643 newHash = hashList[1] 644 } 645 if newHash != "" && !isAssetHashTrackedInMap(otherTrustedAssets, assetName, newHash) { 646 // asset revision is not used used elsewhere, we can remove it from the cache 647 if err := o.cache.Remove(bl.Name(), assetName, newHash); err != nil { 648 // XXX: should this be a log instead? 649 return gadget.ChangeAbort, fmt.Errorf("cannot remove unused boot asset %v:%v: %v", assetName, newHash, err) 650 } 651 } 652 653 // update modeenv content 654 if !newlyAdded { 655 (*trustedAssets)[assetName] = hashList[:1] 656 } else { 657 delete(*trustedAssets, assetName) 658 } 659 660 if err := o.modeenv.Write(); err != nil { 661 return gadget.ChangeAbort, fmt.Errorf("cannot write modeeenv: %v", err) 662 } 663 664 return gadget.ChangeApply, nil 665 } 666 667 // BeforeWrite is called when the update process has been staged for execution. 668 func (o *TrustedAssetsUpdateObserver) BeforeWrite() error { 669 if o.modeenv == nil { 670 // modeenv wasn't even loaded yet, meaning none of the trusted 671 // boot assets was updated 672 return nil 673 } 674 const expectReseal = true 675 if err := resealKeyToModeenv(dirs.GlobalRootDir, o.modeenv, expectReseal); err != nil { 676 return err 677 } 678 return nil 679 } 680 681 func (o *TrustedAssetsUpdateObserver) canceledUpdate(recovery bool) { 682 trustedAssets := &o.modeenv.CurrentTrustedBootAssets 683 otherTrustedAssets := o.modeenv.CurrentTrustedRecoveryBootAssets 684 changedAssets := o.changedAssets 685 if recovery { 686 trustedAssets = &o.modeenv.CurrentTrustedRecoveryBootAssets 687 otherTrustedAssets = o.modeenv.CurrentTrustedBootAssets 688 changedAssets = o.seedChangedAssets 689 } 690 691 if len(*trustedAssets) == 0 { 692 return 693 } 694 695 for _, changed := range changedAssets { 696 hashList, ok := (*trustedAssets)[changed.name] 697 if !ok || len(hashList) == 0 { 698 // not tracked already, nothing to do 699 continue 700 } 701 if len(hashList) == 1 { 702 currentAssetHash := hashList[0] 703 if currentAssetHash != changed.hash { 704 // assets list has already been trimmed, nothing 705 // to do 706 continue 707 } else { 708 // asset was newly added 709 delete(*trustedAssets, changed.name) 710 } 711 } else { 712 // asset updates were appended to the list 713 (*trustedAssets)[changed.name] = hashList[:1] 714 } 715 if !isAssetHashTrackedInMap(otherTrustedAssets, changed.name, changed.hash) { 716 // asset revision is not used used elsewhere, we can remove it from the cache 717 if err := o.cache.Remove(changed.blName, changed.name, changed.hash); err != nil { 718 logger.Noticef("cannot remove unused boot asset %v:%v: %v", changed.name, changed.hash, err) 719 } 720 } 721 } 722 } 723 724 // Canceled is called when the update has been canceled, or if changes 725 // were written and the update has been reverted. 726 func (o *TrustedAssetsUpdateObserver) Canceled() error { 727 if o.modeenv == nil { 728 // modeenv wasn't even loaded yet, meaning none of the boot 729 // assets was updated 730 return nil 731 } 732 for _, isRecovery := range []bool{false, true} { 733 o.canceledUpdate(isRecovery) 734 } 735 736 if err := o.modeenv.Write(); err != nil { 737 return fmt.Errorf("cannot write modeeenv: %v", err) 738 } 739 740 const expectReseal = true 741 if err := resealKeyToModeenv(dirs.GlobalRootDir, o.modeenv, expectReseal); err != nil { 742 return fmt.Errorf("while canceling gadget update: %v", err) 743 } 744 return nil 745 } 746 747 func observeSuccessfulBootAssetsForBootloader(m *Modeenv, root string, opts *bootloader.Options) (drop []*trackedAsset, err error) { 748 trustedAssetsMap := &m.CurrentTrustedBootAssets 749 otherTrustedAssetsMap := m.CurrentTrustedRecoveryBootAssets 750 whichBootloader := "run mode" 751 if opts != nil && opts.Role == bootloader.RoleRecovery { 752 trustedAssetsMap = &m.CurrentTrustedRecoveryBootAssets 753 otherTrustedAssetsMap = m.CurrentTrustedBootAssets 754 whichBootloader = "recovery" 755 } 756 757 if len(*trustedAssetsMap) == 0 { 758 // bootloader may have trusted assets, but we are not tracking 759 // any for the boot process 760 return nil, nil 761 } 762 763 // let's find the bootloader first 764 bl, trustedAssets, err := findMaybeTrustedBootloaderAndAssets(root, opts) 765 if err != nil { 766 return nil, err 767 } 768 if len(trustedAssets) == 0 { 769 // not a trusted assets bootloader, nothing to do 770 return nil, nil 771 } 772 773 cache := newTrustedAssetsCache(dirs.SnapBootAssetsDir) 774 for _, trustedAsset := range trustedAssets { 775 assetName := filepath.Base(trustedAsset) 776 777 // find the hash of the file on disk 778 assetHash, err := cache.fileHash(filepath.Join(root, trustedAsset)) 779 if err != nil && !os.IsNotExist(err) { 780 return nil, fmt.Errorf("cannot calculate the digest of existing trusted asset: %v", err) 781 } 782 if assetHash == "" { 783 // no trusted asset on disk, but we booted nonetheless, 784 // at least log something 785 logger.Noticef("system booted without %v bootloader trusted asset %q", whichBootloader, trustedAsset) 786 // given that asset names cannot be reused, clear the 787 // boot assets map for the current bootloader 788 delete(*trustedAssetsMap, assetName) 789 continue 790 } 791 792 // this is what we booted with 793 bootedWith := []string{assetHash} 794 // one of these was expected during boot 795 hashList := (*trustedAssetsMap)[assetName] 796 797 assetFound := false 798 // find out if anything needs to be dropped 799 for _, hash := range hashList { 800 if hash == assetHash { 801 assetFound = true 802 continue 803 } 804 if !isAssetHashTrackedInMap(otherTrustedAssetsMap, assetName, hash) { 805 // asset can be dropped 806 drop = append(drop, &trackedAsset{ 807 blName: bl.Name(), 808 name: assetName, 809 hash: hash, 810 }) 811 } 812 } 813 814 if !assetFound { 815 // unexpected, we have booted with an asset whose hash 816 // is not listed among the ones we expect 817 818 // TODO:UC20: try to restore the asset from cache 819 return nil, fmt.Errorf("system booted with unexpected %v bootloader asset %q hash %v", whichBootloader, trustedAsset, assetHash) 820 } 821 822 // update the list of what we booted with 823 (*trustedAssetsMap)[assetName] = bootedWith 824 825 } 826 return drop, nil 827 } 828 829 // observeSuccessfulBootAssets observes the state of the trusted boot assets 830 // after a successful boot. Returns a modified modeenv reflecting a new state, 831 // and a list of assets that can be dropped from the cache. 832 func observeSuccessfulBootAssets(m *Modeenv) (newM *Modeenv, drop []*trackedAsset, err error) { 833 // TODO:UC20 only care about run mode for now 834 if m.Mode != "run" { 835 return m, nil, nil 836 } 837 838 newM, err = m.Copy() 839 if err != nil { 840 return nil, nil, err 841 } 842 843 for _, bl := range []struct { 844 root string 845 opts *bootloader.Options 846 }{ 847 { 848 // ubuntu-boot bootloader 849 root: InitramfsUbuntuBootDir, 850 opts: &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true}, 851 }, { 852 // ubuntu-seed bootloader 853 root: InitramfsUbuntuSeedDir, 854 opts: &bootloader.Options{Role: bootloader.RoleRecovery, NoSlashBoot: true}, 855 }, 856 } { 857 dropForBootloader, err := observeSuccessfulBootAssetsForBootloader(newM, bl.root, bl.opts) 858 if err != nil { 859 return nil, nil, err 860 } 861 drop = append(drop, dropForBootloader...) 862 } 863 return newM, drop, nil 864 }