github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/cgroups/devices/devices_emulator_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 /* 3 * Copyright (C) 2020 Aleksa Sarai <cyphar@cyphar.com> 4 * Copyright (C) 2020 SUSE LLC 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package devices 20 21 import ( 22 "bufio" 23 "bytes" 24 "reflect" 25 "strings" 26 "testing" 27 28 "github.com/opencontainers/runc/libcontainer/devices" 29 ) 30 31 func TestDeviceEmulatorLoad(t *testing.T) { 32 tests := []struct { 33 name, list string 34 expected *emulator 35 }{ 36 { 37 name: "BlacklistMode", 38 list: `a *:* rwm`, 39 expected: &emulator{ 40 defaultAllow: true, 41 }, 42 }, 43 { 44 name: "WhitelistBasic", 45 list: `c 4:2 rw`, 46 expected: &emulator{ 47 defaultAllow: false, 48 rules: deviceRules{ 49 { 50 node: devices.CharDevice, 51 major: 4, 52 minor: 2, 53 }: devices.Permissions("rw"), 54 }, 55 }, 56 }, 57 { 58 name: "WhitelistWildcard", 59 list: `b 0:* m`, 60 expected: &emulator{ 61 defaultAllow: false, 62 rules: deviceRules{ 63 { 64 node: devices.BlockDevice, 65 major: 0, 66 minor: devices.Wildcard, 67 }: devices.Permissions("m"), 68 }, 69 }, 70 }, 71 { 72 name: "WhitelistDuplicate", 73 list: `c *:* rwm 74 c 1:1 r`, 75 expected: &emulator{ 76 defaultAllow: false, 77 rules: deviceRules{ 78 { 79 node: devices.CharDevice, 80 major: devices.Wildcard, 81 minor: devices.Wildcard, 82 }: devices.Permissions("rwm"), 83 // To match the kernel, we allow redundant rules. 84 { 85 node: devices.CharDevice, 86 major: 1, 87 minor: 1, 88 }: devices.Permissions("r"), 89 }, 90 }, 91 }, 92 { 93 name: "WhitelistComplicated", 94 list: `c *:* m 95 b *:* m 96 c 1:3 rwm 97 c 1:5 rwm 98 c 1:7 rwm 99 c 1:8 rwm 100 c 1:9 rwm 101 c 5:0 rwm 102 c 5:2 rwm 103 c 136:* rwm 104 c 10:200 rwm`, 105 expected: &emulator{ 106 defaultAllow: false, 107 rules: deviceRules{ 108 { 109 node: devices.CharDevice, 110 major: devices.Wildcard, 111 minor: devices.Wildcard, 112 }: devices.Permissions("m"), 113 { 114 node: devices.BlockDevice, 115 major: devices.Wildcard, 116 minor: devices.Wildcard, 117 }: devices.Permissions("m"), 118 { 119 node: devices.CharDevice, 120 major: 1, 121 minor: 3, 122 }: devices.Permissions("rwm"), 123 { 124 node: devices.CharDevice, 125 major: 1, 126 minor: 5, 127 }: devices.Permissions("rwm"), 128 { 129 node: devices.CharDevice, 130 major: 1, 131 minor: 7, 132 }: devices.Permissions("rwm"), 133 { 134 node: devices.CharDevice, 135 major: 1, 136 minor: 8, 137 }: devices.Permissions("rwm"), 138 { 139 node: devices.CharDevice, 140 major: 1, 141 minor: 9, 142 }: devices.Permissions("rwm"), 143 { 144 node: devices.CharDevice, 145 major: 5, 146 minor: 0, 147 }: devices.Permissions("rwm"), 148 { 149 node: devices.CharDevice, 150 major: 5, 151 minor: 2, 152 }: devices.Permissions("rwm"), 153 { 154 node: devices.CharDevice, 155 major: 136, 156 minor: devices.Wildcard, 157 }: devices.Permissions("rwm"), 158 { 159 node: devices.CharDevice, 160 major: 10, 161 minor: 200, 162 }: devices.Permissions("rwm"), 163 }, 164 }, 165 }, 166 // Some invalid lists. 167 { 168 name: "InvalidFieldNumber", 169 list: `b 1:0`, 170 expected: nil, 171 }, 172 { 173 name: "InvalidDeviceType", 174 list: `p *:* rwm`, 175 expected: nil, 176 }, 177 { 178 name: "InvalidMajorNumber1", 179 list: `p -1:3 rwm`, 180 expected: nil, 181 }, 182 { 183 name: "InvalidMajorNumber2", 184 list: `c foo:27 rwm`, 185 expected: nil, 186 }, 187 { 188 name: "InvalidMinorNumber1", 189 list: `b 1:-4 rwm`, 190 expected: nil, 191 }, 192 { 193 name: "InvalidMinorNumber2", 194 list: `b 1:foo rwm`, 195 expected: nil, 196 }, 197 { 198 name: "InvalidPermissions", 199 list: `b 1:7 rwk`, 200 expected: nil, 201 }, 202 } 203 204 for _, test := range tests { 205 test := test // capture range variable 206 t.Run(test.name, func(t *testing.T) { 207 list := bytes.NewBufferString(test.list) 208 emu, err := emulatorFromList(list) 209 if err != nil && test.expected != nil { 210 t.Fatalf("unexpected failure when creating emulator: %v", err) 211 } else if err == nil && test.expected == nil { 212 t.Fatalf("unexpected success when creating emulator: %#v", emu) 213 } 214 215 if !reflect.DeepEqual(emu, test.expected) { 216 t.Errorf("final emulator state mismatch: %#v != %#v", emu, test.expected) 217 } 218 }) 219 } 220 } 221 222 func testDeviceEmulatorApply(t *testing.T, baseDefaultAllow bool) { 223 tests := []struct { 224 name string 225 rule devices.Rule 226 base, expected *emulator 227 }{ 228 // Switch between default modes. 229 { 230 name: "SwitchToOtherMode", 231 rule: devices.Rule{ 232 Type: devices.WildcardDevice, 233 Major: devices.Wildcard, 234 Minor: devices.Wildcard, 235 Permissions: devices.Permissions("rwm"), 236 Allow: !baseDefaultAllow, 237 }, 238 base: &emulator{ 239 defaultAllow: baseDefaultAllow, 240 rules: deviceRules{ 241 { 242 node: devices.CharDevice, 243 major: devices.Wildcard, 244 minor: devices.Wildcard, 245 }: devices.Permissions("rwm"), 246 { 247 node: devices.CharDevice, 248 major: 1, 249 minor: 1, 250 }: devices.Permissions("r"), 251 }, 252 }, 253 expected: &emulator{ 254 defaultAllow: !baseDefaultAllow, 255 rules: nil, 256 }, 257 }, 258 { 259 name: "SwitchToSameModeNoop", 260 rule: devices.Rule{ 261 Type: devices.WildcardDevice, 262 Major: devices.Wildcard, 263 Minor: devices.Wildcard, 264 Permissions: devices.Permissions("rwm"), 265 Allow: baseDefaultAllow, 266 }, 267 base: &emulator{ 268 defaultAllow: baseDefaultAllow, 269 rules: nil, 270 }, 271 expected: &emulator{ 272 defaultAllow: baseDefaultAllow, 273 rules: nil, 274 }, 275 }, 276 { 277 name: "SwitchToSameMode", 278 rule: devices.Rule{ 279 Type: devices.WildcardDevice, 280 Major: devices.Wildcard, 281 Minor: devices.Wildcard, 282 Permissions: devices.Permissions("rwm"), 283 Allow: baseDefaultAllow, 284 }, 285 base: &emulator{ 286 defaultAllow: baseDefaultAllow, 287 rules: deviceRules{ 288 { 289 node: devices.CharDevice, 290 major: devices.Wildcard, 291 minor: devices.Wildcard, 292 }: devices.Permissions("rwm"), 293 { 294 node: devices.CharDevice, 295 major: 1, 296 minor: 1, 297 }: devices.Permissions("r"), 298 }, 299 }, 300 expected: &emulator{ 301 defaultAllow: baseDefaultAllow, 302 rules: nil, 303 }, 304 }, 305 // Rule addition logic. 306 { 307 name: "RuleAdditionBasic", 308 rule: devices.Rule{ 309 Type: devices.CharDevice, 310 Major: 42, 311 Minor: 1337, 312 Permissions: devices.Permissions("rm"), 313 Allow: !baseDefaultAllow, 314 }, 315 base: &emulator{ 316 defaultAllow: baseDefaultAllow, 317 rules: deviceRules{ 318 { 319 node: devices.CharDevice, 320 major: 2, 321 minor: 1, 322 }: devices.Permissions("rwm"), 323 { 324 node: devices.BlockDevice, 325 major: 1, 326 minor: 5, 327 }: devices.Permissions("r"), 328 }, 329 }, 330 expected: &emulator{ 331 defaultAllow: baseDefaultAllow, 332 rules: deviceRules{ 333 { 334 node: devices.CharDevice, 335 major: 2, 336 minor: 1, 337 }: devices.Permissions("rwm"), 338 { 339 node: devices.BlockDevice, 340 major: 1, 341 minor: 5, 342 }: devices.Permissions("r"), 343 { 344 node: devices.CharDevice, 345 major: 42, 346 minor: 1337, 347 }: devices.Permissions("rm"), 348 }, 349 }, 350 }, 351 { 352 name: "RuleAdditionBasicDuplicate", 353 rule: devices.Rule{ 354 Type: devices.CharDevice, 355 Major: 42, 356 Minor: 1337, 357 Permissions: devices.Permissions("rm"), 358 Allow: !baseDefaultAllow, 359 }, 360 base: &emulator{ 361 defaultAllow: baseDefaultAllow, 362 rules: deviceRules{ 363 { 364 node: devices.CharDevice, 365 major: 42, 366 minor: devices.Wildcard, 367 }: devices.Permissions("rwm"), 368 }, 369 }, 370 expected: &emulator{ 371 defaultAllow: baseDefaultAllow, 372 rules: deviceRules{ 373 { 374 node: devices.CharDevice, 375 major: 42, 376 minor: devices.Wildcard, 377 }: devices.Permissions("rwm"), 378 // To match the kernel, we allow redundant rules. 379 { 380 node: devices.CharDevice, 381 major: 42, 382 minor: 1337, 383 }: devices.Permissions("rm"), 384 }, 385 }, 386 }, 387 { 388 name: "RuleAdditionBasicDuplicateNoop", 389 rule: devices.Rule{ 390 Type: devices.CharDevice, 391 Major: 42, 392 Minor: 1337, 393 Permissions: devices.Permissions("rm"), 394 Allow: !baseDefaultAllow, 395 }, 396 base: &emulator{ 397 defaultAllow: baseDefaultAllow, 398 rules: deviceRules{ 399 { 400 node: devices.CharDevice, 401 major: 42, 402 minor: 1337, 403 }: devices.Permissions("rm"), 404 }, 405 }, 406 expected: &emulator{ 407 defaultAllow: baseDefaultAllow, 408 rules: deviceRules{ 409 { 410 node: devices.CharDevice, 411 major: 42, 412 minor: 1337, 413 }: devices.Permissions("rm"), 414 }, 415 }, 416 }, 417 { 418 name: "RuleAdditionMerge", 419 rule: devices.Rule{ 420 Type: devices.BlockDevice, 421 Major: 5, 422 Minor: 12, 423 Permissions: devices.Permissions("rm"), 424 Allow: !baseDefaultAllow, 425 }, 426 base: &emulator{ 427 defaultAllow: baseDefaultAllow, 428 rules: deviceRules{ 429 { 430 node: devices.CharDevice, 431 major: 2, 432 minor: 1, 433 }: devices.Permissions("rwm"), 434 { 435 node: devices.BlockDevice, 436 major: 5, 437 minor: 12, 438 }: devices.Permissions("rw"), 439 }, 440 }, 441 expected: &emulator{ 442 defaultAllow: baseDefaultAllow, 443 rules: deviceRules{ 444 { 445 node: devices.CharDevice, 446 major: 2, 447 minor: 1, 448 }: devices.Permissions("rwm"), 449 { 450 node: devices.BlockDevice, 451 major: 5, 452 minor: 12, 453 }: devices.Permissions("rwm"), 454 }, 455 }, 456 }, 457 { 458 name: "RuleAdditionMergeWildcard", 459 rule: devices.Rule{ 460 Type: devices.BlockDevice, 461 Major: 5, 462 Minor: devices.Wildcard, 463 Permissions: devices.Permissions("rm"), 464 Allow: !baseDefaultAllow, 465 }, 466 base: &emulator{ 467 defaultAllow: baseDefaultAllow, 468 rules: deviceRules{ 469 { 470 node: devices.CharDevice, 471 major: 2, 472 minor: 1, 473 }: devices.Permissions("rwm"), 474 { 475 node: devices.BlockDevice, 476 major: 5, 477 minor: devices.Wildcard, 478 }: devices.Permissions("rw"), 479 }, 480 }, 481 expected: &emulator{ 482 defaultAllow: baseDefaultAllow, 483 rules: deviceRules{ 484 { 485 node: devices.CharDevice, 486 major: 2, 487 minor: 1, 488 }: devices.Permissions("rwm"), 489 { 490 node: devices.BlockDevice, 491 major: 5, 492 minor: devices.Wildcard, 493 }: devices.Permissions("rwm"), 494 }, 495 }, 496 }, 497 { 498 name: "RuleAdditionMergeNoop", 499 rule: devices.Rule{ 500 Type: devices.BlockDevice, 501 Major: 5, 502 Minor: 12, 503 Permissions: devices.Permissions("r"), 504 Allow: !baseDefaultAllow, 505 }, 506 base: &emulator{ 507 defaultAllow: baseDefaultAllow, 508 rules: deviceRules{ 509 { 510 node: devices.CharDevice, 511 major: 2, 512 minor: 1, 513 }: devices.Permissions("rwm"), 514 { 515 node: devices.BlockDevice, 516 major: 5, 517 minor: 12, 518 }: devices.Permissions("rw"), 519 }, 520 }, 521 expected: &emulator{ 522 defaultAllow: baseDefaultAllow, 523 rules: deviceRules{ 524 { 525 node: devices.CharDevice, 526 major: 2, 527 minor: 1, 528 }: devices.Permissions("rwm"), 529 { 530 node: devices.BlockDevice, 531 major: 5, 532 minor: 12, 533 }: devices.Permissions("rw"), 534 }, 535 }, 536 }, 537 // Rule removal logic. 538 { 539 name: "RuleRemovalBasic", 540 rule: devices.Rule{ 541 Type: devices.CharDevice, 542 Major: 42, 543 Minor: 1337, 544 Permissions: devices.Permissions("rm"), 545 Allow: baseDefaultAllow, 546 }, 547 base: &emulator{ 548 defaultAllow: baseDefaultAllow, 549 rules: deviceRules{ 550 { 551 node: devices.CharDevice, 552 major: 42, 553 minor: 1337, 554 }: devices.Permissions("rm"), 555 { 556 node: devices.BlockDevice, 557 major: 1, 558 minor: 5, 559 }: devices.Permissions("r"), 560 }, 561 }, 562 expected: &emulator{ 563 defaultAllow: baseDefaultAllow, 564 rules: deviceRules{ 565 { 566 node: devices.BlockDevice, 567 major: 1, 568 minor: 5, 569 }: devices.Permissions("r"), 570 }, 571 }, 572 }, 573 { 574 name: "RuleRemovalNonexistent", 575 rule: devices.Rule{ 576 Type: devices.CharDevice, 577 Major: 4, 578 Minor: 1, 579 Permissions: devices.Permissions("rw"), 580 Allow: baseDefaultAllow, 581 }, 582 base: &emulator{ 583 defaultAllow: baseDefaultAllow, 584 rules: deviceRules{ 585 { 586 node: devices.BlockDevice, 587 major: 1, 588 minor: 5, 589 }: devices.Permissions("r"), 590 }, 591 }, 592 expected: &emulator{ 593 defaultAllow: baseDefaultAllow, 594 rules: deviceRules{ 595 { 596 node: devices.BlockDevice, 597 major: 1, 598 minor: 5, 599 }: devices.Permissions("r"), 600 }, 601 }, 602 }, 603 { 604 name: "RuleRemovalFull", 605 rule: devices.Rule{ 606 Type: devices.CharDevice, 607 Major: 42, 608 Minor: 1337, 609 Permissions: devices.Permissions("rw"), 610 Allow: baseDefaultAllow, 611 }, 612 base: &emulator{ 613 defaultAllow: baseDefaultAllow, 614 rules: deviceRules{ 615 { 616 node: devices.CharDevice, 617 major: 42, 618 minor: 1337, 619 }: devices.Permissions("w"), 620 { 621 node: devices.BlockDevice, 622 major: 1, 623 minor: 5, 624 }: devices.Permissions("r"), 625 }, 626 }, 627 expected: &emulator{ 628 defaultAllow: baseDefaultAllow, 629 rules: deviceRules{ 630 { 631 node: devices.BlockDevice, 632 major: 1, 633 minor: 5, 634 }: devices.Permissions("r"), 635 }, 636 }, 637 }, 638 { 639 name: "RuleRemovalPartial", 640 rule: devices.Rule{ 641 Type: devices.CharDevice, 642 Major: 42, 643 Minor: 1337, 644 Permissions: devices.Permissions("r"), 645 Allow: baseDefaultAllow, 646 }, 647 base: &emulator{ 648 defaultAllow: baseDefaultAllow, 649 rules: deviceRules{ 650 { 651 node: devices.CharDevice, 652 major: 42, 653 minor: 1337, 654 }: devices.Permissions("rm"), 655 { 656 node: devices.BlockDevice, 657 major: 1, 658 minor: 5, 659 }: devices.Permissions("r"), 660 }, 661 }, 662 expected: &emulator{ 663 defaultAllow: baseDefaultAllow, 664 rules: deviceRules{ 665 { 666 node: devices.CharDevice, 667 major: 42, 668 minor: 1337, 669 }: devices.Permissions("m"), 670 { 671 node: devices.BlockDevice, 672 major: 1, 673 minor: 5, 674 }: devices.Permissions("r"), 675 }, 676 }, 677 }, 678 // Check our non-canonical behaviour when it comes to try to "punch 679 // out" holes in a wildcard rule. 680 { 681 name: "RuleRemovalWildcardPunchoutImpossible", 682 rule: devices.Rule{ 683 Type: devices.CharDevice, 684 Major: 42, 685 Minor: 1337, 686 Permissions: devices.Permissions("r"), 687 Allow: baseDefaultAllow, 688 }, 689 base: &emulator{ 690 defaultAllow: baseDefaultAllow, 691 rules: deviceRules{ 692 { 693 node: devices.CharDevice, 694 major: 42, 695 minor: devices.Wildcard, 696 }: devices.Permissions("rm"), 697 { 698 node: devices.CharDevice, 699 major: 42, 700 minor: 1337, 701 }: devices.Permissions("r"), 702 }, 703 }, 704 expected: nil, 705 }, 706 { 707 name: "RuleRemovalWildcardPunchoutPossible", 708 rule: devices.Rule{ 709 Type: devices.CharDevice, 710 Major: 42, 711 Minor: 1337, 712 Permissions: devices.Permissions("r"), 713 Allow: baseDefaultAllow, 714 }, 715 base: &emulator{ 716 defaultAllow: baseDefaultAllow, 717 rules: deviceRules{ 718 { 719 node: devices.CharDevice, 720 major: 42, 721 minor: devices.Wildcard, 722 }: devices.Permissions("wm"), 723 { 724 node: devices.CharDevice, 725 major: 42, 726 minor: 1337, 727 }: devices.Permissions("r"), 728 }, 729 }, 730 expected: &emulator{ 731 defaultAllow: baseDefaultAllow, 732 rules: deviceRules{ 733 { 734 node: devices.CharDevice, 735 major: 42, 736 minor: devices.Wildcard, 737 }: devices.Permissions("wm"), 738 }, 739 }, 740 }, 741 } 742 743 for _, test := range tests { 744 test := test 745 t.Run(test.name, func(t *testing.T) { 746 err := test.base.Apply(test.rule) 747 if err != nil && test.expected != nil { 748 t.Fatalf("unexpected failure when applying apply rule: %v", err) 749 } else if err == nil && test.expected == nil { 750 t.Fatalf("unexpected success when applying apply rule: %#v", test.base) 751 } 752 753 if test.expected != nil && !reflect.DeepEqual(test.base, test.expected) { 754 t.Errorf("final emulator state mismatch: %#v != %#v", test.base, test.expected) 755 } 756 }) 757 } 758 } 759 760 func TestDeviceEmulatorWhitelistApply(t *testing.T) { 761 testDeviceEmulatorApply(t, false) 762 } 763 764 func TestDeviceEmulatorBlacklistApply(t *testing.T) { 765 testDeviceEmulatorApply(t, true) 766 } 767 768 func testDeviceEmulatorTransition(t *testing.T, sourceDefaultAllow bool) { 769 tests := []struct { 770 name string 771 source, target *emulator 772 expected []*devices.Rule 773 }{ 774 // No-op changes. 775 { 776 name: "Noop", 777 source: &emulator{ 778 defaultAllow: sourceDefaultAllow, 779 rules: deviceRules{ 780 { 781 node: devices.CharDevice, 782 major: 42, 783 minor: devices.Wildcard, 784 }: devices.Permissions("wm"), 785 }, 786 }, 787 target: &emulator{ 788 defaultAllow: sourceDefaultAllow, 789 rules: deviceRules{ 790 { 791 node: devices.CharDevice, 792 major: 42, 793 minor: devices.Wildcard, 794 }: devices.Permissions("wm"), 795 }, 796 }, 797 // Identical white-lists produce no extra rules. 798 expected: nil, 799 }, 800 // Switching modes. 801 { 802 name: "SwitchToOtherMode", 803 source: &emulator{ 804 defaultAllow: sourceDefaultAllow, 805 rules: deviceRules{ 806 { 807 node: devices.CharDevice, 808 major: 1, 809 minor: 2, 810 }: devices.Permissions("rwm"), 811 }, 812 }, 813 target: &emulator{ 814 defaultAllow: !sourceDefaultAllow, 815 rules: deviceRules{ 816 { 817 node: devices.BlockDevice, 818 major: 42, 819 minor: devices.Wildcard, 820 }: devices.Permissions("wm"), 821 }, 822 }, 823 expected: []*devices.Rule{ 824 // Clear-all rule. 825 { 826 Type: devices.WildcardDevice, 827 Major: devices.Wildcard, 828 Minor: devices.Wildcard, 829 Permissions: devices.Permissions("rwm"), 830 Allow: !sourceDefaultAllow, 831 }, 832 // The actual rule-set. 833 { 834 Type: devices.BlockDevice, 835 Major: 42, 836 Minor: devices.Wildcard, 837 Permissions: devices.Permissions("wm"), 838 Allow: sourceDefaultAllow, 839 }, 840 }, 841 }, 842 // Rule changes. 843 { 844 name: "RuleAddition", 845 source: &emulator{ 846 defaultAllow: sourceDefaultAllow, 847 rules: deviceRules{ 848 { 849 node: devices.CharDevice, 850 major: 1, 851 minor: 2, 852 }: devices.Permissions("rwm"), 853 }, 854 }, 855 target: &emulator{ 856 defaultAllow: sourceDefaultAllow, 857 rules: deviceRules{ 858 { 859 node: devices.CharDevice, 860 major: 1, 861 minor: 2, 862 }: devices.Permissions("rwm"), 863 { 864 node: devices.BlockDevice, 865 major: 42, 866 minor: 1337, 867 }: devices.Permissions("rwm"), 868 }, 869 }, 870 expected: []*devices.Rule{ 871 { 872 Type: devices.BlockDevice, 873 Major: 42, 874 Minor: 1337, 875 Permissions: devices.Permissions("rwm"), 876 Allow: !sourceDefaultAllow, 877 }, 878 }, 879 }, 880 { 881 name: "RuleRemoval", 882 source: &emulator{ 883 defaultAllow: sourceDefaultAllow, 884 rules: deviceRules{ 885 { 886 node: devices.CharDevice, 887 major: 1, 888 minor: 2, 889 }: devices.Permissions("rwm"), 890 { 891 node: devices.BlockDevice, 892 major: 42, 893 minor: 1337, 894 }: devices.Permissions("rwm"), 895 }, 896 }, 897 target: &emulator{ 898 defaultAllow: sourceDefaultAllow, 899 rules: deviceRules{ 900 { 901 node: devices.CharDevice, 902 major: 1, 903 minor: 2, 904 }: devices.Permissions("rwm"), 905 }, 906 }, 907 expected: []*devices.Rule{ 908 { 909 Type: devices.BlockDevice, 910 Major: 42, 911 Minor: 1337, 912 Permissions: devices.Permissions("rwm"), 913 Allow: sourceDefaultAllow, 914 }, 915 }, 916 }, 917 { 918 name: "RuleMultipleAdditionRemoval", 919 source: &emulator{ 920 defaultAllow: sourceDefaultAllow, 921 rules: deviceRules{ 922 { 923 node: devices.CharDevice, 924 major: 1, 925 minor: 2, 926 }: devices.Permissions("rwm"), 927 { 928 node: devices.BlockDevice, 929 major: 3, 930 minor: 9, 931 }: devices.Permissions("rw"), 932 }, 933 }, 934 target: &emulator{ 935 defaultAllow: sourceDefaultAllow, 936 rules: deviceRules{ 937 { 938 node: devices.CharDevice, 939 major: 1, 940 minor: 2, 941 }: devices.Permissions("rwm"), 942 }, 943 }, 944 expected: []*devices.Rule{ 945 { 946 Type: devices.BlockDevice, 947 Major: 3, 948 Minor: 9, 949 Permissions: devices.Permissions("rw"), 950 Allow: sourceDefaultAllow, 951 }, 952 }, 953 }, 954 // Modifying the access permissions. 955 { 956 name: "RulePartialAddition", 957 source: &emulator{ 958 defaultAllow: sourceDefaultAllow, 959 rules: deviceRules{ 960 { 961 node: devices.CharDevice, 962 major: 1, 963 minor: 2, 964 }: devices.Permissions("r"), 965 }, 966 }, 967 target: &emulator{ 968 defaultAllow: sourceDefaultAllow, 969 rules: deviceRules{ 970 { 971 node: devices.CharDevice, 972 major: 1, 973 minor: 2, 974 }: devices.Permissions("rwm"), 975 }, 976 }, 977 expected: []*devices.Rule{ 978 { 979 Type: devices.CharDevice, 980 Major: 1, 981 Minor: 2, 982 Permissions: devices.Permissions("wm"), 983 Allow: !sourceDefaultAllow, 984 }, 985 }, 986 }, 987 { 988 name: "RulePartialRemoval", 989 source: &emulator{ 990 defaultAllow: sourceDefaultAllow, 991 rules: deviceRules{ 992 { 993 node: devices.CharDevice, 994 major: 1, 995 minor: 2, 996 }: devices.Permissions("rw"), 997 }, 998 }, 999 target: &emulator{ 1000 defaultAllow: sourceDefaultAllow, 1001 rules: deviceRules{ 1002 { 1003 node: devices.CharDevice, 1004 major: 1, 1005 minor: 2, 1006 }: devices.Permissions("w"), 1007 }, 1008 }, 1009 expected: []*devices.Rule{ 1010 { 1011 Type: devices.CharDevice, 1012 Major: 1, 1013 Minor: 2, 1014 Permissions: devices.Permissions("r"), 1015 Allow: sourceDefaultAllow, 1016 }, 1017 }, 1018 }, 1019 { 1020 name: "RulePartialBoth", 1021 source: &emulator{ 1022 defaultAllow: sourceDefaultAllow, 1023 rules: deviceRules{ 1024 { 1025 node: devices.CharDevice, 1026 major: 1, 1027 minor: 2, 1028 }: devices.Permissions("rw"), 1029 }, 1030 }, 1031 target: &emulator{ 1032 defaultAllow: sourceDefaultAllow, 1033 rules: deviceRules{ 1034 { 1035 node: devices.CharDevice, 1036 major: 1, 1037 minor: 2, 1038 }: devices.Permissions("rm"), 1039 }, 1040 }, 1041 expected: []*devices.Rule{ 1042 { 1043 Type: devices.CharDevice, 1044 Major: 1, 1045 Minor: 2, 1046 Permissions: devices.Permissions("w"), 1047 Allow: sourceDefaultAllow, 1048 }, 1049 { 1050 Type: devices.CharDevice, 1051 Major: 1, 1052 Minor: 2, 1053 Permissions: devices.Permissions("m"), 1054 Allow: !sourceDefaultAllow, 1055 }, 1056 }, 1057 }, 1058 } 1059 1060 for _, test := range tests { 1061 test := test 1062 t.Run(test.name, func(t *testing.T) { 1063 // If we are in black-list mode, we need to prepend the relevant 1064 // clear-all rule (the expected rule lists are written with 1065 // white-list mode in mind), and then make a full copy of the 1066 // target rules. 1067 if sourceDefaultAllow && test.source.defaultAllow == test.target.defaultAllow { 1068 test.expected = []*devices.Rule{{ 1069 Type: devices.WildcardDevice, 1070 Major: devices.Wildcard, 1071 Minor: devices.Wildcard, 1072 Permissions: devices.Permissions("rwm"), 1073 Allow: test.target.defaultAllow, 1074 }} 1075 for _, rule := range test.target.rules.orderedEntries() { 1076 test.expected = append(test.expected, &devices.Rule{ 1077 Type: rule.meta.node, 1078 Major: rule.meta.major, 1079 Minor: rule.meta.minor, 1080 Permissions: rule.perms, 1081 Allow: !test.target.defaultAllow, 1082 }) 1083 } 1084 } 1085 1086 rules, err := test.source.Transition(test.target) 1087 if err != nil { 1088 t.Fatalf("unexpected error while calculating transition rules: %#v", err) 1089 } 1090 1091 if !reflect.DeepEqual(rules, test.expected) { 1092 t.Errorf("rules don't match expected set: %#v != %#v", rules, test.expected) 1093 } 1094 1095 // Apply the rules to the source to see if it actually transitions 1096 // correctly. This is all emulated but it's a good thing to 1097 // double-check. 1098 for _, rule := range rules { 1099 if err := test.source.Apply(*rule); err != nil { 1100 t.Fatalf("error while applying transition rule [%#v]: %v", rule, err) 1101 } 1102 } 1103 if !reflect.DeepEqual(test.source, test.target) { 1104 t.Errorf("transition incomplete after applying all rules: %#v != %#v", test.source, test.target) 1105 } 1106 }) 1107 } 1108 } 1109 1110 func TestDeviceEmulatorTransitionFromBlacklist(t *testing.T) { 1111 testDeviceEmulatorTransition(t, true) 1112 } 1113 1114 func TestDeviceEmulatorTransitionFromWhitelist(t *testing.T) { 1115 testDeviceEmulatorTransition(t, false) 1116 } 1117 1118 func BenchmarkParseLine(b *testing.B) { 1119 list := `c *:* m 1120 b *:* m 1121 c 1:3 rwm 1122 c 1:5 rwm 1123 c 1:7 rwm 1124 c 1:8 rwm 1125 c 1:9 rwm 1126 c 5:0 rwm 1127 c 5:2 rwm 1128 c 136:* rwm 1129 c 10:200 rwm` 1130 1131 var r *deviceRule 1132 var err error 1133 for i := 0; i < b.N; i++ { 1134 s := bufio.NewScanner(strings.NewReader(list)) 1135 for s.Scan() { 1136 line := s.Text() 1137 r, err = parseLine(line) 1138 } 1139 if err := s.Err(); err != nil { 1140 b.Fatal(err) 1141 } 1142 } 1143 b.Logf("rule: %v, err: %v", r, err) 1144 }