github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/runsc/specutils/seccomp/seccomp_test.go (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package seccomp
    16  
    17  import (
    18  	"fmt"
    19  	"testing"
    20  
    21  	specs "github.com/opencontainers/runtime-spec/specs-go"
    22  	"golang.org/x/sys/unix"
    23  	"github.com/SagerNet/gvisor/pkg/abi/linux"
    24  	"github.com/SagerNet/gvisor/pkg/bpf"
    25  	"github.com/SagerNet/gvisor/pkg/hostarch"
    26  	"github.com/SagerNet/gvisor/pkg/marshal"
    27  )
    28  
    29  // asInput converts a linux.SeccompData to a bpf.Input.
    30  func asInput(d *linux.SeccompData) bpf.Input {
    31  	return bpf.InputBytes{marshal.Marshal(d), hostarch.ByteOrder}
    32  }
    33  
    34  // testInput creates an Input struct with given seccomp input values.
    35  func testInput(arch uint32, syscallName string, args *[6]uint64) bpf.Input {
    36  	syscallNo, err := lookupSyscallNo(arch, syscallName)
    37  	if err != nil {
    38  		// Assume tests set valid syscall names.
    39  		panic(err)
    40  	}
    41  
    42  	if args == nil {
    43  		argArray := [6]uint64{0, 0, 0, 0, 0, 0}
    44  		args = &argArray
    45  	}
    46  
    47  	data := linux.SeccompData{
    48  		Nr:   int32(syscallNo),
    49  		Arch: arch,
    50  		Args: *args,
    51  	}
    52  
    53  	return asInput(&data)
    54  }
    55  
    56  // testCase holds a seccomp test case.
    57  type testCase struct {
    58  	name     string
    59  	config   specs.LinuxSeccomp
    60  	input    bpf.Input
    61  	expected uint32
    62  }
    63  
    64  var (
    65  	// seccompTests is a list of speccomp test cases.
    66  	seccompTests = []testCase{
    67  		{
    68  			name: "default_allow",
    69  			config: specs.LinuxSeccomp{
    70  				DefaultAction: specs.ActAllow,
    71  			},
    72  			input:    testInput(nativeArchAuditNo, "read", nil),
    73  			expected: uint32(allowAction),
    74  		},
    75  		{
    76  			name: "default_deny",
    77  			config: specs.LinuxSeccomp{
    78  				DefaultAction: specs.ActErrno,
    79  			},
    80  			input:    testInput(nativeArchAuditNo, "read", nil),
    81  			expected: uint32(errnoAction),
    82  		},
    83  		{
    84  			name: "deny_arch",
    85  			config: specs.LinuxSeccomp{
    86  				DefaultAction: specs.ActAllow,
    87  				Syscalls: []specs.LinuxSyscall{
    88  					{
    89  						Names: []string{
    90  							"getcwd",
    91  						},
    92  						Action: specs.ActErrno,
    93  					},
    94  				},
    95  			},
    96  			// Syscall matches but the arch is AUDIT_ARCH_X86 so the return
    97  			// value is the bad arch action.
    98  			input:    asInput(&linux.SeccompData{Nr: 183, Arch: 0x40000003}), //
    99  			expected: uint32(killThreadAction),
   100  		},
   101  		{
   102  			name: "match_name_errno",
   103  			config: specs.LinuxSeccomp{
   104  				DefaultAction: specs.ActAllow,
   105  				Syscalls: []specs.LinuxSyscall{
   106  					{
   107  						Names: []string{
   108  							"getcwd",
   109  							"chmod",
   110  						},
   111  						Action: specs.ActErrno,
   112  					},
   113  					{
   114  						Names: []string{
   115  							"write",
   116  						},
   117  						Action: specs.ActTrace,
   118  					},
   119  				},
   120  			},
   121  			input:    testInput(nativeArchAuditNo, "getcwd", nil),
   122  			expected: uint32(errnoAction),
   123  		},
   124  		{
   125  			name: "match_name_trace",
   126  			config: specs.LinuxSeccomp{
   127  				DefaultAction: specs.ActAllow,
   128  				Syscalls: []specs.LinuxSyscall{
   129  					{
   130  						Names: []string{
   131  							"getcwd",
   132  							"chmod",
   133  						},
   134  						Action: specs.ActErrno,
   135  					},
   136  					{
   137  						Names: []string{
   138  							"write",
   139  						},
   140  						Action: specs.ActTrace,
   141  					},
   142  				},
   143  			},
   144  			input:    testInput(nativeArchAuditNo, "write", nil),
   145  			expected: uint32(traceAction),
   146  		},
   147  		{
   148  			name: "no_match_name_allow",
   149  			config: specs.LinuxSeccomp{
   150  				DefaultAction: specs.ActAllow,
   151  				Syscalls: []specs.LinuxSyscall{
   152  					{
   153  						Names: []string{
   154  							"getcwd",
   155  							"chmod",
   156  						},
   157  						Action: specs.ActErrno,
   158  					},
   159  					{
   160  						Names: []string{
   161  							"write",
   162  						},
   163  						Action: specs.ActTrace,
   164  					},
   165  				},
   166  			},
   167  			input:    testInput(nativeArchAuditNo, "openat", nil),
   168  			expected: uint32(allowAction),
   169  		},
   170  		{
   171  			name: "simple_match_args",
   172  			config: specs.LinuxSeccomp{
   173  				DefaultAction: specs.ActAllow,
   174  				Syscalls: []specs.LinuxSyscall{
   175  					{
   176  						Names: []string{
   177  							"clone",
   178  						},
   179  						Args: []specs.LinuxSeccompArg{
   180  							{
   181  								Index: 0,
   182  								Value: unix.CLONE_FS,
   183  								Op:    specs.OpEqualTo,
   184  							},
   185  						},
   186  						Action: specs.ActErrno,
   187  					},
   188  				},
   189  			},
   190  			input:    testInput(nativeArchAuditNo, "clone", &[6]uint64{unix.CLONE_FS}),
   191  			expected: uint32(errnoAction),
   192  		},
   193  		{
   194  			name: "match_args_or",
   195  			config: specs.LinuxSeccomp{
   196  				DefaultAction: specs.ActAllow,
   197  				Syscalls: []specs.LinuxSyscall{
   198  					{
   199  						Names: []string{
   200  							"clone",
   201  						},
   202  						Args: []specs.LinuxSeccompArg{
   203  							{
   204  								Index: 0,
   205  								Value: unix.CLONE_FS,
   206  								Op:    specs.OpEqualTo,
   207  							},
   208  							{
   209  								Index: 0,
   210  								Value: unix.CLONE_VM,
   211  								Op:    specs.OpEqualTo,
   212  							},
   213  						},
   214  						Action: specs.ActErrno,
   215  					},
   216  				},
   217  			},
   218  			input:    testInput(nativeArchAuditNo, "clone", &[6]uint64{unix.CLONE_FS}),
   219  			expected: uint32(errnoAction),
   220  		},
   221  		{
   222  			name: "match_args_and",
   223  			config: specs.LinuxSeccomp{
   224  				DefaultAction: specs.ActAllow,
   225  				Syscalls: []specs.LinuxSyscall{
   226  					{
   227  						Names: []string{
   228  							"getsockopt",
   229  						},
   230  						Args: []specs.LinuxSeccompArg{
   231  							{
   232  								Index: 1,
   233  								Value: unix.SOL_SOCKET,
   234  								Op:    specs.OpEqualTo,
   235  							},
   236  							{
   237  								Index: 2,
   238  								Value: unix.SO_PEERCRED,
   239  								Op:    specs.OpEqualTo,
   240  							},
   241  						},
   242  						Action: specs.ActErrno,
   243  					},
   244  				},
   245  			},
   246  			input:    testInput(nativeArchAuditNo, "getsockopt", &[6]uint64{0, unix.SOL_SOCKET, unix.SO_PEERCRED}),
   247  			expected: uint32(errnoAction),
   248  		},
   249  		{
   250  			name: "no_match_args_and",
   251  			config: specs.LinuxSeccomp{
   252  				DefaultAction: specs.ActAllow,
   253  				Syscalls: []specs.LinuxSyscall{
   254  					{
   255  						Names: []string{
   256  							"getsockopt",
   257  						},
   258  						Args: []specs.LinuxSeccompArg{
   259  							{
   260  								Index: 1,
   261  								Value: unix.SOL_SOCKET,
   262  								Op:    specs.OpEqualTo,
   263  							},
   264  							{
   265  								Index: 2,
   266  								Value: unix.SO_PEERCRED,
   267  								Op:    specs.OpEqualTo,
   268  							},
   269  						},
   270  						Action: specs.ActErrno,
   271  					},
   272  				},
   273  			},
   274  			input:    testInput(nativeArchAuditNo, "getsockopt", &[6]uint64{0, unix.SOL_SOCKET}),
   275  			expected: uint32(allowAction),
   276  		},
   277  		{
   278  			name: "Simple args (no match)",
   279  			config: specs.LinuxSeccomp{
   280  				DefaultAction: specs.ActAllow,
   281  				Syscalls: []specs.LinuxSyscall{
   282  					{
   283  						Names: []string{
   284  							"clone",
   285  						},
   286  						Args: []specs.LinuxSeccompArg{
   287  							{
   288  								Index: 0,
   289  								Value: unix.CLONE_FS,
   290  								Op:    specs.OpEqualTo,
   291  							},
   292  						},
   293  						Action: specs.ActErrno,
   294  					},
   295  				},
   296  			},
   297  			input:    testInput(nativeArchAuditNo, "clone", &[6]uint64{unix.CLONE_VM}),
   298  			expected: uint32(allowAction),
   299  		},
   300  		{
   301  			name: "OpMaskedEqual (match)",
   302  			config: specs.LinuxSeccomp{
   303  				DefaultAction: specs.ActAllow,
   304  				Syscalls: []specs.LinuxSyscall{
   305  					{
   306  						Names: []string{
   307  							"clone",
   308  						},
   309  						Args: []specs.LinuxSeccompArg{
   310  							{
   311  								Index:    0,
   312  								Value:    unix.CLONE_FS,
   313  								ValueTwo: unix.CLONE_FS,
   314  								Op:       specs.OpMaskedEqual,
   315  							},
   316  						},
   317  						Action: specs.ActErrno,
   318  					},
   319  				},
   320  			},
   321  			input:    testInput(nativeArchAuditNo, "clone", &[6]uint64{unix.CLONE_FS | unix.CLONE_VM}),
   322  			expected: uint32(errnoAction),
   323  		},
   324  		{
   325  			name: "OpMaskedEqual (no match)",
   326  			config: specs.LinuxSeccomp{
   327  				DefaultAction: specs.ActAllow,
   328  				Syscalls: []specs.LinuxSyscall{
   329  					{
   330  						Names: []string{
   331  							"clone",
   332  						},
   333  						Args: []specs.LinuxSeccompArg{
   334  							{
   335  								Index:    0,
   336  								Value:    unix.CLONE_FS | unix.CLONE_VM,
   337  								ValueTwo: unix.CLONE_FS | unix.CLONE_VM,
   338  								Op:       specs.OpMaskedEqual,
   339  							},
   340  						},
   341  						Action: specs.ActErrno,
   342  					},
   343  				},
   344  			},
   345  			input:    testInput(nativeArchAuditNo, "clone", &[6]uint64{unix.CLONE_FS}),
   346  			expected: uint32(allowAction),
   347  		},
   348  		{
   349  			name: "OpMaskedEqual (clone)",
   350  			config: specs.LinuxSeccomp{
   351  				DefaultAction: specs.ActErrno,
   352  				Syscalls: []specs.LinuxSyscall{
   353  					{
   354  						Names: []string{
   355  							"clone",
   356  						},
   357  						// This comes from the Docker default seccomp
   358  						// profile for clone.
   359  						Args: []specs.LinuxSeccompArg{
   360  							{
   361  								Index:    0,
   362  								Value:    0x7e020000,
   363  								ValueTwo: 0x0,
   364  								Op:       specs.OpMaskedEqual,
   365  							},
   366  						},
   367  						Action: specs.ActAllow,
   368  					},
   369  				},
   370  			},
   371  			input:    testInput(nativeArchAuditNo, "clone", &[6]uint64{0x50f00}),
   372  			expected: uint32(allowAction),
   373  		},
   374  	}
   375  )
   376  
   377  // TestRunscSeccomp generates seccomp programs from OCI config and executes
   378  // them using runsc's library, comparing against expected results.
   379  func TestRunscSeccomp(t *testing.T) {
   380  	for _, tc := range seccompTests {
   381  		t.Run(tc.name, func(t *testing.T) {
   382  			runscProgram, err := BuildProgram(&tc.config)
   383  			if err != nil {
   384  				t.Fatalf("generating runsc BPF: %v", err)
   385  			}
   386  
   387  			if err := checkProgram(runscProgram, tc.input, tc.expected); err != nil {
   388  				t.Fatalf("running runsc BPF: %v", err)
   389  			}
   390  		})
   391  	}
   392  }
   393  
   394  // checkProgram runs the given program over the given input and checks the
   395  // result against the expected output.
   396  func checkProgram(p bpf.Program, in bpf.Input, expected uint32) error {
   397  	result, err := bpf.Exec(p, in)
   398  	if err != nil {
   399  		return err
   400  	}
   401  
   402  	if result != expected {
   403  		// Include a decoded version of the program in output for debugging purposes.
   404  		decoded, _ := bpf.DecodeProgram(p)
   405  		return fmt.Errorf("Unexpected result: got: %d, expected: %d\nBPF Program\n%s", result, expected, decoded)
   406  	}
   407  
   408  	return nil
   409  }