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