Skip to content

Commit 8670909

Browse files
committed
overlord/devicestate/handlers_reprovision.go: do not remove keyring keys
1 parent 03f2a0f commit 8670909

6 files changed

Lines changed: 123 additions & 46 deletions

File tree

‎core-initrd/build-source-pkgs.sh‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ EOF
6060
if [ -n "${TEST_BUILD-}" ]; then
6161
# Use local code for test builds
6262
printf "\nreplace github.com/snapcore/snapd => ../../\n" >> go.mod
63+
echo "replace github.com/snapcore/secboot v0.0.0-20260814094831-dd95d855ad64 => github.com/valentindavid/secboot v0.0.0-20260923082132-b24039e2d038" >> go.mod
6364
fi
6465
# solve dependencies
6566
go mod tidy

‎go.mod‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,5 @@ require (
5656
golang.org/x/term v0.20.0 // indirect
5757
maze.io/x/crypto v0.0.0-20190131090603-9b94c9afe066 // indirect
5858
)
59+
60+
replace github.com/snapcore/secboot v0.0.0-20260814094831-dd95d855ad64 => github.com/valentindavid/secboot v0.0.0-20260923082132-b24039e2d038

‎go.sum‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ github.com/seccomp/libseccomp-golang v0.9.2-0.20220502024300-f57e1d55ea18 h1:A15
6161
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502024300-f57e1d55ea18/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
6262
github.com/snapcore/maze.io-x-crypto v0.0.0-20190131090603-9b94c9afe066 h1:InG0EmriMOiI4YgtQNOo+6fNxzLCYioo3Q3BCVLdMCE=
6363
github.com/snapcore/maze.io-x-crypto v0.0.0-20190131090603-9b94c9afe066/go.mod h1:VuAdaITF1MrGzxPU+8GxagM1HW2vg7QhEFEeGHbmEMU=
64-
github.com/snapcore/secboot v0.0.0-20260814094831-dd95d855ad64 h1:jZBwQI4+0/dypcKdQk9v5yi5pfKMVvXs/KG2XQlNjyI=
65-
github.com/snapcore/secboot v0.0.0-20260814094831-dd95d855ad64/go.mod h1:/J5bNHw8HkyxuUZRrk2LUzkne3zO/kEwJlm+/oB16SE=
6664
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
65+
github.com/valentindavid/secboot v0.0.0-20260923082132-b24039e2d038 h1:0w1akpvJms7A05lLdOdv5GHGul9THQgo1AJV1MWH0uY=
66+
github.com/valentindavid/secboot v0.0.0-20260923082132-b24039e2d038/go.mod h1:/J5bNHw8HkyxuUZRrk2LUzkne3zO/kEwJlm+/oB16SE=
6767
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
6868
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
6969
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

