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