github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/boot/bootchain_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 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_test 21 22 import ( 23 "encoding/json" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 28 . "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/boot" 31 "github.com/snapcore/snapd/boot/boottest" 32 "github.com/snapcore/snapd/bootloader" 33 "github.com/snapcore/snapd/dirs" 34 "github.com/snapcore/snapd/secboot" 35 "github.com/snapcore/snapd/testutil" 36 ) 37 38 type bootchainSuite struct { 39 testutil.BaseTest 40 41 rootDir string 42 } 43 44 var _ = Suite(&bootchainSuite{}) 45 46 func (s *bootchainSuite) SetUpTest(c *C) { 47 s.BaseTest.SetUpTest(c) 48 s.rootDir = c.MkDir() 49 s.AddCleanup(func() { dirs.SetRootDir("/") }) 50 dirs.SetRootDir(s.rootDir) 51 52 c.Assert(os.MkdirAll(filepath.Join(dirs.SnapBootAssetsDir), 0755), IsNil) 53 } 54 55 func (s *bootchainSuite) TestBootAssetLess(c *C) { 56 for _, tc := range []struct { 57 l, r *boot.BootAsset 58 exp bool 59 }{ 60 {&boot.BootAsset{Role: "recovery"}, &boot.BootAsset{Role: "run"}, true}, 61 {&boot.BootAsset{Role: "run"}, &boot.BootAsset{Role: "recovery"}, false}, 62 {&boot.BootAsset{Name: "1"}, &boot.BootAsset{Name: "11"}, true}, 63 {&boot.BootAsset{Name: "11"}, &boot.BootAsset{Name: "1"}, false}, 64 {&boot.BootAsset{Hashes: []string{"11"}}, &boot.BootAsset{Hashes: []string{"11", "11"}}, true}, 65 {&boot.BootAsset{Hashes: []string{"11"}}, &boot.BootAsset{Hashes: []string{"12"}}, true}, 66 } { 67 less := boot.BootAssetLess(tc.l, tc.r) 68 c.Check(less, Equals, tc.exp, Commentf("expected %v got %v for:\nl:%v\nr:%v", tc.exp, less, tc.l, tc.r)) 69 } 70 } 71 72 func (s *bootchainSuite) TestBootAssetsPredictable(c *C) { 73 // by role 74 ba := boot.BootAsset{ 75 Role: bootloader.RoleRunMode, Name: "list", Hashes: []string{"b", "a"}, 76 } 77 pred := boot.ToPredictableBootAsset(&ba) 78 c.Check(pred, DeepEquals, &boot.BootAsset{ 79 Role: bootloader.RoleRunMode, Name: "list", Hashes: []string{"a", "b"}, 80 }) 81 // original structure is not changed 82 c.Check(ba, DeepEquals, boot.BootAsset{ 83 Role: bootloader.RoleRunMode, Name: "list", Hashes: []string{"b", "a"}, 84 }) 85 86 // try to make a predictable struct predictable once more 87 predAgain := boot.ToPredictableBootAsset(pred) 88 c.Check(predAgain, DeepEquals, pred) 89 90 baNil := boot.ToPredictableBootAsset(nil) 91 c.Check(baNil, IsNil) 92 } 93 94 func (s *bootchainSuite) TestBootChainMarshalOnlyAssets(c *C) { 95 pbNil := boot.ToPredictableBootChain(nil) 96 c.Check(pbNil, IsNil) 97 98 bc := &boot.BootChain{ 99 AssetChain: []boot.BootAsset{ 100 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"b"}}, 101 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"e", "d"}}, 102 {Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"d", "c"}}, 103 {Role: bootloader.RoleRunMode, Name: "1oader", Hashes: []string{"e", "d"}}, 104 {Role: bootloader.RoleRunMode, Name: "0oader", Hashes: []string{"z", "x"}}, 105 }, 106 } 107 108 predictableBc := boot.ToPredictableBootChain(bc) 109 110 c.Check(predictableBc, DeepEquals, &boot.BootChain{ 111 // assets not reordered 112 AssetChain: []boot.BootAsset{ 113 // hash lists are sorted 114 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"b"}}, 115 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"d", "e"}}, 116 {Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"c", "d"}}, 117 {Role: bootloader.RoleRunMode, Name: "1oader", Hashes: []string{"d", "e"}}, 118 {Role: bootloader.RoleRunMode, Name: "0oader", Hashes: []string{"x", "z"}}, 119 }, 120 }) 121 122 // already predictable, but try again 123 alreadySortedBc := boot.ToPredictableBootChain(predictableBc) 124 c.Check(alreadySortedBc, DeepEquals, predictableBc) 125 126 // boot chain with 2 identical assets 127 bcIdenticalAssets := &boot.BootChain{ 128 AssetChain: []boot.BootAsset{ 129 {Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"z"}}, 130 {Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"z"}}, 131 }, 132 } 133 sortedBcIdentical := boot.ToPredictableBootChain(bcIdenticalAssets) 134 c.Check(sortedBcIdentical, DeepEquals, bcIdenticalAssets) 135 } 136 137 func (s *bootchainSuite) TestBootChainMarshalFull(c *C) { 138 bc := &boot.BootChain{ 139 BrandID: "mybrand", 140 Model: "foo", 141 Grade: "dangerous", 142 ModelSignKeyID: "my-key-id", 143 // asset chain does not get sorted when marshaling 144 AssetChain: []boot.BootAsset{ 145 // hash list will get sorted 146 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"b", "a"}}, 147 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"d"}}, 148 {Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"c", "d"}}, 149 }, 150 Kernel: "pc-kernel", 151 KernelRevision: "1234", 152 KernelCmdlines: []string{`foo=bar baz=0x123`, `a=1`}, 153 } 154 155 uc20model := boottest.MakeMockUC20Model() 156 bc.SetModelAssertion(uc20model) 157 kernelBootFile := bootloader.NewBootFile("pc-kernel", "/foo", bootloader.RoleRecovery) 158 bc.SetKernelBootFile(kernelBootFile) 159 160 expectedPredictableBc := &boot.BootChain{ 161 BrandID: "mybrand", 162 Model: "foo", 163 Grade: "dangerous", 164 ModelSignKeyID: "my-key-id", 165 // assets are not reordered 166 AssetChain: []boot.BootAsset{ 167 // hash lists are sorted 168 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"a", "b"}}, 169 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"d"}}, 170 {Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"c", "d"}}, 171 }, 172 Kernel: "pc-kernel", 173 KernelRevision: "1234", 174 KernelCmdlines: []string{`a=1`, `foo=bar baz=0x123`}, 175 } 176 // those can't be set directly, but are copied as well 177 expectedPredictableBc.SetModelAssertion(uc20model) 178 expectedPredictableBc.SetKernelBootFile(kernelBootFile) 179 180 predictableBc := boot.ToPredictableBootChain(bc) 181 c.Check(predictableBc, DeepEquals, expectedPredictableBc) 182 183 d, err := json.Marshal(predictableBc) 184 c.Assert(err, IsNil) 185 c.Check(string(d), Equals, `{"brand-id":"mybrand","model":"foo","grade":"dangerous","model-sign-key-id":"my-key-id","asset-chain":[{"role":"recovery","name":"shim","hashes":["a","b"]},{"role":"recovery","name":"loader","hashes":["d"]},{"role":"run-mode","name":"loader","hashes":["c","d"]}],"kernel":"pc-kernel","kernel-revision":"1234","kernel-cmdlines":["a=1","foo=bar baz=0x123"]}`) 186 expectedOriginal := &boot.BootChain{ 187 BrandID: "mybrand", 188 Model: "foo", 189 Grade: "dangerous", 190 ModelSignKeyID: "my-key-id", 191 AssetChain: []boot.BootAsset{ 192 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"b", "a"}}, 193 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"d"}}, 194 {Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"c", "d"}}, 195 }, 196 Kernel: "pc-kernel", 197 KernelRevision: "1234", 198 KernelCmdlines: []string{`foo=bar baz=0x123`, `a=1`}, 199 } 200 expectedOriginal.SetModelAssertion(uc20model) 201 expectedOriginal.SetKernelBootFile(kernelBootFile) 202 // original structure has not been modified 203 c.Check(bc, DeepEquals, expectedOriginal) 204 } 205 206 func (s *bootchainSuite) TestPredictableBootChainsEqualForReseal(c *C) { 207 var pbNil boot.PredictableBootChains 208 209 c.Check(boot.PredictableBootChainsEqualForReseal(pbNil, pbNil), Equals, boot.BootChainEquivalent) 210 211 bcJustOne := []boot.BootChain{ 212 { 213 BrandID: "mybrand", 214 Model: "foo", 215 Grade: "dangerous", 216 ModelSignKeyID: "my-key-id", 217 AssetChain: []boot.BootAsset{ 218 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"b", "a"}}, 219 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"d"}}, 220 {Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"c", "d"}}, 221 }, 222 Kernel: "pc-kernel-other", 223 KernelRevision: "1234", 224 KernelCmdlines: []string{`foo`}, 225 }, 226 } 227 pbJustOne := boot.ToPredictableBootChains(bcJustOne) 228 // equal with self 229 c.Check(boot.PredictableBootChainsEqualForReseal(pbJustOne, pbJustOne), Equals, boot.BootChainEquivalent) 230 231 // equal with nil? 232 c.Check(boot.PredictableBootChainsEqualForReseal(pbJustOne, pbNil), Equals, boot.BootChainDifferent) 233 234 bcMoreAssets := []boot.BootChain{ 235 { 236 BrandID: "mybrand", 237 Model: "foo", 238 Grade: "dangerous", 239 ModelSignKeyID: "my-key-id", 240 AssetChain: []boot.BootAsset{ 241 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"a", "b"}}, 242 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"d"}}, 243 }, 244 Kernel: "pc-kernel-recovery", 245 KernelRevision: "1234", 246 KernelCmdlines: []string{`foo`}, 247 }, { 248 BrandID: "mybrand", 249 Model: "foo", 250 Grade: "dangerous", 251 ModelSignKeyID: "my-key-id", 252 AssetChain: []boot.BootAsset{ 253 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"a", "b"}}, 254 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"d"}}, 255 {Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"c", "d"}}, 256 }, 257 Kernel: "pc-kernel-other", 258 KernelRevision: "1234", 259 KernelCmdlines: []string{`foo`}, 260 }, 261 } 262 263 pbMoreAssets := boot.ToPredictableBootChains(bcMoreAssets) 264 265 c.Check(boot.PredictableBootChainsEqualForReseal(pbMoreAssets, pbJustOne), Equals, boot.BootChainDifferent) 266 // with self 267 c.Check(boot.PredictableBootChainsEqualForReseal(pbMoreAssets, pbMoreAssets), Equals, boot.BootChainEquivalent) 268 // chains composed of respective elements are not equal 269 c.Check(boot.PredictableBootChainsEqualForReseal( 270 []boot.BootChain{pbMoreAssets[0]}, 271 []boot.BootChain{pbMoreAssets[1]}), 272 Equals, boot.BootChainDifferent) 273 274 // unrevisioned/unasserted kernels 275 bcUnrevOne := []boot.BootChain{pbJustOne[0]} 276 bcUnrevOne[0].KernelRevision = "" 277 pbUnrevOne := boot.ToPredictableBootChains(bcUnrevOne) 278 // soundness 279 c.Check(boot.PredictableBootChainsEqualForReseal(pbJustOne, pbJustOne), Equals, boot.BootChainEquivalent) 280 // never equal even with self because of unrevisioned 281 c.Check(boot.PredictableBootChainsEqualForReseal(pbJustOne, pbUnrevOne), Equals, boot.BootChainDifferent) 282 c.Check(boot.PredictableBootChainsEqualForReseal(pbUnrevOne, pbUnrevOne), Equals, boot.BootChainUnrevisioned) 283 284 bcUnrevMoreAssets := []boot.BootChain{pbMoreAssets[0], pbMoreAssets[1]} 285 bcUnrevMoreAssets[1].KernelRevision = "" 286 pbUnrevMoreAssets := boot.ToPredictableBootChains(bcUnrevMoreAssets) 287 // never equal even with self because of unrevisioned 288 c.Check(boot.PredictableBootChainsEqualForReseal(pbUnrevMoreAssets, pbMoreAssets), Equals, boot.BootChainDifferent) 289 c.Check(boot.PredictableBootChainsEqualForReseal(pbUnrevMoreAssets, pbUnrevMoreAssets), Equals, boot.BootChainUnrevisioned) 290 } 291 292 func (s *bootchainSuite) TestPredictableBootChainsFullMarshal(c *C) { 293 // chains will be sorted 294 chains := []boot.BootChain{ 295 { 296 BrandID: "mybrand", 297 Model: "foo", 298 Grade: "signed", 299 ModelSignKeyID: "my-key-id", 300 AssetChain: []boot.BootAsset{ 301 // hashes will be sorted 302 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"x", "y"}}, 303 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}}, 304 {Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"z", "x"}}, 305 }, 306 Kernel: "pc-kernel-other", 307 KernelRevision: "2345", 308 KernelCmdlines: []string{`snapd_recovery_mode=run foo`}, 309 }, { 310 BrandID: "mybrand", 311 Model: "foo", 312 Grade: "dangerous", 313 ModelSignKeyID: "my-key-id", 314 AssetChain: []boot.BootAsset{ 315 // hashes will be sorted 316 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"y", "x"}}, 317 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}}, 318 {Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"b", "a"}}, 319 }, 320 Kernel: "pc-kernel-other", 321 KernelRevision: "1234", 322 KernelCmdlines: []string{`snapd_recovery_mode=run foo`}, 323 }, { 324 // recovery system 325 BrandID: "mybrand", 326 Model: "foo", 327 Grade: "dangerous", 328 ModelSignKeyID: "my-key-id", 329 AssetChain: []boot.BootAsset{ 330 // hashes will be sorted 331 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"y", "x"}}, 332 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}}, 333 }, 334 Kernel: "pc-kernel-other", 335 KernelRevision: "12", 336 KernelCmdlines: []string{ 337 // will be sorted 338 `snapd_recovery_mode=recover snapd_recovery_system=23 foo`, 339 `snapd_recovery_mode=recover snapd_recovery_system=12 foo`, 340 }, 341 }, 342 } 343 344 predictableChains := boot.ToPredictableBootChains(chains) 345 d, err := json.Marshal(predictableChains) 346 c.Assert(err, IsNil) 347 348 var data []map[string]interface{} 349 err = json.Unmarshal(d, &data) 350 c.Assert(err, IsNil) 351 c.Check(data, DeepEquals, []map[string]interface{}{ 352 { 353 "model": "foo", 354 "brand-id": "mybrand", 355 "grade": "dangerous", 356 "model-sign-key-id": "my-key-id", 357 "kernel": "pc-kernel-other", 358 "kernel-revision": "12", 359 "kernel-cmdlines": []interface{}{ 360 `snapd_recovery_mode=recover snapd_recovery_system=12 foo`, 361 `snapd_recovery_mode=recover snapd_recovery_system=23 foo`, 362 }, 363 "asset-chain": []interface{}{ 364 map[string]interface{}{"role": "recovery", "name": "shim", "hashes": []interface{}{"x", "y"}}, 365 map[string]interface{}{"role": "recovery", "name": "loader", "hashes": []interface{}{"c", "d"}}, 366 }, 367 }, { 368 "model": "foo", 369 "brand-id": "mybrand", 370 "grade": "dangerous", 371 "model-sign-key-id": "my-key-id", 372 "kernel": "pc-kernel-other", 373 "kernel-revision": "1234", 374 "kernel-cmdlines": []interface{}{"snapd_recovery_mode=run foo"}, 375 "asset-chain": []interface{}{ 376 map[string]interface{}{"role": "recovery", "name": "shim", "hashes": []interface{}{"x", "y"}}, 377 map[string]interface{}{"role": "recovery", "name": "loader", "hashes": []interface{}{"c", "d"}}, 378 map[string]interface{}{"role": "run-mode", "name": "loader", "hashes": []interface{}{"a", "b"}}, 379 }, 380 }, { 381 "model": "foo", 382 "brand-id": "mybrand", 383 "grade": "signed", 384 "model-sign-key-id": "my-key-id", 385 "kernel": "pc-kernel-other", 386 "kernel-revision": "2345", 387 "kernel-cmdlines": []interface{}{"snapd_recovery_mode=run foo"}, 388 "asset-chain": []interface{}{ 389 map[string]interface{}{"role": "recovery", "name": "shim", "hashes": []interface{}{"x", "y"}}, 390 map[string]interface{}{"role": "recovery", "name": "loader", "hashes": []interface{}{"c", "d"}}, 391 map[string]interface{}{"role": "run-mode", "name": "loader", "hashes": []interface{}{"x", "z"}}, 392 }, 393 }, 394 }) 395 } 396 397 func (s *bootchainSuite) TestPredictableBootChainsFields(c *C) { 398 chainsNil := boot.ToPredictableBootChains(nil) 399 c.Check(chainsNil, IsNil) 400 401 justOne := []boot.BootChain{ 402 { 403 BrandID: "mybrand", 404 Model: "foo", 405 Grade: "signed", 406 ModelSignKeyID: "my-key-id", 407 Kernel: "pc-kernel-other", 408 KernelRevision: "2345", 409 KernelCmdlines: []string{`foo`}, 410 }, 411 } 412 predictableJustOne := boot.ToPredictableBootChains(justOne) 413 c.Check(predictableJustOne, DeepEquals, boot.PredictableBootChains(justOne)) 414 415 chainsGrade := []boot.BootChain{ 416 { 417 Grade: "signed", 418 }, { 419 Grade: "dangerous", 420 }, 421 } 422 c.Check(boot.ToPredictableBootChains(chainsGrade), DeepEquals, boot.PredictableBootChains{ 423 { 424 Grade: "dangerous", 425 }, { 426 Grade: "signed", 427 }, 428 }) 429 430 chainsKernel := []boot.BootChain{ 431 { 432 Grade: "dangerous", 433 Kernel: "foo", 434 }, { 435 Grade: "dangerous", 436 Kernel: "bar", 437 }, 438 } 439 c.Check(boot.ToPredictableBootChains(chainsKernel), DeepEquals, boot.PredictableBootChains{ 440 { 441 Grade: "dangerous", 442 Kernel: "bar", 443 }, { 444 Grade: "dangerous", 445 Kernel: "foo", 446 }, 447 }) 448 449 chainsKernelRevision := []boot.BootChain{ 450 { 451 Kernel: "foo", 452 KernelRevision: "9", 453 }, { 454 Kernel: "foo", 455 KernelRevision: "21", 456 }, 457 } 458 c.Check(boot.ToPredictableBootChains(chainsKernelRevision), DeepEquals, boot.PredictableBootChains{ 459 { 460 Kernel: "foo", 461 KernelRevision: "21", 462 }, { 463 Kernel: "foo", 464 KernelRevision: "9", 465 }, 466 }) 467 468 chainsCmdline := []boot.BootChain{ 469 { 470 Grade: "dangerous", 471 Kernel: "foo", 472 KernelCmdlines: []string{`panic=1`}, 473 }, { 474 Grade: "dangerous", 475 Kernel: "foo", 476 KernelCmdlines: []string{`a`}, 477 }, 478 } 479 c.Check(boot.ToPredictableBootChains(chainsCmdline), DeepEquals, boot.PredictableBootChains{ 480 { 481 Grade: "dangerous", 482 Kernel: "foo", 483 KernelCmdlines: []string{`a`}, 484 }, { 485 Grade: "dangerous", 486 Kernel: "foo", 487 KernelCmdlines: []string{`panic=1`}, 488 }, 489 }) 490 491 chainsModel := []boot.BootChain{ 492 { 493 Model: "fridge", 494 Grade: "dangerous", 495 Kernel: "foo", 496 KernelCmdlines: []string{`panic=1`}, 497 }, { 498 Model: "box", 499 Grade: "dangerous", 500 Kernel: "foo", 501 KernelCmdlines: []string{`panic=1`}, 502 }, 503 } 504 c.Check(boot.ToPredictableBootChains(chainsModel), DeepEquals, boot.PredictableBootChains{ 505 { 506 Model: "box", 507 Grade: "dangerous", 508 Kernel: "foo", 509 KernelCmdlines: []string{`panic=1`}, 510 }, { 511 Model: "fridge", 512 Grade: "dangerous", 513 Kernel: "foo", 514 KernelCmdlines: []string{`panic=1`}, 515 }, 516 }) 517 518 chainsBrand := []boot.BootChain{ 519 { 520 BrandID: "foo", 521 Model: "box", 522 Grade: "dangerous", 523 Kernel: "foo", 524 KernelCmdlines: []string{`panic=1`}, 525 }, { 526 BrandID: "acme", 527 Model: "box", 528 Grade: "dangerous", 529 Kernel: "foo", 530 KernelCmdlines: []string{`panic=1`}, 531 }, 532 } 533 c.Check(boot.ToPredictableBootChains(chainsBrand), DeepEquals, boot.PredictableBootChains{ 534 { 535 BrandID: "acme", 536 Model: "box", 537 Grade: "dangerous", 538 Kernel: "foo", 539 KernelCmdlines: []string{`panic=1`}, 540 }, { 541 BrandID: "foo", 542 Model: "box", 543 Grade: "dangerous", 544 Kernel: "foo", 545 KernelCmdlines: []string{`panic=1`}, 546 }, 547 }) 548 549 chainsKeyID := []boot.BootChain{ 550 { 551 BrandID: "foo", 552 Model: "box", 553 Grade: "dangerous", 554 Kernel: "foo", 555 KernelCmdlines: []string{`panic=1`}, 556 ModelSignKeyID: "key-2", 557 }, { 558 BrandID: "foo", 559 Model: "box", 560 Grade: "dangerous", 561 Kernel: "foo", 562 KernelCmdlines: []string{`panic=1`}, 563 ModelSignKeyID: "key-1", 564 }, 565 } 566 c.Check(boot.ToPredictableBootChains(chainsKeyID), DeepEquals, boot.PredictableBootChains{ 567 { 568 BrandID: "foo", 569 Model: "box", 570 Grade: "dangerous", 571 Kernel: "foo", 572 KernelCmdlines: []string{`panic=1`}, 573 ModelSignKeyID: "key-1", 574 }, { 575 BrandID: "foo", 576 Model: "box", 577 Grade: "dangerous", 578 Kernel: "foo", 579 KernelCmdlines: []string{`panic=1`}, 580 ModelSignKeyID: "key-2", 581 }, 582 }) 583 584 chainsAssets := []boot.BootChain{ 585 { 586 BrandID: "foo", 587 Model: "box", 588 Grade: "dangerous", 589 ModelSignKeyID: "key-1", 590 AssetChain: []boot.BootAsset{ 591 // will be sorted 592 {Hashes: []string{"b", "a"}}, 593 }, 594 Kernel: "foo", 595 KernelCmdlines: []string{`panic=1`}, 596 }, { 597 BrandID: "foo", 598 Model: "box", 599 Grade: "dangerous", 600 ModelSignKeyID: "key-1", 601 AssetChain: []boot.BootAsset{ 602 {Hashes: []string{"b"}}, 603 }, 604 Kernel: "foo", 605 KernelCmdlines: []string{`panic=1`}, 606 }, 607 } 608 c.Check(boot.ToPredictableBootChains(chainsAssets), DeepEquals, boot.PredictableBootChains{ 609 { 610 BrandID: "foo", 611 Model: "box", 612 Grade: "dangerous", 613 ModelSignKeyID: "key-1", 614 AssetChain: []boot.BootAsset{ 615 {Hashes: []string{"b"}}, 616 }, 617 Kernel: "foo", 618 KernelCmdlines: []string{`panic=1`}, 619 }, { 620 BrandID: "foo", 621 Model: "box", 622 Grade: "dangerous", 623 ModelSignKeyID: "key-1", 624 AssetChain: []boot.BootAsset{ 625 {Hashes: []string{"a", "b"}}, 626 }, 627 Kernel: "foo", 628 KernelCmdlines: []string{`panic=1`}, 629 }, 630 }) 631 632 chainsFewerAssets := []boot.BootChain{ 633 { 634 AssetChain: []boot.BootAsset{ 635 {Hashes: []string{"b", "a"}}, 636 {Hashes: []string{"c", "d"}}, 637 }, 638 }, { 639 AssetChain: []boot.BootAsset{ 640 {Hashes: []string{"b"}}, 641 }, 642 }, 643 } 644 c.Check(boot.ToPredictableBootChains(chainsFewerAssets), DeepEquals, boot.PredictableBootChains{ 645 { 646 AssetChain: []boot.BootAsset{ 647 {Hashes: []string{"b"}}, 648 }, 649 }, { 650 AssetChain: []boot.BootAsset{ 651 {Hashes: []string{"a", "b"}}, 652 {Hashes: []string{"c", "d"}}, 653 }, 654 }, 655 }) 656 657 // not confused if 2 chains are identical 658 chainsIdenticalAssets := []boot.BootChain{ 659 { 660 BrandID: "foo", 661 Model: "box", 662 ModelSignKeyID: "key-1", 663 AssetChain: []boot.BootAsset{ 664 {Name: "asset", Hashes: []string{"a", "b"}}, 665 {Name: "asset", Hashes: []string{"a", "b"}}, 666 }, 667 Grade: "dangerous", 668 Kernel: "foo", 669 KernelCmdlines: []string{`panic=1`}, 670 }, { 671 BrandID: "foo", 672 Model: "box", 673 Grade: "dangerous", 674 ModelSignKeyID: "key-1", 675 AssetChain: []boot.BootAsset{ 676 {Name: "asset", Hashes: []string{"a", "b"}}, 677 {Name: "asset", Hashes: []string{"a", "b"}}, 678 }, 679 Kernel: "foo", 680 KernelCmdlines: []string{`panic=1`}, 681 }, 682 } 683 c.Check(boot.ToPredictableBootChains(chainsIdenticalAssets), DeepEquals, boot.PredictableBootChains(chainsIdenticalAssets)) 684 } 685 686 func (s *bootchainSuite) TestPredictableBootChainsSortOrder(c *C) { 687 // check that sort order is model info, assets, kernel, kernel cmdline 688 689 chains := []boot.BootChain{ 690 { 691 Model: "b", 692 AssetChain: []boot.BootAsset{ 693 {Name: "asset", Hashes: []string{"y"}}, 694 }, 695 Kernel: "k1", 696 KernelCmdlines: []string{"cm=1"}, 697 }, 698 { 699 Model: "b", 700 AssetChain: []boot.BootAsset{ 701 {Name: "asset", Hashes: []string{"y"}}, 702 }, 703 Kernel: "k2", 704 KernelCmdlines: []string{"cm=1"}, 705 }, 706 { 707 Model: "a", 708 AssetChain: []boot.BootAsset{ 709 {Name: "asset", Hashes: []string{"y"}}, 710 }, 711 Kernel: "k1", 712 KernelCmdlines: []string{"cm=1"}, 713 }, 714 { 715 Model: "a", 716 AssetChain: []boot.BootAsset{ 717 {Name: "asset", Hashes: []string{"y"}}, 718 }, 719 Kernel: "k2", 720 KernelCmdlines: []string{"cm=1"}, 721 }, 722 { 723 Model: "b", 724 AssetChain: []boot.BootAsset{ 725 {Name: "asset", Hashes: []string{"y"}}, 726 }, 727 Kernel: "k1", 728 KernelCmdlines: []string{"cm=2"}, 729 }, 730 { 731 Model: "b", 732 AssetChain: []boot.BootAsset{ 733 {Name: "asset", Hashes: []string{"y"}}, 734 }, 735 Kernel: "k2", 736 KernelCmdlines: []string{"cm=2"}, 737 }, 738 { 739 Model: "a", 740 AssetChain: []boot.BootAsset{ 741 {Name: "asset", Hashes: []string{"y"}}, 742 }, 743 Kernel: "k1", 744 KernelCmdlines: []string{"cm=2"}, 745 }, 746 { 747 Model: "a", 748 AssetChain: []boot.BootAsset{ 749 {Name: "asset", Hashes: []string{"y"}}, 750 }, 751 Kernel: "k2", 752 KernelCmdlines: []string{"cm=2"}, 753 }, 754 { 755 Model: "b", 756 AssetChain: []boot.BootAsset{ 757 {Name: "asset", Hashes: []string{"x"}}, 758 }, 759 Kernel: "k1", 760 KernelCmdlines: []string{"cm=1"}, 761 }, 762 { 763 Model: "b", 764 AssetChain: []boot.BootAsset{ 765 {Name: "asset", Hashes: []string{"x"}}, 766 }, 767 Kernel: "k2", 768 KernelCmdlines: []string{"cm=1"}, 769 }, 770 { 771 Model: "a", 772 AssetChain: []boot.BootAsset{ 773 {Name: "asset", Hashes: []string{"x"}}, 774 }, 775 Kernel: "k1", 776 KernelCmdlines: []string{"cm=1"}, 777 }, 778 { 779 Model: "a", 780 AssetChain: []boot.BootAsset{ 781 {Name: "asset", Hashes: []string{"x"}}, 782 }, 783 Kernel: "k2", 784 KernelCmdlines: []string{"cm=1"}, 785 }, 786 { 787 Model: "b", 788 AssetChain: []boot.BootAsset{ 789 {Name: "asset", Hashes: []string{"x"}}, 790 }, 791 Kernel: "k1", 792 KernelCmdlines: []string{"cm=2"}, 793 }, 794 { 795 Model: "b", 796 AssetChain: []boot.BootAsset{ 797 {Name: "asset", Hashes: []string{"x"}}, 798 }, 799 Kernel: "k2", 800 KernelCmdlines: []string{"cm=2"}, 801 }, 802 { 803 Model: "a", 804 AssetChain: []boot.BootAsset{ 805 {Name: "asset", Hashes: []string{"x"}}, 806 }, 807 Kernel: "k1", 808 KernelCmdlines: []string{"cm=2"}, 809 }, 810 { 811 Model: "a", 812 AssetChain: []boot.BootAsset{ 813 {Name: "asset", Hashes: []string{"x"}}, 814 }, 815 Kernel: "k2", 816 KernelCmdlines: []string{"cm=2"}, 817 }, 818 { 819 Model: "a", 820 AssetChain: []boot.BootAsset{ 821 {Name: "asset", Hashes: []string{"y"}}, 822 }, 823 Kernel: "k2", 824 KernelCmdlines: []string{"cm=1", "cm=2"}, 825 }, 826 { 827 Model: "a", 828 AssetChain: []boot.BootAsset{ 829 {Name: "asset", Hashes: []string{"y"}}, 830 }, 831 Kernel: "k1", 832 KernelCmdlines: []string{"cm=1", "cm=2"}, 833 }, 834 } 835 predictable := boot.ToPredictableBootChains(chains) 836 c.Check(predictable, DeepEquals, boot.PredictableBootChains{ 837 { 838 Model: "a", 839 AssetChain: []boot.BootAsset{ 840 {Name: "asset", Hashes: []string{"x"}}, 841 }, 842 Kernel: "k1", 843 KernelCmdlines: []string{"cm=1"}, 844 }, 845 { 846 Model: "a", 847 AssetChain: []boot.BootAsset{ 848 {Name: "asset", Hashes: []string{"x"}}, 849 }, 850 Kernel: "k1", 851 KernelCmdlines: []string{"cm=2"}, 852 }, 853 { 854 Model: "a", 855 AssetChain: []boot.BootAsset{ 856 {Name: "asset", Hashes: []string{"x"}}, 857 }, 858 Kernel: "k2", 859 KernelCmdlines: []string{"cm=1"}, 860 }, 861 { 862 Model: "a", 863 AssetChain: []boot.BootAsset{ 864 {Name: "asset", Hashes: []string{"x"}}, 865 }, 866 Kernel: "k2", 867 KernelCmdlines: []string{"cm=2"}, 868 }, 869 { 870 Model: "a", 871 AssetChain: []boot.BootAsset{ 872 {Name: "asset", Hashes: []string{"y"}}, 873 }, 874 Kernel: "k1", 875 KernelCmdlines: []string{"cm=1"}, 876 }, 877 { 878 Model: "a", 879 AssetChain: []boot.BootAsset{ 880 {Name: "asset", Hashes: []string{"y"}}, 881 }, 882 Kernel: "k1", 883 KernelCmdlines: []string{"cm=2"}, 884 }, 885 { 886 Model: "a", 887 AssetChain: []boot.BootAsset{ 888 {Name: "asset", Hashes: []string{"y"}}, 889 }, 890 Kernel: "k1", 891 KernelCmdlines: []string{"cm=1", "cm=2"}, 892 }, 893 { 894 Model: "a", 895 AssetChain: []boot.BootAsset{ 896 {Name: "asset", Hashes: []string{"y"}}, 897 }, 898 Kernel: "k2", 899 KernelCmdlines: []string{"cm=1"}, 900 }, 901 { 902 Model: "a", 903 AssetChain: []boot.BootAsset{ 904 {Name: "asset", Hashes: []string{"y"}}, 905 }, 906 Kernel: "k2", 907 KernelCmdlines: []string{"cm=2"}, 908 }, 909 { 910 Model: "a", 911 AssetChain: []boot.BootAsset{ 912 {Name: "asset", Hashes: []string{"y"}}, 913 }, 914 Kernel: "k2", 915 KernelCmdlines: []string{"cm=1", "cm=2"}, 916 }, 917 { 918 Model: "b", 919 AssetChain: []boot.BootAsset{ 920 {Name: "asset", Hashes: []string{"x"}}, 921 }, 922 Kernel: "k1", 923 KernelCmdlines: []string{"cm=1"}, 924 }, 925 { 926 Model: "b", 927 AssetChain: []boot.BootAsset{ 928 {Name: "asset", Hashes: []string{"x"}}, 929 }, 930 Kernel: "k1", 931 KernelCmdlines: []string{"cm=2"}, 932 }, 933 { 934 Model: "b", 935 AssetChain: []boot.BootAsset{ 936 {Name: "asset", Hashes: []string{"x"}}, 937 }, 938 Kernel: "k2", 939 KernelCmdlines: []string{"cm=1"}, 940 }, 941 { 942 Model: "b", 943 AssetChain: []boot.BootAsset{ 944 {Name: "asset", Hashes: []string{"x"}}, 945 }, 946 Kernel: "k2", 947 KernelCmdlines: []string{"cm=2"}, 948 }, 949 { 950 Model: "b", 951 AssetChain: []boot.BootAsset{ 952 {Name: "asset", Hashes: []string{"y"}}, 953 }, 954 Kernel: "k1", 955 KernelCmdlines: []string{"cm=1"}, 956 }, 957 { 958 Model: "b", 959 AssetChain: []boot.BootAsset{ 960 {Name: "asset", Hashes: []string{"y"}}, 961 }, 962 Kernel: "k1", 963 KernelCmdlines: []string{"cm=2"}, 964 }, 965 { 966 Model: "b", 967 AssetChain: []boot.BootAsset{ 968 {Name: "asset", Hashes: []string{"y"}}, 969 }, 970 Kernel: "k2", 971 KernelCmdlines: []string{"cm=1"}, 972 }, 973 { 974 Model: "b", 975 AssetChain: []boot.BootAsset{ 976 {Name: "asset", Hashes: []string{"y"}}, 977 }, 978 Kernel: "k2", 979 KernelCmdlines: []string{"cm=2"}, 980 }, 981 }) 982 } 983 984 func printChain(c *C, chain *secboot.LoadChain, prefix string) { 985 c.Logf("%v %v", prefix, chain.BootFile) 986 for _, n := range chain.Next { 987 printChain(c, n, prefix+"-") 988 } 989 } 990 991 // cPath returns a path under boot assets cache directory 992 func cPath(p string) string { 993 return filepath.Join(dirs.SnapBootAssetsDir, p) 994 } 995 996 // nbf is bootloader.NewBootFile but shorter 997 var nbf = bootloader.NewBootFile 998 999 func (s *bootchainSuite) TestBootAssetsToLoadChainTrivialKernel(c *C) { 1000 kbl := bootloader.NewBootFile("pc-kernel", "kernel.efi", bootloader.RoleRunMode) 1001 1002 chains, err := boot.BootAssetsToLoadChains(nil, kbl, nil) 1003 c.Assert(err, IsNil) 1004 1005 c.Check(chains, DeepEquals, []*secboot.LoadChain{ 1006 secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode)), 1007 }) 1008 } 1009 1010 func (s *bootchainSuite) TestBootAssetsToLoadChainErr(c *C) { 1011 kbl := bootloader.NewBootFile("pc-kernel", "kernel.efi", bootloader.RoleRunMode) 1012 1013 assets := []boot.BootAsset{ 1014 {Name: "shim", Hashes: []string{"hash0"}, Role: bootloader.RoleRecovery}, 1015 {Name: "loader-recovery", Hashes: []string{"hash0"}, Role: bootloader.RoleRecovery}, 1016 {Name: "loader-run", Hashes: []string{"hash0"}, Role: bootloader.RoleRunMode}, 1017 } 1018 1019 blNames := map[bootloader.Role]string{ 1020 bootloader.RoleRecovery: "recovery-bl", 1021 // missing bootloader name for role "run-mode" 1022 } 1023 // fails when probing the shim asset in the cache 1024 chains, err := boot.BootAssetsToLoadChains(assets, kbl, blNames) 1025 c.Assert(err, ErrorMatches, "file .*/recovery-bl/shim-hash0 not found in boot assets cache") 1026 c.Check(chains, IsNil) 1027 // make it work now 1028 c.Assert(os.MkdirAll(filepath.Dir(cPath("recovery-bl/shim-hash0")), 0755), IsNil) 1029 c.Assert(ioutil.WriteFile(cPath("recovery-bl/shim-hash0"), nil, 0644), IsNil) 1030 1031 // nested error bubbled up 1032 chains, err = boot.BootAssetsToLoadChains(assets, kbl, blNames) 1033 c.Assert(err, ErrorMatches, "file .*/recovery-bl/loader-recovery-hash0 not found in boot assets cache") 1034 c.Check(chains, IsNil) 1035 // again, make it work 1036 c.Assert(os.MkdirAll(filepath.Dir(cPath("recovery-bl/loader-recovery-hash0")), 0755), IsNil) 1037 c.Assert(ioutil.WriteFile(cPath("recovery-bl/loader-recovery-hash0"), nil, 0644), IsNil) 1038 1039 // fails on missing bootloader name for role "run-mode" 1040 chains, err = boot.BootAssetsToLoadChains(assets, kbl, blNames) 1041 c.Assert(err, ErrorMatches, `internal error: no bootloader name for boot asset role "run-mode"`) 1042 c.Check(chains, IsNil) 1043 } 1044 1045 func (s *bootchainSuite) TestBootAssetsToLoadChainSimpleChain(c *C) { 1046 kbl := bootloader.NewBootFile("pc-kernel", "kernel.efi", bootloader.RoleRunMode) 1047 1048 assets := []boot.BootAsset{ 1049 {Name: "shim", Hashes: []string{"hash0"}, Role: bootloader.RoleRecovery}, 1050 {Name: "loader-recovery", Hashes: []string{"hash0"}, Role: bootloader.RoleRecovery}, 1051 {Name: "loader-run", Hashes: []string{"hash0"}, Role: bootloader.RoleRunMode}, 1052 } 1053 1054 // mock relevant files in cache 1055 for _, name := range []string{ 1056 "recovery-bl/shim-hash0", 1057 "recovery-bl/loader-recovery-hash0", 1058 "run-bl/loader-run-hash0", 1059 } { 1060 p := filepath.Join(dirs.SnapBootAssetsDir, name) 1061 c.Assert(os.MkdirAll(filepath.Dir(p), 0755), IsNil) 1062 c.Assert(ioutil.WriteFile(p, nil, 0644), IsNil) 1063 } 1064 1065 blNames := map[bootloader.Role]string{ 1066 bootloader.RoleRecovery: "recovery-bl", 1067 bootloader.RoleRunMode: "run-bl", 1068 } 1069 1070 chains, err := boot.BootAssetsToLoadChains(assets, kbl, blNames) 1071 c.Assert(err, IsNil) 1072 1073 c.Logf("got:") 1074 for _, ch := range chains { 1075 printChain(c, ch, "-") 1076 } 1077 1078 expected := []*secboot.LoadChain{ 1079 secboot.NewLoadChain(nbf("", cPath("recovery-bl/shim-hash0"), bootloader.RoleRecovery), 1080 secboot.NewLoadChain(nbf("", cPath("recovery-bl/loader-recovery-hash0"), bootloader.RoleRecovery), 1081 secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash0"), bootloader.RoleRunMode), 1082 secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode))))), 1083 } 1084 c.Check(chains, DeepEquals, expected) 1085 } 1086 1087 func (s *bootchainSuite) TestBootAssetsToLoadChainWithAlternativeChains(c *C) { 1088 kbl := bootloader.NewBootFile("pc-kernel", "kernel.efi", bootloader.RoleRunMode) 1089 1090 assets := []boot.BootAsset{ 1091 {Name: "shim", Hashes: []string{"hash0", "hash1"}, Role: bootloader.RoleRecovery}, 1092 {Name: "loader-recovery", Hashes: []string{"hash0", "hash1"}, Role: bootloader.RoleRecovery}, 1093 {Name: "loader-run", Hashes: []string{"hash0", "hash1"}, Role: bootloader.RoleRunMode}, 1094 } 1095 1096 // mock relevant files in cache 1097 mockAssetsCache(c, s.rootDir, "recovery-bl", []string{ 1098 "shim-hash0", 1099 "shim-hash1", 1100 "loader-recovery-hash0", 1101 "loader-recovery-hash1", 1102 }) 1103 mockAssetsCache(c, s.rootDir, "run-bl", []string{ 1104 "loader-run-hash0", 1105 "loader-run-hash1", 1106 }) 1107 1108 blNames := map[bootloader.Role]string{ 1109 bootloader.RoleRecovery: "recovery-bl", 1110 bootloader.RoleRunMode: "run-bl", 1111 } 1112 chains, err := boot.BootAssetsToLoadChains(assets, kbl, blNames) 1113 c.Assert(err, IsNil) 1114 1115 c.Logf("got:") 1116 for _, ch := range chains { 1117 printChain(c, ch, "-") 1118 } 1119 1120 expected := []*secboot.LoadChain{ 1121 secboot.NewLoadChain(nbf("", cPath("recovery-bl/shim-hash0"), bootloader.RoleRecovery), 1122 secboot.NewLoadChain(nbf("", cPath("recovery-bl/loader-recovery-hash0"), bootloader.RoleRecovery), 1123 secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash0"), bootloader.RoleRunMode), 1124 secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode))), 1125 secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash1"), bootloader.RoleRunMode), 1126 secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode)))), 1127 secboot.NewLoadChain(nbf("", cPath("recovery-bl/loader-recovery-hash1"), bootloader.RoleRecovery), 1128 secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash0"), bootloader.RoleRunMode), 1129 secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode))), 1130 secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash1"), bootloader.RoleRunMode), 1131 secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode))))), 1132 secboot.NewLoadChain(nbf("", cPath("recovery-bl/shim-hash1"), bootloader.RoleRecovery), 1133 secboot.NewLoadChain(nbf("", cPath("recovery-bl/loader-recovery-hash0"), bootloader.RoleRecovery), 1134 secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash0"), bootloader.RoleRunMode), 1135 secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode))), 1136 secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash1"), bootloader.RoleRunMode), 1137 secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode)))), 1138 secboot.NewLoadChain(nbf("", cPath("recovery-bl/loader-recovery-hash1"), bootloader.RoleRecovery), 1139 secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash0"), bootloader.RoleRunMode), 1140 secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode))), 1141 secboot.NewLoadChain(nbf("", cPath("run-bl/loader-run-hash1"), bootloader.RoleRunMode), 1142 secboot.NewLoadChain(nbf("pc-kernel", "kernel.efi", bootloader.RoleRunMode))))), 1143 } 1144 c.Check(chains, DeepEquals, expected) 1145 } 1146 1147 func (s *sealSuite) TestReadWriteBootChains(c *C) { 1148 if os.Geteuid() == 0 { 1149 c.Skip("the test cannot be run by the root user") 1150 } 1151 1152 chains := []boot.BootChain{ 1153 { 1154 BrandID: "mybrand", 1155 Model: "foo", 1156 Grade: "signed", 1157 ModelSignKeyID: "my-key-id", 1158 AssetChain: []boot.BootAsset{ 1159 // hashes will be sorted 1160 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"x", "y"}}, 1161 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}}, 1162 {Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"z", "x"}}, 1163 }, 1164 Kernel: "pc-kernel-other", 1165 KernelRevision: "2345", 1166 KernelCmdlines: []string{`snapd_recovery_mode=run foo`}, 1167 }, { 1168 BrandID: "mybrand", 1169 Model: "foo", 1170 Grade: "dangerous", 1171 ModelSignKeyID: "my-key-id", 1172 AssetChain: []boot.BootAsset{ 1173 // hashes will be sorted 1174 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"y", "x"}}, 1175 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}}, 1176 }, 1177 Kernel: "pc-kernel-recovery", 1178 KernelRevision: "1234", 1179 KernelCmdlines: []string{`snapd_recovery_mode=recover foo`}, 1180 }, 1181 } 1182 1183 pbc := boot.ToPredictableBootChains(chains) 1184 1185 rootdir := c.MkDir() 1186 1187 expected := `{"reseal-count":0,"boot-chains":[{"brand-id":"mybrand","model":"foo","grade":"dangerous","model-sign-key-id":"my-key-id","asset-chain":[{"role":"recovery","name":"shim","hashes":["x","y"]},{"role":"recovery","name":"loader","hashes":["c","d"]}],"kernel":"pc-kernel-recovery","kernel-revision":"1234","kernel-cmdlines":["snapd_recovery_mode=recover foo"]},{"brand-id":"mybrand","model":"foo","grade":"signed","model-sign-key-id":"my-key-id","asset-chain":[{"role":"recovery","name":"shim","hashes":["x","y"]},{"role":"recovery","name":"loader","hashes":["c","d"]},{"role":"run-mode","name":"loader","hashes":["x","z"]}],"kernel":"pc-kernel-other","kernel-revision":"2345","kernel-cmdlines":["snapd_recovery_mode=run foo"]}]} 1188 ` 1189 // creates a complete tree and writes a file 1190 err := boot.WriteBootChains(pbc, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0) 1191 c.Assert(err, IsNil) 1192 c.Check(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), testutil.FileEquals, expected) 1193 1194 fi, err := os.Stat(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains")) 1195 c.Assert(err, IsNil) 1196 c.Check(fi.Mode().Perm(), Equals, os.FileMode(0600)) 1197 1198 loaded, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains")) 1199 c.Assert(err, IsNil) 1200 c.Check(loaded, DeepEquals, pbc) 1201 c.Check(cnt, Equals, 0) 1202 // boot chains should be same for reseal purpose 1203 c.Check(boot.PredictableBootChainsEqualForReseal(pbc, loaded), Equals, boot.BootChainEquivalent) 1204 1205 // write them again with count > 0 1206 err = boot.WriteBootChains(pbc, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 99) 1207 c.Assert(err, IsNil) 1208 1209 _, cnt, err = boot.ReadBootChains(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains")) 1210 c.Assert(err, IsNil) 1211 c.Check(cnt, Equals, 99) 1212 1213 // make device/fde directory read only so that writing fails 1214 otherRootdir := c.MkDir() 1215 c.Assert(os.MkdirAll(dirs.SnapFDEDirUnder(otherRootdir), 0755), IsNil) 1216 c.Assert(os.Chmod(dirs.SnapFDEDirUnder(otherRootdir), 0000), IsNil) 1217 defer os.Chmod(dirs.SnapFDEDirUnder(otherRootdir), 0755) 1218 1219 err = boot.WriteBootChains(pbc, filepath.Join(dirs.SnapFDEDirUnder(otherRootdir), "boot-chains"), 0) 1220 c.Assert(err, ErrorMatches, `cannot create a temporary boot chains file: open .*/boot-chains\.[a-zA-Z0-9]+~: permission denied`) 1221 1222 // make the original file non readable 1223 c.Assert(os.Chmod(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0000), IsNil) 1224 defer os.Chmod(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0755) 1225 loaded, _, err = boot.ReadBootChains(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains")) 1226 c.Assert(err, ErrorMatches, "cannot open existing boot chains data file: open .*/boot-chains: permission denied") 1227 c.Check(loaded, IsNil) 1228 1229 // loading from a file that does not exist yields a nil boot chain 1230 // and 0 count 1231 loaded, cnt, err = boot.ReadBootChains("does-not-exist") 1232 c.Assert(err, IsNil) 1233 c.Check(loaded, IsNil) 1234 c.Check(cnt, Equals, 0) 1235 }