‎overlord/devicestate/handlers_reprovision.go‎

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ var (
5353
secbootDeleteContainerKey = secboot.DeleteContainerKey
5454
secbootSaveCheckResult = (*secboot.PreinstallCheckContext).SaveCheckResult
5555
secbootCheckResult = (*secboot.PreinstallCheckContext).CheckResult
56+
secbootIsKeyUsedByKeyring = secboot.IsKeyUsedByKeyring
5657

5758
keysNewProtectorKey = keys.NewProtectorKey
5859
keysCreateProtectedKey = (keys.ProtectorKey).CreateProtectedKey
@@ -87,6 +88,41 @@ func hookKeyProtectorFactoryImpl(m *DeviceManager, kernelInfo *snap.Info) (secbo
8788

8889
var hookKeyProtectorFactory = hookKeyProtectorFactoryImpl
8990

91+
func removeKeyringBackupIfUnused(devicePath, keyslot string) error {
92+
usedByKeyring, err := secbootIsKeyUsedByKeyring(devicePath, keyslot)
93+
if err != nil {
94+
return fmt.Errorf("cannot verify if key %s:%s was used to unlock disk: %v", devicePath, keyslot, err)
95+
} else if !usedByKeyring {
96+
if err := secbootDeleteContainerKey(devicePath, keyslot); err != nil {
97+
if !errors.Is(err, secboot.ErrKeyslotNameNotExist) {
98+
return err
99+
}
100+
}
101+
}
102+
return nil
103+
}
104+
105+
func safelyRemoveKey(devicePath, keyslot, backup string) error {
106+
usedByKeyring, err := secbootIsKeyUsedByKeyring(devicePath, keyslot)
107+
if err != nil {
108+
return fmt.Errorf("cannot verify if key %s:%s was used to unlock disk: %v", devicePath, keyslot, err)
109+
} else if usedByKeyring {
110+
if err := removeKeyringBackupIfUnused(devicePath, backup); err != nil {
111+
return err
112+
}
113+
if err := secbootRenameContainerKey(devicePath, keyslot, backup); err != nil {
114+
return err
115+
}
116+
} else {
117+
if err := secbootDeleteContainerKey(devicePath, keyslot); err != nil {
118+
if !errors.Is(err, secboot.ErrKeyslotNameNotExist) {
119+
return err
120+
}
121+
}
122+
}
123+
return nil
124+
}
125+
90126
func (m *DeviceManager) doReprovision(t *state.Task, _ *tomb.Tomb) error {
91127
renames := []struct {
92128
// new is the name of latest keyslot if there are 2
@@ -148,6 +184,8 @@ func (m *DeviceManager) doReprovision(t *state.Task, _ *tomb.Tomb) error {
148184
return err
149185
}
150186

187+
const backupName = "snapd-active-key-backup"
188+
151189
// revertReprovisionAttempt is called either if we detect that we have already
152190
// called reprovision but did not reach step 6. Or if we
153191
// return with error before step 6.
@@ -179,6 +217,10 @@ func (m *DeviceManager) doReprovision(t *state.Task, _ *tomb.Tomb) error {
179217
hasKeySlot[k] = true
180218
}
181219

220+
if err := removeKeyringBackupIfUnused(disk, backupName); err != nil {
221+
logger.Debugf("cannot remove keyring backup key for %s: %v", disk, err)
222+
}
223+
182224
for _, rename := range renames {
183225
if hasPlatformKeyslot[rename.old] && hasPlatformKeyslot[rename.new] {
184226
nv, err := secbootGetPCRHandleFromToken(disk, rename.new)
@@ -203,7 +245,7 @@ func (m *DeviceManager) doReprovision(t *state.Task, _ *tomb.Tomb) error {
203245
if rename.new == "default" && disk == saveDisk.DevPath() {
204246
continue
205247
}
206-
if err := secbootDeleteContainerKey(disk, rename.new); err != nil {
248+
if err := safelyRemoveKey(disk, rename.new, backupName); err != nil {
207249
logger.Debugf("cannot remove %s on %s: %v", rename.new, disk, err)
208250
}
209251
if err := secbootRenameContainerKey(disk, rename.old, rename.new); err != nil {
@@ -231,7 +273,7 @@ func (m *DeviceManager) doReprovision(t *state.Task, _ *tomb.Tomb) error {
231273
// be removed before we try to do step 1 (rename existing key slots). But those
232274
// "new" keys are wrong only if the "snapd-reprovision-default" of save matches
233275
// the protector key file.
234-
if err := secbootDeleteContainerKey(saveDisk.DevPath(), "default"); err != nil {
276+
if err := safelyRemoveKey(saveDisk.DevPath(), "default", backupName); err != nil {
235277
logger.Debugf("could not remove default on %s: %v", saveDisk.DevPath(), err)
236278
}
237279
if err := secbootRenameContainerKey(saveDisk.DevPath(), "snapd-reprovision-default", "default"); err != nil {
@@ -324,7 +366,7 @@ func (m *DeviceManager) doReprovision(t *state.Task, _ *tomb.Tomb) error {
324366
}
325367

326368
for _, rename := range renames {
327-
if err := secbootDeleteContainerKey(disk, rename.old); err != nil {
369+
if err := safelyRemoveKey(disk, rename.old, backupName); err != nil {
328370
// We do not expect it to exist, we should not fail on error.
329371
// For example due to previous run not cleaned up.
330372
// We know it is not a key that is still in use because we
@@ -500,6 +542,11 @@ func (m *DeviceManager) doReprovision(t *state.Task, _ *tomb.Tomb) error {
500542
removedNvIndices := map[uint32]bool{}
501543

502544
for _, disk := range []string{dataDisk.DevPath(), saveDisk.DevPath()} {
545+
// Since we committed the keyring, we should be able to clean up the keyring backup keys
546+
if err := removeKeyringBackupIfUnused(disk, backupName); err != nil {
547+
return err
548+
}
549+
503550
recoveryKeyNames, err := secbootListContainerRecoveryKeyNames(disk)
504551
if err != nil {
505552
return err
@@ -508,7 +555,7 @@ func (m *DeviceManager) doReprovision(t *state.Task, _ *tomb.Tomb) error {
508555
if key == "default-recovery" {
509556
continue
510557
}
511-
if err := secbootDeleteContainerKey(disk, key); err != nil {
558+
if err := safelyRemoveKey(disk, key, backupName); err != nil {
512559
return err
513560
}
514561
}
@@ -539,13 +586,13 @@ func (m *DeviceManager) doReprovision(t *state.Task, _ *tomb.Tomb) error {
539586
continue
540587
}
541588

542-
if err := secbootDeleteContainerKey(disk, key); err != nil {
589+
if err := safelyRemoveKey(disk, key, backupName); err != nil {
543590
return err
544591
}
545592
}
546593
}
547594

548-
if err := secbootDeleteContainerKey(saveDisk.DevPath(), "snapd-reprovision-default"); err != nil {
595+
if err := safelyRemoveKey(saveDisk.DevPath(), "snapd-reprovision-default", backupName); err != nil {
549596
return err
550597
}
551598

‎secboot/secboot_sb.go‎

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,17 @@ func sbNewLUKS2KeyDataReaderImpl(device, slot string) (sb.KeyDataReader, error)
5050
}
5151

5252
var (
53-
sbFindStorageContainer = sb.FindStorageContainer
54-
sbAddLUKS2ContainerUnlockKey = sb.AddLUKS2ContainerUnlockKey
55-
sbRenameLUKS2ContainerKey = sb.RenameLUKS2ContainerKey
56-
sbNewLUKS2KeyDataReader = sbNewLUKS2KeyDataReaderImpl
57-
sbSetProtectorKeys = sb_plainkey.SetProtectorKeys
58-
sbGetPrimaryKeyFromKernel = sb.GetPrimaryKeyFromKernel
59-
sbTestLUKS2ContainerKey = sb.TestLUKS2ContainerKey
60-
sbCheckPassphraseEntropy = sb.CheckPassphraseEntropy
61-
disksDevlinks = disks.Devlinks
62-
sbNewActivateContext = sb.NewActivateContext
53+
sbFindStorageContainer = sb.FindStorageContainer
54+
sbAddLUKS2ContainerUnlockKey = sb.AddLUKS2ContainerUnlockKey
55+
sbRenameLUKS2ContainerKey = sb.RenameLUKS2ContainerKey
56+
sbNewLUKS2KeyDataReader = sbNewLUKS2KeyDataReaderImpl
57+
sbSetProtectorKeys = sb_plainkey.SetProtectorKeys
58+
sbGetPrimaryKeyFromKernel = sb.GetPrimaryKeyFromKernel
59+
sbTestLUKS2ContainerKey = sb.TestLUKS2ContainerKey
60+
sbTestLUKS2ContainerKeyForKeyslot = sb.TestLUKS2ContainerKeyForKeyslot
61+
sbCheckPassphraseEntropy = sb.CheckPassphraseEntropy
62+
disksDevlinks = disks.Devlinks
63+
sbNewActivateContext = sb.NewActivateContext
6364

6465
sbKeyDataChangePassphrase = (*sb.KeyData).ChangePassphrase
6566
sbKeyDataChangePIN = (*sb.KeyData).ChangePIN
@@ -74,6 +75,8 @@ var (
7475
sbWithPassphraseTries = sb.WithPassphraseTries
7576
sbWithPINTries = sb.WithPINTries
7677
sbWithAuthRequestorUserVisibleName = sb.WithAuthRequestorUserVisibleName
78+
79+
ErrKeyslotNameNotExist = sb.ErrKeyslotNameNotExist
7780
)
7881

7982
func init() {
@@ -1107,3 +1110,54 @@ func validatePINImpl(pin string) error {
11071110
_, err := secboot.ParsePIN(pin)
11081111
return err
11091112
}
1113+
1114+
func findUnlockKey(devicePath string) ([]byte, error) {
1115+
const remove = false
1116+
key, err := sbGetDiskUnlockKeyFromKernel(keyringPrefix, devicePath, remove)
1117+
if err == nil {
1118+
return key, nil
1119+
}
1120+
if !errors.Is(err, sb.ErrKernelKeyNotFound) {
1121+
return nil, err
1122+
}
1123+
1124+
// Old kernels will use "by-partuuid" symlinks. So let's
1125+
// look at all the symlinks of the device.
1126+
devlinks, errDevlinks := disksDevlinks(devicePath)
1127+
if errDevlinks != nil {
1128+
return nil, err
1129+
}
1130+
var errDevlink error
1131+
for _, devlink := range devlinks {
1132+
if !strings.HasPrefix(devlink, "/dev/disk/by-partuuid/") {
1133+
continue
1134+
}
1135+
key, errDevlink = sbGetDiskUnlockKeyFromKernel(keyringPrefix, devlink, remove)
1136+
if errDevlink == nil {
1137+
return key, nil
1138+
}
1139+
}
1140+
return nil, err
1141+
}
1142+
1143+
func IsKeyUsedByKeyring(devicePath string, name string) (bool, error) {
1144+
unlockKey, err := findUnlockKey(devicePath)
1145+
if err != nil {
1146+
if errors.Is(err, sb.ErrKernelKeyNotFound) {
1147+
return false, nil
1148+
} else {
1149+
return false, err
1150+
}
1151+
}
1152+
1153+
isUsed, err := sbTestLUKS2ContainerKeyForKeyslot(devicePath, name, unlockKey)
1154+
if err != nil {
1155+
if errors.Is(err, sb.ErrKeyslotNameNotExist) {
1156+
return false, nil
1157+
} else {
1158+
return false, err
1159+
}
1160+
}
1161+
1162+
return isUsed, err
1163+
}

‎tests/nested/manual/hybrid-fde-reprovision/task.yaml‎

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -164,34 +164,7 @@ execute: |
164164
reprovision_change_3="$(gojq --raw-output '.change' <reprovision-3.resp)"
165165
retry -n 100 --wait 5 sh -c "remote.exec sudo snap changes | MATCH '^${reprovision_change_3}\s+(Done|Undone|Error)'"
166166
167-
if remote.exec sudo snap changes | NOMATCH "^${reprovision_change_3}\s+Done"; then
168-
# TODO: for now if we got in a broken state where we unlocked some disks with new keys, then we will not be able to
169-
# finish reprovision. However after a attempt and a reboot, we should be able to fix that.
170-
# Verify we are in such a case
171-
remote.exec sudo cat /run/snapd/snap-bootstrap/unlocked.json >unlocked-post-last-reprovision.json
172-
test "$(gojq -r '."ubuntu-data"."unlock-key"' <unlocked-post-last-reprovision.json)" = run
173-
174-
boot_id="$(tests.nested boot-id)"
175-
tests.nested vm stop
176-
tests.nested vm start
177-
remote.wait-for reboot "${boot_id}"
178-
179-
remote.wait-for snap-command
180-
181-
wait_for_auto_repair_state autorepair-just-reboot.json not-attempted
182-
api_get_v2_system_info_storage_encrypted | gojq -c '.result.recommendations' | MATCH 'require-reprovision'
183-
184-
remote.exec "sudo snap debug api /v2/systems?running=true 2>/dev/null" | gojq '.result["storage-encryption"].support' | MATCH "available"
185-
186-
echo '{"action": "generate-recovery-key"}' | remote.exec "sudo snap debug api -X POST -H 'Content-Type: application/json' /v2/systems" >rkey-reprovision-4.resp
187-
gojq --raw-output '.result."recovery-key"' < rkey-reprovision-4.resp > rkey-reprovision-4.out
188-
189-
echo '{"action":"reprovision"}' | remote.exec "sudo snap debug api -X POST -H 'Content-Type: application/json' /v2/systems" >reprovision-4.resp
190-
191-
reprovision_change_4="$(gojq --raw-output '.change' <reprovision-4.resp)"
192-
retry -n 100 --wait 5 sh -c "remote.exec sudo snap changes | MATCH '^${reprovision_change_4}\s+(Done|Undone|Error)'"
193-
remote.exec sudo snap changes | MATCH "^${reprovision_change_4}\s+Done"
194-
fi
167+
remote.exec sudo snap changes | MATCH "^${reprovision_change_3}\s+Done"
195168
196169
tests.nested vm set-recovery-key ""
197170

0 commit comments

Comments
 (0)