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