1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
|
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2021 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package boot
import (
"fmt"
"path/filepath"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
)
// DeviceChange handles a change of the underlying device. Specifically it can
// be used during remodel when a new device is associated with a new model. The
// encryption keys will be resealed for both models. The device model file which
// is measured during boot will be updated. The recovery systems that belong to
// the old model will no longer be usable.
func DeviceChange(from snap.Device, to snap.Device, unlocker Unlocker) error {
if !to.HasModeenv() {
// nothing useful happens on a non-UC20 system here
return nil
}
modeenvLock()
defer modeenvUnlock()
m, err := loadModeenv()
if err != nil {
return err
}
newModel := to.Model()
oldModel := from.Model()
modified := false
if modelUniqueID(m.TryModelForSealing()) != modelUniqueID(newModel) {
// we either haven't been here yet, or a reboot occurred after
// try model was cleared and modeenv was rewritten
m.setTryModel(newModel)
modified = true
}
if modelUniqueID(m.ModelForSealing()) != modelUniqueID(oldModel) {
// a modeenv with new model was already written, restore
// the 'expected' original state, the model file on disk
// will match one of the models
m.setModel(oldModel)
modified = true
}
if modified {
if err := m.Write(); err != nil {
return err
}
}
// reseal with both models now, such that we'd still be able to boot
// even if there is a reboot before the device/model file is updated, or
// before the final reseal with one model
// Because changing models affect FDE hooks keys, we need to
// make sure those are also resealed.
resealOpts := ResealKeyToModeenvOptions{ExpectReseal: true, IgnoreFDEHooks: false}
if err := resealKeyToModeenv(dirs.GlobalRootDir, m, resealOpts, unlocker); err != nil {
// best effort clear the modeenv's try model
m.clearTryModel()
if mErr := m.Write(); mErr != nil {
return fmt.Errorf("%v (restoring modeenv failed: %v)", err, mErr)
}
return err
}
// update the device model file in boot (we may be overwriting the same
// model file if we reached this place before a reboot has occurred)
if err := writeModelToUbuntuBoot(to.Model()); err != nil {
err = fmt.Errorf("cannot write new model file: %v", err)
// the file has not been modified, so just clear the try model
m.clearTryModel()
if mErr := m.Write(); mErr != nil {
return fmt.Errorf("%v (restoring modeenv failed: %v)", err, mErr)
}
return err
}
// now we can update the model to the new one
m.setModel(newModel)
// and clear the try model
m.clearTryModel()
if err := m.Write(); err != nil {
// modeenv has not been written and still contains both the old
// and a new model, but the model file has been modified,
// restore the original model file
if restoreErr := writeModelToUbuntuBoot(from.Model()); restoreErr != nil {
return fmt.Errorf("%v (restoring model failed: %v)", err, restoreErr)
}
// however writing modeenv failed, so trying to clear the model
// and write it again could be pointless, let the failure
// percolate up the stack
return err
}
// Inject fault during resealing
osutil.MaybeInjectFault("remodel-boot-assets")
// past a successful reseal, the old recovery systems become unusable and will
// not be able to access the data anymore
if err := resealKeyToModeenv(dirs.GlobalRootDir, m, resealOpts, unlocker); err != nil {
// resealing failed, but modeenv and the file have been modified
// first restore the modeenv in case we reboot, such that if the
// post reboot code reseals, it will allow both models (in case
// even more reboots occur)
m.setModel(from.Model())
m.setTryModel(newModel)
if mErr := m.Write(); mErr != nil {
return fmt.Errorf("%v (writing modeenv failed: %v)", err, mErr)
}
// restore the original model file (we have resealed for both
// models previously)
if restoreErr := writeModelToUbuntuBoot(from.Model()); restoreErr != nil {
return fmt.Errorf("%v (restoring model failed: %v)", err, restoreErr)
}
// drop the tried model
m.clearTryModel()
if mErr := m.Write(); mErr != nil {
return fmt.Errorf("%v (restoring modeenv failed: %v)", err, mErr)
}
// resealing failed, so no point in trying it again
return err
}
return nil
}
var writeModelToUbuntuBoot = writeModelToUbuntuBootImpl
func writeModelToUbuntuBootImpl(model *asserts.Model) error {
modelPath := filepath.Join(InitramfsUbuntuBootDir, "device/model")
f, err := osutil.NewAtomicFile(modelPath, 0644, 0, osutil.NoChown, osutil.NoChown)
if err != nil {
return err
}
defer f.Cancel()
if err := asserts.NewEncoder(f).Encode(model); err != nil {
return err
}
return f.Commit()
}
|