github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/boot/model.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 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 "fmt" 24 "path/filepath" 25 26 "github.com/snapcore/snapd/asserts" 27 "github.com/snapcore/snapd/dirs" 28 "github.com/snapcore/snapd/osutil" 29 ) 30 31 // DeviceChange handles a change of the underlying device. Specifically it can 32 // be used during remodel when a new device is associated with a new model. The 33 // encryption keys will be resealed for both models. The device model file which 34 // is measured during boot will be updated. The recovery systems that belong to 35 // the old model will no longer be usable. 36 func DeviceChange(from Device, to Device) error { 37 if !to.HasModeenv() { 38 // nothing useful happens on a non-UC20 system here 39 return nil 40 } 41 42 m, err := loadModeenv() 43 if err != nil { 44 return err 45 } 46 47 newModel := to.Model() 48 oldModel := from.Model() 49 modified := false 50 if modelUniqueID(m.TryModelForSealing()) != modelUniqueID(newModel) { 51 // we either haven't been here yet, or a reboot occurred after 52 // try model was cleared and modeenv was rewritten 53 m.setTryModel(newModel) 54 modified = true 55 } 56 if modelUniqueID(m.ModelForSealing()) != modelUniqueID(oldModel) { 57 // a modeenv with new model was already written, restore 58 // the 'expected' original state, the model file on disk 59 // will match one of the models 60 m.setModel(oldModel) 61 modified = true 62 } 63 if modified { 64 if err := m.Write(); err != nil { 65 return err 66 } 67 } 68 69 // reseal with both models now, such that we'd still be able to boot 70 // even if there is a reboot before the device/model file is updated, or 71 // before the final reseal with one model 72 const expectReseal = true 73 if err := resealKeyToModeenv(dirs.GlobalRootDir, m, expectReseal); err != nil { 74 // best effort clear the modeenv's try model 75 m.clearTryModel() 76 if mErr := m.Write(); mErr != nil { 77 return fmt.Errorf("%v (restoring modeenv failed: %v)", err, mErr) 78 } 79 return err 80 } 81 82 // update the device model file in boot (we may be overwriting the same 83 // model file if we reached this place before a reboot has occurred) 84 if err := writeModelToUbuntuBoot(to.Model()); err != nil { 85 err = fmt.Errorf("cannot write new model file: %v", err) 86 // the file has not been modified, so just clear the try model 87 m.clearTryModel() 88 if mErr := m.Write(); mErr != nil { 89 return fmt.Errorf("%v (restoring modeenv failed: %v)", err, mErr) 90 } 91 return err 92 } 93 94 // now we can update the model to the new one 95 m.setModel(newModel) 96 // and clear the try model 97 m.clearTryModel() 98 99 if err := m.Write(); err != nil { 100 // modeenv has not been written and still contains both the old 101 // and a new model, but the model file has been modified, 102 // restore the original model file 103 if restoreErr := writeModelToUbuntuBoot(from.Model()); restoreErr != nil { 104 return fmt.Errorf("%v (restoring model failed: %v)", err, restoreErr) 105 } 106 // however writing modeenv failed, so trying to clear the model 107 // and write it again could be pointless, let the failure 108 // percolate up the stack 109 return err 110 } 111 112 // past a successful reseal, the old recovery systems become unusable and will 113 // not be able to access the data anymore 114 if err := resealKeyToModeenv(dirs.GlobalRootDir, m, expectReseal); err != nil { 115 // resealing failed, but modeenv and the file have been modified 116 117 // first restore the modeenv in case we reboot, such that if the 118 // post reboot code reseals, it will allow both models (in case 119 // even more reboots occur) 120 m.setModel(from.Model()) 121 m.setTryModel(newModel) 122 if mErr := m.Write(); mErr != nil { 123 return fmt.Errorf("%v (writing modeenv failed: %v)", err, mErr) 124 } 125 126 // restore the original model file (we have resealed for both 127 // models previously) 128 if restoreErr := writeModelToUbuntuBoot(from.Model()); restoreErr != nil { 129 return fmt.Errorf("%v (restoring model failed: %v)", err, restoreErr) 130 } 131 132 // drop the tried model 133 m.clearTryModel() 134 if mErr := m.Write(); mErr != nil { 135 return fmt.Errorf("%v (restoring modeenv failed: %v)", err, mErr) 136 } 137 138 // resealing failed, so no point in trying it again 139 return err 140 } 141 return nil 142 } 143 144 var writeModelToUbuntuBoot = writeModelToUbuntuBootImpl 145 146 func writeModelToUbuntuBootImpl(model *asserts.Model) error { 147 modelPath := filepath.Join(InitramfsUbuntuBootDir, "device/model") 148 f, err := osutil.NewAtomicFile(modelPath, 0644, 0, osutil.NoChown, osutil.NoChown) 149 if err != nil { 150 return err 151 } 152 defer f.Cancel() 153 if err := asserts.NewEncoder(f).Encode(model); err != nil { 154 return err 155 } 156 return f.Commit() 157 }