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