File: model.go

package info (click to toggle)
snapd 2.71-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 79,536 kB
  • sloc: ansic: 16,114; sh: 16,105; python: 9,941; makefile: 1,890; exp: 190; awk: 40; xml: 22
file content (165 lines) | stat: -rw-r--r-- 5,506 bytes parent folder | download
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()
}