-
Notifications
You must be signed in to change notification settings - Fork 601
/
Copy pathmodel.go
157 lines (139 loc) · 5.03 KB
/
model.go
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
// -*- 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"
)
// 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 Device, to Device) error {
if !to.HasModeenv() {
// nothing useful happens on a non-UC20 system here
return nil
}
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
const expectReseal = true
if err := resealKeyToModeenv(dirs.GlobalRootDir, m, expectReseal); 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
}
// 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, expectReseal); 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()
}