github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/sandbox/seccomp/compiler.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package seccomp
    21  
    22  import (
    23  	"bytes"
    24  	"errors"
    25  	"fmt"
    26  	"os/exec"
    27  	"regexp"
    28  	"strconv"
    29  	"strings"
    30  
    31  	"github.com/snapcore/snapd/osutil"
    32  )
    33  
    34  var (
    35  	// version-info format: <build-id> <libseccomp-version> <hash>
    36  	// <features> Where, the hash is calculated over all syscall names
    37  	// supported by the libseccomp library. The build-id is a string of up
    38  	// to 166 chars, accommodates 128-bit MD5 (32 chars), 160-bit SHA-1 (40
    39  	// chars) generated by GNU ld, and 83-byte (166 chars) build ID
    40  	// generated by Go toolchain, also provides an upper limit of the
    41  	// user-settable build ID. The hash is a 256-bit SHA-256 (64 char)
    42  	// string. Allow libseccomp version to be 1-5 chars per field (eg, 1.2.3
    43  	// or 12345.23456.34567) and 1-30 chars of colon-separated features. Ex:
    44  	// 7ac348ac9c934269214b00d1692dfa50d5d4a157 2.3.3
    45  	// 03e996919907bc7163bc83b95bca0ecab31300f20dfa365ea14047c698340e7c
    46  	// bpf-actlog
    47  	validVersionInfo = regexp.MustCompile(`^[0-9a-f]{1,166} [0-9]{1,5}\.[0-9]{1,5}\.[0-9]{1,5} [0-9a-f]{1,64} [-a-z0-9:]{1,30}$`)
    48  )
    49  
    50  type Compiler struct {
    51  	snapSeccomp string
    52  }
    53  
    54  // NewCompiler returns a wrapper for the compiler binary. The path to the binary is
    55  // looked up using the lookupTool helper.
    56  func NewCompiler(lookupTool func(name string) (string, error)) (*Compiler, error) {
    57  	if lookupTool == nil {
    58  		panic("lookup tool func not provided")
    59  	}
    60  
    61  	path, err := lookupTool("snap-seccomp")
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	return &Compiler{snapSeccomp: path}, nil
    67  }
    68  
    69  // VersionInfo returns the version information of the compiler. The format of
    70  // version information is: <build-id> <libseccomp-version> <hash> <features>.
    71  // Where, the hash is calculated over all syscall names supported by the
    72  // libseccomp library.
    73  func (c *Compiler) VersionInfo() (VersionInfo, error) {
    74  	cmd := exec.Command(c.snapSeccomp, "version-info")
    75  	output, err := cmd.Output()
    76  	if err != nil {
    77  		if exitErr, ok := err.(*exec.ExitError); ok && len(exitErr.Stderr) > 0 {
    78  			output = exitErr.Stderr
    79  		}
    80  		return "", osutil.OutputErr(output, err)
    81  	}
    82  	raw := bytes.TrimSpace(output)
    83  	// Example valid output:
    84  	// 7ac348ac9c934269214b00d1692dfa50d5d4a157 2.3.3 03e996919907bc7163bc83b95bca0ecab31300f20dfa365ea14047c698340e7c bpf-actlog
    85  	if match := validVersionInfo.Match(raw); !match {
    86  		return "", fmt.Errorf("invalid format of version-info: %q", raw)
    87  	}
    88  
    89  	return VersionInfo(raw), nil
    90  }
    91  
    92  var compilerVersionInfoImpl = func(lookupTool func(name string) (string, error)) (VersionInfo, error) {
    93  	c, err := NewCompiler(lookupTool)
    94  	if err != nil {
    95  		return VersionInfo(""), err
    96  	}
    97  	return c.VersionInfo()
    98  }
    99  
   100  // CompilerVersionInfo returns the version information of snap-seccomp
   101  // looked up via lookupTool.
   102  func CompilerVersionInfo(lookupTool func(name string) (string, error)) (VersionInfo, error) {
   103  	return compilerVersionInfoImpl(lookupTool)
   104  }
   105  
   106  // MockCompilerVersionInfo mocks the return value of CompilerVersionInfo.
   107  func MockCompilerVersionInfo(versionInfo string) (restore func()) {
   108  	old := compilerVersionInfoImpl
   109  	compilerVersionInfoImpl = func(_ func(name string) (string, error)) (VersionInfo, error) {
   110  		return VersionInfo(versionInfo), nil
   111  	}
   112  	return func() {
   113  		compilerVersionInfoImpl = old
   114  	}
   115  }
   116  
   117  var errEmptyVersionInfo = errors.New("empty version-info")
   118  
   119  // VersionInfo represents information about the seccomp compiler
   120  type VersionInfo string
   121  
   122  // LibseccompVersion parses VersionInfo and provides the libseccomp version
   123  func (vi VersionInfo) LibseccompVersion() (string, error) {
   124  	if vi == "" {
   125  		return "", errEmptyVersionInfo
   126  	}
   127  	if match := validVersionInfo.Match([]byte(vi)); !match {
   128  		return "", fmt.Errorf("invalid format of version-info: %q", vi)
   129  	}
   130  	return strings.Split(string(vi), " ")[1], nil
   131  }
   132  
   133  // Features parses the output of VersionInfo and provides the
   134  // golang seccomp features
   135  func (vi VersionInfo) Features() (string, error) {
   136  	if vi == "" {
   137  		return "", errEmptyVersionInfo
   138  	}
   139  	if match := validVersionInfo.Match([]byte(vi)); !match {
   140  		return "", fmt.Errorf("invalid format of version-info: %q", vi)
   141  	}
   142  	return strings.Split(string(vi), " ")[3], nil
   143  }
   144  
   145  // HasFeature parses the output of VersionInfo and answers whether or
   146  // not golang-seccomp supports the feature
   147  func (vi VersionInfo) HasFeature(feature string) (bool, error) {
   148  	features, err := vi.Features()
   149  	if err != nil {
   150  		return false, err
   151  	}
   152  	for _, f := range strings.Split(features, ":") {
   153  		if f == feature {
   154  			return true, nil
   155  		}
   156  	}
   157  	return false, nil
   158  }
   159  
   160  // BuildTimeRequirementError represents the error case of a feature
   161  // that cannot be supported because of unfulfilled build time
   162  // requirements.
   163  type BuildTimeRequirementError struct {
   164  	Feature      string
   165  	Requirements []string
   166  }
   167  
   168  func (e *BuildTimeRequirementError) RequirementsString() string {
   169  	return strings.Join(e.Requirements, ", ")
   170  }
   171  
   172  func (e *BuildTimeRequirementError) Error() string {
   173  	return fmt.Sprintf("%s requires a snapd built against %s", e.Feature, e.RequirementsString())
   174  }
   175  
   176  // SupportsRobustArgumentFiltering parses the output of VersionInfo and
   177  // determines if libseccomp and golang-seccomp are new enough to support robust
   178  // argument filtering
   179  func (vi VersionInfo) SupportsRobustArgumentFiltering() error {
   180  	libseccompVersion, err := vi.LibseccompVersion()
   181  	if err != nil {
   182  		return err
   183  	}
   184  
   185  	// Parse <libseccomp version>
   186  	tmp := strings.Split(libseccompVersion, ".")
   187  	maj, err := strconv.Atoi(tmp[0])
   188  	if err != nil {
   189  		return fmt.Errorf("cannot obtain seccomp compiler information: %v", err)
   190  	}
   191  	min, err := strconv.Atoi(tmp[1])
   192  	if err != nil {
   193  		return fmt.Errorf("cannot obtain seccomp compiler information: %v", err)
   194  	}
   195  
   196  	var unfulfilledReqs []string
   197  
   198  	// libseccomp < 2.4 has significant argument filtering bugs that we
   199  	// cannot reliably work around with this feature.
   200  	if maj < 2 || (maj == 2 && min < 4) {
   201  		unfulfilledReqs = append(unfulfilledReqs, "libseccomp >= 2.4")
   202  	}
   203  
   204  	// Due to https://github.com/seccomp/libseccomp-golang/issues/22,
   205  	// golang-seccomp <= 0.9.0 cannot create correct BPFs for this feature.
   206  	// The package does not contain any version information, but we know
   207  	// that ActLog was implemented in the library after this issue was
   208  	// fixed, so base the decision on that. ActLog is first available in
   209  	// 0.9.1.
   210  	res, err := vi.HasFeature("bpf-actlog")
   211  	if err != nil {
   212  		return err
   213  	}
   214  	if !res {
   215  		unfulfilledReqs = append(unfulfilledReqs, "golang-seccomp >= 0.9.1")
   216  	}
   217  
   218  	if len(unfulfilledReqs) != 0 {
   219  		return &BuildTimeRequirementError{
   220  			Feature:      "robust argument filtering",
   221  			Requirements: unfulfilledReqs,
   222  		}
   223  	}
   224  
   225  	return nil
   226  }
   227  
   228  // Compile compiles given source profile and saves the result to the out
   229  // location.
   230  func (c *Compiler) Compile(in, out string) error {
   231  	cmd := exec.Command(c.snapSeccomp, "compile", in, out)
   232  	if output, err := cmd.CombinedOutput(); err != nil {
   233  		return osutil.OutputErr(output, err)
   234  	}
   235  	return nil
   236  }