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