...

Source file src/github.com/opencontainers/runc/libcontainer/cgroups/devices/devices_emulator_test.go

Documentation: github.com/opencontainers/runc/libcontainer/cgroups/devices

     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  }
  1145  

View as plain text