github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/runsc/specutils/seccomp/seccomp.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 implements some features of libseccomp in order to support
    16  // OCI.
    17  package seccomp
    18  
    19  import (
    20  	"fmt"
    21  
    22  	specs "github.com/opencontainers/runtime-spec/specs-go"
    23  	"golang.org/x/sys/unix"
    24  	"github.com/metacubex/gvisor/pkg/abi/linux"
    25  	"github.com/metacubex/gvisor/pkg/bpf"
    26  	"github.com/metacubex/gvisor/pkg/log"
    27  	"github.com/metacubex/gvisor/pkg/seccomp"
    28  	"github.com/metacubex/gvisor/pkg/sentry/kernel"
    29  	slinux "github.com/metacubex/gvisor/pkg/sentry/syscalls/linux"
    30  )
    31  
    32  var (
    33  	killThreadAction = linux.SECCOMP_RET_KILL_THREAD
    34  	trapAction       = linux.SECCOMP_RET_TRAP
    35  	// runc always returns EPERM as the errorcode for SECCOMP_RET_ERRNO
    36  	errnoAction = linux.SECCOMP_RET_ERRNO.WithReturnCode(uint16(unix.EPERM))
    37  	// runc always returns EPERM as the errorcode for SECCOMP_RET_TRACE
    38  	traceAction = linux.SECCOMP_RET_TRACE.WithReturnCode(uint16(unix.EPERM))
    39  	allowAction = linux.SECCOMP_RET_ALLOW
    40  )
    41  
    42  // BuildProgram generates a bpf program based on the given OCI seccomp
    43  // config.
    44  func BuildProgram(s *specs.LinuxSeccomp) (bpf.Program, error) {
    45  	defaultAction, err := convertAction(s.DefaultAction)
    46  	if err != nil {
    47  		return bpf.Program{}, fmt.Errorf("secomp default action: %w", err)
    48  	}
    49  	ruleset, err := convertRules(s)
    50  	if err != nil {
    51  		return bpf.Program{}, fmt.Errorf("invalid seccomp rules: %w", err)
    52  	}
    53  
    54  	instrs, _, err := seccomp.BuildProgram(ruleset, seccomp.ProgramOptions{
    55  		DefaultAction: defaultAction,
    56  		BadArchAction: killThreadAction,
    57  	})
    58  	if err != nil {
    59  		return bpf.Program{}, fmt.Errorf("building seccomp program: %w", err)
    60  	}
    61  
    62  	program, err := bpf.Compile(instrs, true /* optimize */)
    63  	if err != nil {
    64  		return bpf.Program{}, fmt.Errorf("compiling seccomp program: %w", err)
    65  	}
    66  
    67  	return program, nil
    68  }
    69  
    70  // lookupSyscallNo gets the syscall number for the syscall with the given name
    71  // for the given architecture.
    72  func lookupSyscallNo(arch uint32, name string) (uint32, error) {
    73  	var table *kernel.SyscallTable
    74  	switch arch {
    75  	case linux.AUDIT_ARCH_X86_64:
    76  		table = slinux.AMD64
    77  	case linux.AUDIT_ARCH_AARCH64:
    78  		table = slinux.ARM64
    79  	}
    80  	if table == nil {
    81  		return 0, fmt.Errorf("unsupported architecture: %d", arch)
    82  	}
    83  	n, err := table.LookupNo(name)
    84  	if err != nil {
    85  		return 0, err
    86  	}
    87  	return uint32(n), nil
    88  }
    89  
    90  // convertAction converts a LinuxSeccompAction to BPFAction
    91  func convertAction(act specs.LinuxSeccompAction) (linux.BPFAction, error) {
    92  	// TODO(gvisor.dev/issue/3124): Update specs package to include ActLog and ActKillProcess.
    93  	switch act {
    94  	case specs.ActKill:
    95  		return killThreadAction, nil
    96  	case specs.ActTrap:
    97  		return trapAction, nil
    98  	case specs.ActErrno:
    99  		return errnoAction, nil
   100  	case specs.ActTrace:
   101  		return traceAction, nil
   102  	case specs.ActAllow:
   103  		return allowAction, nil
   104  	default:
   105  		return 0, fmt.Errorf("invalid action: %v", act)
   106  	}
   107  }
   108  
   109  // convertRules converts OCI linux seccomp rules into RuleSets that can be used by
   110  // the seccomp package to build a seccomp program.
   111  func convertRules(s *specs.LinuxSeccomp) ([]seccomp.RuleSet, error) {
   112  	// NOTE: Architectures are only really relevant when calling 32bit syscalls
   113  	// on a 64bit system. Since we don't support that in gVisor anyway, we
   114  	// ignore Architectures and only test against the native architecture.
   115  
   116  	ruleset := []seccomp.RuleSet{}
   117  
   118  	for _, syscall := range s.Syscalls {
   119  		sysRules := seccomp.NewSyscallRules()
   120  
   121  		action, err := convertAction(syscall.Action)
   122  		if err != nil {
   123  			return nil, err
   124  		}
   125  
   126  		// Args
   127  		rule, err := convertArgs(syscall.Args)
   128  		if err != nil {
   129  			return nil, err
   130  		}
   131  
   132  		for _, name := range syscall.Names {
   133  			syscallNo, err := lookupSyscallNo(nativeArchAuditNo, name)
   134  			if err != nil {
   135  				// If there is an error looking up the syscall number, assume it is
   136  				// not supported on this architecture and ignore it. This is, for
   137  				// better or worse, what runc does.
   138  				log.Warningf("OCI seccomp: ignoring syscall %q", name)
   139  				continue
   140  			}
   141  
   142  			sysRules.Add(uintptr(syscallNo), rule)
   143  		}
   144  
   145  		ruleset = append(ruleset, seccomp.RuleSet{
   146  			Rules:  sysRules,
   147  			Action: action,
   148  		})
   149  	}
   150  
   151  	return ruleset, nil
   152  }
   153  
   154  // convertArgs converts an OCI seccomp argument rule to a list of seccomp.Rule.
   155  func convertArgs(args []specs.LinuxSeccompArg) (seccomp.SyscallRule, error) {
   156  	argCounts := make([]uint, 6)
   157  
   158  	for _, arg := range args {
   159  		if arg.Index > 6 {
   160  			return nil, fmt.Errorf("invalid index: %d", arg.Index)
   161  		}
   162  
   163  		argCounts[arg.Index]++
   164  	}
   165  
   166  	// NOTE: If multiple rules apply to the same argument (same index) the
   167  	// action is triggered if any one of the rules matches (OR). If not, then
   168  	// all rules much match in order to trigger the action (AND). This appears to
   169  	// be some kind of legacy behavior of runc that nevertheless needs to be
   170  	// supported to maintain compatibility.
   171  
   172  	hasMultipleArgs := false
   173  	for _, count := range argCounts {
   174  		if count > 1 {
   175  			hasMultipleArgs = true
   176  			break
   177  		}
   178  	}
   179  
   180  	if hasMultipleArgs {
   181  		rules := seccomp.Or{}
   182  
   183  		// Old runc behavior - do this for compatibility.
   184  		// Add rules as ORs by adding separate Rules.
   185  		for _, arg := range args {
   186  			rule := seccomp.PerArg{nil, nil, nil, nil, nil, nil}
   187  
   188  			if err := convertRule(arg, &rule); err != nil {
   189  				return nil, err
   190  			}
   191  
   192  			rules = append(rules, rule)
   193  		}
   194  
   195  		return rules, nil
   196  	}
   197  
   198  	// Add rules as ANDs by adding to the same Rule.
   199  	rule := seccomp.PerArg{nil, nil, nil, nil, nil, nil}
   200  	for _, arg := range args {
   201  		if err := convertRule(arg, &rule); err != nil {
   202  			return nil, err
   203  		}
   204  	}
   205  
   206  	return rule, nil
   207  }
   208  
   209  // convertRule converts and adds the arg to a PerArg rule.
   210  func convertRule(arg specs.LinuxSeccompArg, perArg *seccomp.PerArg) error {
   211  	switch arg.Op {
   212  	case specs.OpEqualTo:
   213  		perArg[arg.Index] = seccomp.EqualTo(arg.Value)
   214  	case specs.OpNotEqual:
   215  		perArg[arg.Index] = seccomp.NotEqual(arg.Value)
   216  	case specs.OpGreaterThan:
   217  		perArg[arg.Index] = seccomp.GreaterThan(arg.Value)
   218  	case specs.OpGreaterEqual:
   219  		perArg[arg.Index] = seccomp.GreaterThanOrEqual(arg.Value)
   220  	case specs.OpLessThan:
   221  		perArg[arg.Index] = seccomp.LessThan(arg.Value)
   222  	case specs.OpLessEqual:
   223  		perArg[arg.Index] = seccomp.LessThanOrEqual(arg.Value)
   224  	case specs.OpMaskedEqual:
   225  		perArg[arg.Index] = seccomp.MaskedEqual(uintptr(arg.Value), uintptr(arg.ValueTwo))
   226  	default:
   227  		return fmt.Errorf("unsupported operand: %q", arg.Op)
   228  	}
   229  	return nil
   230  }