1115 lines
25 KiB
Go
1115 lines
25 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
/*
|
|
* Copyright (C) 2020 Aleksa Sarai <cyphar@cyphar.com>
|
|
* Copyright (C) 2020 SUSE LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package devices
|
|
|
|
import (
|
|
"bytes"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
)
|
|
|
|
func TestDeviceEmulatorLoad(t *testing.T) {
|
|
tests := []struct {
|
|
name, list string
|
|
expected *Emulator
|
|
}{
|
|
{
|
|
name: "BlacklistMode",
|
|
list: `a *:* rwm`,
|
|
expected: &Emulator{
|
|
defaultAllow: true,
|
|
},
|
|
},
|
|
{
|
|
name: "WhitelistBasic",
|
|
list: `c 4:2 rw`,
|
|
expected: &Emulator{
|
|
defaultAllow: false,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 4,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("rw"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "WhitelistWildcard",
|
|
list: `b 0:* m`,
|
|
expected: &Emulator{
|
|
defaultAllow: false,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 0,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("m"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "WhitelistDuplicate",
|
|
list: `c *:* rwm
|
|
c 1:1 r`,
|
|
expected: &Emulator{
|
|
defaultAllow: false,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: configs.Wildcard,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("rwm"),
|
|
// To match the kernel, we allow redundant rules.
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 1,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "WhitelistComplicated",
|
|
list: `c *:* m
|
|
b *:* m
|
|
c 1:3 rwm
|
|
c 1:5 rwm
|
|
c 1:7 rwm
|
|
c 1:8 rwm
|
|
c 1:9 rwm
|
|
c 5:0 rwm
|
|
c 5:2 rwm
|
|
c 136:* rwm
|
|
c 10:200 rwm`,
|
|
expected: &Emulator{
|
|
defaultAllow: false,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: configs.Wildcard,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("m"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: configs.Wildcard,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("m"),
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 3,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 5,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 7,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 8,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 9,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 5,
|
|
minor: 0,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 5,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 136,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 10,
|
|
minor: 200,
|
|
}: configs.DevicePermissions("rwm"),
|
|
},
|
|
},
|
|
},
|
|
// Some invalid lists.
|
|
{
|
|
name: "InvalidFieldNumber",
|
|
list: `b 1:0`,
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "InvalidDeviceType",
|
|
list: `p *:* rwm`,
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "InvalidMajorNumber1",
|
|
list: `p -1:3 rwm`,
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "InvalidMajorNumber2",
|
|
list: `c foo:27 rwm`,
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "InvalidMinorNumber1",
|
|
list: `b 1:-4 rwm`,
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "InvalidMinorNumber2",
|
|
list: `b 1:foo rwm`,
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "InvalidPermissions",
|
|
list: `b 1:7 rwk`,
|
|
expected: nil,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
test := test // capture range variable
|
|
t.Run(test.name, func(t *testing.T) {
|
|
list := bytes.NewBufferString(test.list)
|
|
emu, err := EmulatorFromList(list)
|
|
if err != nil && test.expected != nil {
|
|
t.Fatalf("unexpected failure when creating emulator: %v", err)
|
|
} else if err == nil && test.expected == nil {
|
|
t.Fatalf("unexpected success when creating emulator: %#v", emu)
|
|
}
|
|
|
|
if !reflect.DeepEqual(emu, test.expected) {
|
|
t.Errorf("final emulator state mismatch: %#v != %#v", emu, test.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func testDeviceEmulatorApply(t *testing.T, baseDefaultAllow bool) {
|
|
tests := []struct {
|
|
name string
|
|
rule configs.DeviceRule
|
|
base, expected *Emulator
|
|
}{
|
|
// Switch between default modes.
|
|
{
|
|
name: "SwitchToOtherMode",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.WildcardDevice,
|
|
Major: configs.Wildcard,
|
|
Minor: configs.Wildcard,
|
|
Permissions: configs.DevicePermissions("rwm"),
|
|
Allow: !baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: configs.Wildcard,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 1,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
expected: &Emulator{
|
|
defaultAllow: !baseDefaultAllow,
|
|
rules: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "SwitchToSameModeNoop",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.WildcardDevice,
|
|
Major: configs.Wildcard,
|
|
Minor: configs.Wildcard,
|
|
Permissions: configs.DevicePermissions("rwm"),
|
|
Allow: baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: nil,
|
|
},
|
|
expected: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "SwitchToSameMode",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.WildcardDevice,
|
|
Major: configs.Wildcard,
|
|
Minor: configs.Wildcard,
|
|
Permissions: configs.DevicePermissions("rwm"),
|
|
Allow: baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: configs.Wildcard,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 1,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
expected: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: nil,
|
|
},
|
|
},
|
|
// Rule addition logic.
|
|
{
|
|
name: "RuleAdditionBasic",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.CharDevice,
|
|
Major: 42,
|
|
Minor: 1337,
|
|
Permissions: configs.DevicePermissions("rm"),
|
|
Allow: !baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 2,
|
|
minor: 1,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 1,
|
|
minor: 5,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
expected: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 2,
|
|
minor: 1,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 1,
|
|
minor: 5,
|
|
}: configs.DevicePermissions("r"),
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: 1337,
|
|
}: configs.DevicePermissions("rm"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "RuleAdditionBasicDuplicate",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.CharDevice,
|
|
Major: 42,
|
|
Minor: 1337,
|
|
Permissions: configs.DevicePermissions("rm"),
|
|
Allow: !baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("rwm"),
|
|
},
|
|
},
|
|
expected: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("rwm"),
|
|
// To match the kernel, we allow redundant rules.
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: 1337,
|
|
}: configs.DevicePermissions("rm"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "RuleAdditionBasicDuplicateNoop",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.CharDevice,
|
|
Major: 42,
|
|
Minor: 1337,
|
|
Permissions: configs.DevicePermissions("rm"),
|
|
Allow: !baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: 1337,
|
|
}: configs.DevicePermissions("rm"),
|
|
},
|
|
},
|
|
expected: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: 1337,
|
|
}: configs.DevicePermissions("rm"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "RuleAdditionMerge",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.BlockDevice,
|
|
Major: 5,
|
|
Minor: 12,
|
|
Permissions: configs.DevicePermissions("rm"),
|
|
Allow: !baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 2,
|
|
minor: 1,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 5,
|
|
minor: 12,
|
|
}: configs.DevicePermissions("rw"),
|
|
},
|
|
},
|
|
expected: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 2,
|
|
minor: 1,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 5,
|
|
minor: 12,
|
|
}: configs.DevicePermissions("rwm"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "RuleAdditionMergeWildcard",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.BlockDevice,
|
|
Major: 5,
|
|
Minor: configs.Wildcard,
|
|
Permissions: configs.DevicePermissions("rm"),
|
|
Allow: !baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 2,
|
|
minor: 1,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 5,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("rw"),
|
|
},
|
|
},
|
|
expected: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 2,
|
|
minor: 1,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 5,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("rwm"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "RuleAdditionMergeNoop",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.BlockDevice,
|
|
Major: 5,
|
|
Minor: 12,
|
|
Permissions: configs.DevicePermissions("r"),
|
|
Allow: !baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 2,
|
|
minor: 1,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 5,
|
|
minor: 12,
|
|
}: configs.DevicePermissions("rw"),
|
|
},
|
|
},
|
|
expected: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 2,
|
|
minor: 1,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 5,
|
|
minor: 12,
|
|
}: configs.DevicePermissions("rw"),
|
|
},
|
|
},
|
|
},
|
|
// Rule removal logic.
|
|
{
|
|
name: "RuleRemovalBasic",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.CharDevice,
|
|
Major: 42,
|
|
Minor: 1337,
|
|
Permissions: configs.DevicePermissions("rm"),
|
|
Allow: baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: 1337,
|
|
}: configs.DevicePermissions("rm"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 1,
|
|
minor: 5,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
expected: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 1,
|
|
minor: 5,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "RuleRemovalNonexistent",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.CharDevice,
|
|
Major: 4,
|
|
Minor: 1,
|
|
Permissions: configs.DevicePermissions("rw"),
|
|
Allow: baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 1,
|
|
minor: 5,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
expected: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 1,
|
|
minor: 5,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "RuleRemovalFull",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.CharDevice,
|
|
Major: 42,
|
|
Minor: 1337,
|
|
Permissions: configs.DevicePermissions("rw"),
|
|
Allow: baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: 1337,
|
|
}: configs.DevicePermissions("w"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 1,
|
|
minor: 5,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
expected: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 1,
|
|
minor: 5,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "RuleRemovalPartial",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.CharDevice,
|
|
Major: 42,
|
|
Minor: 1337,
|
|
Permissions: configs.DevicePermissions("r"),
|
|
Allow: baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: 1337,
|
|
}: configs.DevicePermissions("rm"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 1,
|
|
minor: 5,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
expected: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: 1337,
|
|
}: configs.DevicePermissions("m"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 1,
|
|
minor: 5,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
},
|
|
// Check our non-canonical behaviour when it comes to try to "punch
|
|
// out" holes in a wildcard rule.
|
|
{
|
|
name: "RuleRemovalWildcardPunchoutImpossible",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.CharDevice,
|
|
Major: 42,
|
|
Minor: 1337,
|
|
Permissions: configs.DevicePermissions("r"),
|
|
Allow: baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("rm"),
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: 1337,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "RuleRemovalWildcardPunchoutPossible",
|
|
rule: configs.DeviceRule{
|
|
Type: configs.CharDevice,
|
|
Major: 42,
|
|
Minor: 1337,
|
|
Permissions: configs.DevicePermissions("r"),
|
|
Allow: baseDefaultAllow,
|
|
},
|
|
base: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("wm"),
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: 1337,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
expected: &Emulator{
|
|
defaultAllow: baseDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("wm"),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.name, func(t *testing.T) {
|
|
err := test.base.Apply(test.rule)
|
|
if err != nil && test.expected != nil {
|
|
t.Fatalf("unexpected failure when applying apply rule: %v", err)
|
|
} else if err == nil && test.expected == nil {
|
|
t.Fatalf("unexpected success when applying apply rule: %#v", test.base)
|
|
}
|
|
|
|
if test.expected != nil && !reflect.DeepEqual(test.base, test.expected) {
|
|
t.Errorf("final emulator state mismatch: %#v != %#v", test.base, test.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeviceEmulatorWhitelistApply(t *testing.T) {
|
|
testDeviceEmulatorApply(t, false)
|
|
}
|
|
|
|
func TestDeviceEmulatorBlacklistApply(t *testing.T) {
|
|
testDeviceEmulatorApply(t, true)
|
|
}
|
|
|
|
func testDeviceEmulatorTransition(t *testing.T, sourceDefaultAllow bool) {
|
|
tests := []struct {
|
|
name string
|
|
source, target *Emulator
|
|
expected []*configs.DeviceRule
|
|
}{
|
|
// No-op changes.
|
|
{
|
|
name: "Noop",
|
|
source: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("wm"),
|
|
},
|
|
},
|
|
target: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 42,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("wm"),
|
|
},
|
|
},
|
|
// Identical white-lists produce no extra rules.
|
|
expected: nil,
|
|
},
|
|
// Switching modes.
|
|
{
|
|
name: "SwitchToOtherMode",
|
|
source: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("rwm"),
|
|
},
|
|
},
|
|
target: &Emulator{
|
|
defaultAllow: !sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 42,
|
|
minor: configs.Wildcard,
|
|
}: configs.DevicePermissions("wm"),
|
|
},
|
|
},
|
|
expected: []*configs.DeviceRule{
|
|
// Clear-all rule.
|
|
{
|
|
Type: configs.WildcardDevice,
|
|
Major: configs.Wildcard,
|
|
Minor: configs.Wildcard,
|
|
Permissions: configs.DevicePermissions("rwm"),
|
|
Allow: !sourceDefaultAllow,
|
|
},
|
|
// The actual rule-set.
|
|
{
|
|
Type: configs.BlockDevice,
|
|
Major: 42,
|
|
Minor: configs.Wildcard,
|
|
Permissions: configs.DevicePermissions("wm"),
|
|
Allow: sourceDefaultAllow,
|
|
},
|
|
},
|
|
},
|
|
// Rule changes.
|
|
{
|
|
name: "RuleAddition",
|
|
source: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("rwm"),
|
|
},
|
|
},
|
|
target: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 42,
|
|
minor: 1337,
|
|
}: configs.DevicePermissions("rwm"),
|
|
},
|
|
},
|
|
expected: []*configs.DeviceRule{
|
|
{
|
|
Type: configs.BlockDevice,
|
|
Major: 42,
|
|
Minor: 1337,
|
|
Permissions: configs.DevicePermissions("rwm"),
|
|
Allow: !sourceDefaultAllow,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "RuleRemoval",
|
|
source: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 42,
|
|
minor: 1337,
|
|
}: configs.DevicePermissions("rwm"),
|
|
},
|
|
},
|
|
target: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("rwm"),
|
|
},
|
|
},
|
|
expected: []*configs.DeviceRule{
|
|
{
|
|
Type: configs.BlockDevice,
|
|
Major: 42,
|
|
Minor: 1337,
|
|
Permissions: configs.DevicePermissions("rwm"),
|
|
Allow: sourceDefaultAllow,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "RuleMultipleAdditionRemoval",
|
|
source: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("rwm"),
|
|
{
|
|
node: configs.BlockDevice,
|
|
major: 3,
|
|
minor: 9,
|
|
}: configs.DevicePermissions("rw"),
|
|
},
|
|
},
|
|
target: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("rwm"),
|
|
},
|
|
},
|
|
expected: []*configs.DeviceRule{
|
|
{
|
|
Type: configs.BlockDevice,
|
|
Major: 3,
|
|
Minor: 9,
|
|
Permissions: configs.DevicePermissions("rw"),
|
|
Allow: sourceDefaultAllow,
|
|
},
|
|
},
|
|
},
|
|
// Modifying the access permissions.
|
|
{
|
|
name: "RulePartialAddition",
|
|
source: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("r"),
|
|
},
|
|
},
|
|
target: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("rwm"),
|
|
},
|
|
},
|
|
expected: []*configs.DeviceRule{
|
|
{
|
|
Type: configs.CharDevice,
|
|
Major: 1,
|
|
Minor: 2,
|
|
Permissions: configs.DevicePermissions("wm"),
|
|
Allow: !sourceDefaultAllow,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "RulePartialRemoval",
|
|
source: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("rw"),
|
|
},
|
|
},
|
|
target: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("w"),
|
|
},
|
|
},
|
|
expected: []*configs.DeviceRule{
|
|
{
|
|
Type: configs.CharDevice,
|
|
Major: 1,
|
|
Minor: 2,
|
|
Permissions: configs.DevicePermissions("r"),
|
|
Allow: sourceDefaultAllow,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "RulePartialBoth",
|
|
source: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("rw"),
|
|
},
|
|
},
|
|
target: &Emulator{
|
|
defaultAllow: sourceDefaultAllow,
|
|
rules: deviceRules{
|
|
{
|
|
node: configs.CharDevice,
|
|
major: 1,
|
|
minor: 2,
|
|
}: configs.DevicePermissions("rm"),
|
|
},
|
|
},
|
|
expected: []*configs.DeviceRule{
|
|
{
|
|
Type: configs.CharDevice,
|
|
Major: 1,
|
|
Minor: 2,
|
|
Permissions: configs.DevicePermissions("w"),
|
|
Allow: sourceDefaultAllow,
|
|
},
|
|
{
|
|
Type: configs.CharDevice,
|
|
Major: 1,
|
|
Minor: 2,
|
|
Permissions: configs.DevicePermissions("m"),
|
|
Allow: !sourceDefaultAllow,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.name, func(t *testing.T) {
|
|
// If we are in black-list mode, we need to prepend the relevant
|
|
// clear-all rule (the expected rule lists are written with
|
|
// white-list mode in mind), and then make a full copy of the
|
|
// target rules.
|
|
if sourceDefaultAllow && test.source.defaultAllow == test.target.defaultAllow {
|
|
test.expected = []*configs.DeviceRule{{
|
|
Type: configs.WildcardDevice,
|
|
Major: configs.Wildcard,
|
|
Minor: configs.Wildcard,
|
|
Permissions: configs.DevicePermissions("rwm"),
|
|
Allow: test.target.defaultAllow,
|
|
}}
|
|
for _, rule := range test.target.rules.orderedEntries() {
|
|
test.expected = append(test.expected, &configs.DeviceRule{
|
|
Type: rule.meta.node,
|
|
Major: rule.meta.major,
|
|
Minor: rule.meta.minor,
|
|
Permissions: rule.perms,
|
|
Allow: !test.target.defaultAllow,
|
|
})
|
|
}
|
|
}
|
|
|
|
rules, err := test.source.Transition(test.target)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error while calculating transition rules: %#v", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(rules, test.expected) {
|
|
t.Errorf("rules don't match expected set: %#v != %#v", rules, test.expected)
|
|
}
|
|
|
|
// Apply the rules to the source to see if it actually transitions
|
|
// correctly. This is all emulated but it's a good thing to
|
|
// double-check.
|
|
for _, rule := range rules {
|
|
if err := test.source.Apply(*rule); err != nil {
|
|
t.Fatalf("error while applying transition rule [%#v]: %v", rule, err)
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(test.source, test.target) {
|
|
t.Errorf("transition incomplete after applying all rules: %#v != %#v", test.source, test.target)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeviceEmulatorTransitionFromBlacklist(t *testing.T) {
|
|
testDeviceEmulatorTransition(t, true)
|
|
}
|
|
|
|
func TestDeviceEmulatorTransitionFromWhitelist(t *testing.T) {
|
|
testDeviceEmulatorTransition(t, false)
|
|
}
|